commit 2b9b67b05f4c4a8f1c97cf78002bca1cc2fdd78e Author: David Fifield david@bamsoftware.com Date: Fri Feb 1 01:46:07 2019 -0700
https proxy support for uTLS.
Tor doesn't support this kind of proxy (https://bugs.torproject.org/26306), but I want to support the same kinds of proxies with uTLS as are supported using the native Go net/http, for ease of explaining the proxy restrictions in the man page.
We use the same uTLS ClientHelloID for the TLS connection to the HTTPS proxy, as we use for the TLS connection through the tunnel. --- meek-client/proxy_http.go | 24 +++++++++++++ meek-client/proxy_test.go | 92 ++++++++++++++++++++++++++++++++++++++++++----- meek-client/utls.go | 14 ++++++-- 3 files changed, 119 insertions(+), 11 deletions(-)
diff --git a/meek-client/proxy_http.go b/meek-client/proxy_http.go index 1136eb2..933214f 100644 --- a/meek-client/proxy_http.go +++ b/meek-client/proxy_http.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url"
+ utls "github.com/refraction-networking/utls" "golang.org/x/net/proxy" )
@@ -74,3 +75,26 @@ func ProxyHTTP(network, addr string, auth *proxy.Auth, forward proxy.Dialer) (*h forward: forward, }, nil } + +type UTLSDialer struct { + config *utls.Config + clientHelloID *utls.ClientHelloID + forward proxy.Dialer +} + +func (dialer *UTLSDialer) Dial(network, addr string) (net.Conn, error) { + return dialUTLS(network, addr, dialer.config, dialer.clientHelloID, dialer.forward) +} + +func ProxyHTTPS(network, addr string, auth *proxy.Auth, forward proxy.Dialer, cfg *utls.Config, clientHelloID *utls.ClientHelloID) (*httpProxy, error) { + return &httpProxy{ + network: network, + addr: addr, + auth: auth, + forward: &UTLSDialer{ + config: cfg, + clientHelloID: clientHelloID, + forward: forward, + }, + }, nil +} diff --git a/meek-client/proxy_test.go b/meek-client/proxy_test.go index 783165a..0b37fb3 100644 --- a/meek-client/proxy_test.go +++ b/meek-client/proxy_test.go @@ -2,11 +2,20 @@ package main
import ( "bufio" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "math/big" "net" "net/http" "net/url" "testing" + "time"
+ utls "github.com/refraction-networking/utls" "golang.org/x/net/proxy" )
@@ -66,15 +75,9 @@ func TestAddrForDial(t *testing.T) {
// Dial the given address with the given proxy, and return the http.Request that // the proxy server would have received. -func requestResultingFromDial(makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) { +func requestResultingFromDial(ln net.Listener, makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) { ch := make(chan *http.Request, 1)
- ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, err - } - defer ln.Close() - go func() { defer func() { close(ch) @@ -102,9 +105,18 @@ func requestResultingFromDial(makeProxy func(addr net.Addr) (*httpProxy, error), return <-ch, nil }
+func requestResultingFromDialHTTP(makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + defer ln.Close() + return requestResultingFromDial(ln, makeProxy, network, addr) +} + // Test that the HTTP proxy client sends a correct request. func TestProxyHTTPCONNECT(t *testing.T) { - req, err := requestResultingFromDial(func(addr net.Addr) (*httpProxy, error) { + req, err := requestResultingFromDialHTTP(func(addr net.Addr) (*httpProxy, error) { return ProxyHTTP("tcp", addr.String(), nil, proxy.Direct) }, "tcp", testAddr) if err != nil { @@ -127,7 +139,7 @@ func TestProxyHTTPProxyAuthorization(t *testing.T) { User: testUsername, Password: testPassword, } - req, err := requestResultingFromDial(func(addr net.Addr) (*httpProxy, error) { + req, err := requestResultingFromDialHTTP(func(addr net.Addr) (*httpProxy, error) { return ProxyHTTP("tcp", addr.String(), auth, proxy.Direct) }, "tcp", testAddr) if err != nil { @@ -156,3 +168,65 @@ func TestProxyHTTPProxyAuthorization(t *testing.T) { t.Errorf("expected password %q, got %q", testPassword, password) } } + +func requestResultingFromDialHTTPS(makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) { + // Create a TLS listener using a temporary self-signed certificate. + // https://golang.org/src/crypto/tls/generate_cert.go + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + + notBefore := time.Now() + notAfter := notBefore.Add(100 * time.Second) + template := x509.Certificate{ + SerialNumber: big.NewInt(123), + Subject: pkix.Name{ + Organization: []string{"Test"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + cert, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + config := tls.Config{ + Certificates: []tls.Certificate{ + tls.Certificate{ + Certificate: [][]byte{cert}, + PrivateKey: priv, + }, + }, + } + + ln, err := tls.Listen("tcp", "127.0.0.1:0", &config) + if err != nil { + return nil, err + } + defer ln.Close() + return requestResultingFromDial(ln, makeProxy, network, addr) +} + +func TestProxyHTTPSCONNECT(t *testing.T) { + req, err := requestResultingFromDialHTTPS(func(addr net.Addr) (*httpProxy, error) { + return ProxyHTTPS("tcp", addr.String(), nil, proxy.Direct, &utls.Config{InsecureSkipVerify: true}, &utls.HelloFirefox_Auto) + }, "tcp", testAddr) + if err != nil { + panic(err) + } + if req.Method != "CONNECT" { + t.Errorf("expected method %q, got %q", "CONNECT", req.Method) + } + if req.URL.Hostname() != testHost || req.URL.Port() != testPort { + t.Errorf("expected URL %q, got %q", testAddr, req.URL.String()) + } + if req.Host != testAddr { + t.Errorf("expected %q, got %q", "Host: "+req.Host, "Host: "+testAddr) + } +} diff --git a/meek-client/utls.go b/meek-client/utls.go index cbb2aaa..1ea0cd0 100644 --- a/meek-client/utls.go +++ b/meek-client/utls.go @@ -146,7 +146,7 @@ func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) return rt.rt.RoundTrip(req) }
-func makeProxyDialer(proxyURL *url.URL) (proxy.Dialer, error) { +func makeProxyDialer(proxyURL *url.URL, cfg *utls.Config, clientHelloID *utls.ClientHelloID) (proxy.Dialer, error) { var proxyDialer proxy.Dialer = proxy.Direct if proxyURL == nil { return proxyDialer, nil @@ -172,6 +172,16 @@ func makeProxyDialer(proxyURL *url.URL) (proxy.Dialer, error) { proxyDialer, err = proxy.SOCKS5("tcp", proxyAddr, auth, proxyDialer) case "http": proxyDialer, err = ProxyHTTP("tcp", proxyAddr, auth, proxyDialer) + case "https": + // We use the same uTLS Config for TLS to the HTTPS proxy, as we + // use for HTTPS connections through the tunnel. We make a clone + // of the Config to avoid concurrent modification as the two + // layers set the ServerName value. + var cfgClone *utls.Config + if cfg != nil { + cfgClone = cfg.Clone() + } + proxyDialer, err = ProxyHTTPS("tcp", proxyAddr, auth, proxyDialer, cfgClone, clientHelloID) default: return nil, fmt.Errorf("cannot use proxy scheme %q with uTLS", proxyURL.Scheme) } @@ -282,7 +292,7 @@ func NewUTLSRoundTripper(name string, cfg *utls.Config, proxyURL *url.URL) (http // Special case for "none" and HelloGolang. return httpRoundTripper, nil } - proxyDialer, err := makeProxyDialer(proxyURL) + proxyDialer, err := makeProxyDialer(proxyURL, cfg, clientHelloID) if err != nil { return nil, err }