7
votes

I am trying to test a proxy that is expecting an SSL handshake immediately, even if the client is going to use an SSL CONNECT method. The problem is that my Golang and Python test code both seem to have the same flaw. They connect to the proxy in the clear, and then issue a CONNECT, but the proxy rejects this because it is expecting an SSL handshake.

Does anyone know how to force either technology to use SSL to the proxy using available standard libraries?

Thanks.

Sample code follows:

#!/usr/bin/python

try:
    import urllib2

    proxy = urllib2.ProxyHandler({'https': "myproxyip:6881"})
    opener = urllib2.build_opener(proxy)
    urllib2.install_opener(opener)
    print urllib2.urlopen('https://www.google.ca').read()

except Exception as err:
    print err


try:
    import httplib

    c = httplib.HTTPSConnection('myproxyip', 6881)
    c.set_tunnel('google.ca', 443)
    c.request('GET', '/')
    res = c.getresponse()
    print res.status, res.reason

except Exception as err:
    print err

and golang

// vim: ft=go ts=4 sw=4 et ai:
package main

import (
    "net/http"
    "log"
    "io/ioutil"
    "time"
    "crypto/tls"
    "net/url"
    )

var use_proxy = true
var proxy = "https://myproxyip:6881"
var req_url = "https://google.ca"
var n_seconds time.Duration = 15
var period = time.Second * n_seconds
var n_threads = 50
var thread_start_delay time.Duration = 1

func http_fetch(req_url string) {
    tr := http.Transport {
        TLSClientConfig: &tls.Config {
            InsecureSkipVerify: true,
        },
    }
    proxy_url, err := url.Parse(proxy)
    if err != nil {
        log.Fatal(err)
    }
    if use_proxy {
        tr.Proxy = http.ProxyURL(proxy_url)
    }
    client := &http.Client{}
    client.Transport = &tr

    req, err := http.NewRequest("GET", req_url, nil)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36")

    res, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    body, err := ioutil.ReadAll(res.Body)
    defer res.Body.Close()
    document := string(body)
    print(document)
}


func main() {
    println("main: Starting", n_threads, "threads to hammer", req_url, "every", n_seconds, "seconds, ctrl-c to quit...")
    done := make(<-chan bool)
    for i:= 0; i < n_threads; i++ {
        go func(thread_id int) {
            println("thread", thread_id, ": starting periodic_hammer")
            for {
                println("thread", thread_id, ": fetching", req_url)
                http_fetch(req_url)
                println("thread", thread_id, ": done, sleeping for", n_seconds, "seconds")
                time.Sleep(period)
            }
        }(i+1)
        println("main: delaying", thread_start_delay, "seconds before starting next thread")
        time.Sleep(thread_start_delay * time.Second)
    }
    <-done
}
3
Looks like the Go version will always issue "CONNECT" for HTTPS (see source) - I don't know much about HTTPS but if this is not standards-compliant behavior your might submit a bug report. - Adrian
I think it used to be standards compliant but we're in the middle of a transition to pure SSL on proxies for HTTPS. Browser's support it, but I'm not finding anything but the very latest libcurl that supports it. - Michael Soulier
If it's not standards-compliant then support will be spotty at best, unfortunately. - Adrian

3 Answers

1
votes

Using https proxies is only supported starting with Go 1.10. From the draft release notes:

On the client side, an HTTP proxy (most commonly configured by ProxyFromEnvironment) can now be specified as an https:// URL, meaning that the client connects to the proxy over HTTPS before issuing a standard, proxied HTTP request. (Previously, HTTP proxy URLs were required to begin with http:// or socks5://.)

0
votes

A possible resolution to this issue using Python standard libraries in addition to the ProxyHandler you have:

import ssl, urllib2

# Set ciphers and ssl settings
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
ctx.set_ciphers('HIGH:!DH:!aNULL')

# Set proxy settings
proxy = urllib2.ProxyHandler({'https':'<https proxy host>:443', 'http':'<http proxy host>:8080'})
opener = urllib2.build_opener(urllib2.HTTPSHandler(context=ctx), proxy)
urllib2.install_opener(opener)
0
votes

In the end I had to do my own client, essentially.

func http_fetch(req_host string) {
    buf := make([]byte, 1024)
    conf := &tls.Config {
        InsecureSkipVerify: true,
    }   

    conn, err := tls.Dial("tcp", proxy_host, conf)
    if err != nil {
        //panic(err)
        failure()
        return
    }   
    defer conn.Close()

    // SSL CONNECT
    if _, err = conn.Write([]byte("CONNECT " + req_host + " HTTP/1.0\r\n\r\n")); err != nil {
        // FIXME: need debug logger?
        //panic(err)
        failure()
        return
    }   

    // Read response
    if _, err = conn.Read(buf); err != nil {
        //panic(err)
        failure()
        return
    }   

    // Send http GET
    if _, err = conn.Write([]byte("GET / HTTP/1.0\r\n")); err != nil {
        //panic(err)
        failure()
        return
    }   
    if _, err = conn.Write([]byte("User-Agent: golang\r\n\r\n")); err != nil {
        //panic(err)
        failure()
        return
    }   

    // Read response
    if _, err = conn.Read(buf); err != nil {
        //panic(err)
        failure()
        return
    }   
    success()
}