[tor-commits] [meek/master] https proxy support for uTLS.

dcf at torproject.org dcf at torproject.org
Thu May 9 19:37:25 UTC 2019


commit 4290a67a65a98303b2feb34a230ab23daabb9895
Author: David Fifield <david at 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 | 27 +++++++++++++
 meek-client/proxy_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++-----
 meek-client/utls.go       | 14 ++++++-
 meek-client/utls_test.go  |  1 +
 4 files changed, 127 insertions(+), 11 deletions(-)

diff --git a/meek-client/proxy_http.go b/meek-client/proxy_http.go
index 1136eb2..f9aeb71 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,29 @@ 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,
+			// We use the same uTLS ClientHelloID for the TLS
+			// connection to the HTTPS proxy, as we use for the TLS
+			// connection through the tunnel.
+			clientHelloID: clientHelloID,
+			forward:       forward,
+		},
+	}, nil
+}
diff --git a/meek-client/proxy_test.go b/meek-client/proxy_test.go
index c13cf12..b44c9a2 100644
--- a/meek-client/proxy_test.go
+++ b/meek-client/proxy_test.go
@@ -2,12 +2,21 @@ package main
 
 import (
 	"bufio"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
 	"io"
+	"math/big"
 	"net"
 	"net/http"
 	"net/url"
 	"testing"
+	"time"
 
+	utls "github.com/refraction-networking/utls"
 	"golang.org/x/net/proxy"
 )
 
@@ -67,15 +76,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(t *testing.T, makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) {
+func requestResultingFromDial(t *testing.T, 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)
@@ -109,9 +112,18 @@ func requestResultingFromDial(t *testing.T, makeProxy func(addr net.Addr) (*http
 	return <-ch, nil
 }
 
+func requestResultingFromDialHTTP(t *testing.T, 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(t, ln, makeProxy, network, addr)
+}
+
 // Test that the HTTP proxy client sends a correct request.
 func TestProxyHTTPCONNECT(t *testing.T) {
-	req, err := requestResultingFromDial(t, func(addr net.Addr) (*httpProxy, error) {
+	req, err := requestResultingFromDialHTTP(t, func(addr net.Addr) (*httpProxy, error) {
 		return ProxyHTTP("tcp", addr.String(), nil, proxy.Direct)
 	}, "tcp", testAddr)
 	if err != nil {
@@ -134,7 +146,7 @@ func TestProxyHTTPProxyAuthorization(t *testing.T) {
 		User:     testUsername,
 		Password: testPassword,
 	}
-	req, err := requestResultingFromDial(t, func(addr net.Addr) (*httpProxy, error) {
+	req, err := requestResultingFromDialHTTP(t, func(addr net.Addr) (*httpProxy, error) {
 		return ProxyHTTP("tcp", addr.String(), auth, proxy.Direct)
 	}, "tcp", testAddr)
 	if err != nil {
@@ -163,3 +175,69 @@ func TestProxyHTTPProxyAuthorization(t *testing.T) {
 		t.Errorf("expected password %q, got %q", testPassword, password)
 	}
 }
+
+// Create a TLS listener using a temporary self-signed certificate.
+// https://golang.org/src/crypto/tls/generate_cert.go
+func selfSignedTLSListen(network, addr string) (net.Listener, error) {
+	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,
+			},
+		},
+	}
+
+	return tls.Listen(network, addr, &config)
+}
+
+func requestResultingFromDialHTTPS(t *testing.T, makeProxy func(addr net.Addr) (*httpProxy, error), network, addr string) (*http.Request, error) {
+	ln, err := selfSignedTLSListen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, err
+	}
+	defer ln.Close()
+	return requestResultingFromDial(t, ln, makeProxy, network, addr)
+}
+
+func TestProxyHTTPSCONNECT(t *testing.T) {
+	req, err := requestResultingFromDialHTTPS(t, 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 5c1e484..66b1a93 100644
--- a/meek-client/utls.go
+++ b/meek-client/utls.go
@@ -155,7 +155,7 @@ func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
 // use by setting Proxy on an http.Transport), and unlike when using the browser
 // helper (the browser has its own proxy support), when using uTLS we have to
 // craft our own proxy connections.
-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
@@ -181,6 +181,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)
 	}
@@ -293,7 +303,7 @@ func NewUTLSRoundTripper(name string, cfg *utls.Config, proxyURL *url.URL) (http
 		return httpRoundTripper, nil
 	}
 
-	proxyDialer, err := makeProxyDialer(proxyURL)
+	proxyDialer, err := makeProxyDialer(proxyURL, cfg, clientHelloID)
 	if err != nil {
 		return nil, err
 	}
diff --git a/meek-client/utls_test.go b/meek-client/utls_test.go
index fbdd969..eebb1db 100644
--- a/meek-client/utls_test.go
+++ b/meek-client/utls_test.go
@@ -264,6 +264,7 @@ func TestUTLSHTTPWithProxy(t *testing.T) {
 	for _, proxyURL := range []url.URL{
 		url.URL{Scheme: "socks5", Host: proxyLn.Addr().String()},
 		url.URL{Scheme: "http", Host: proxyLn.Addr().String()},
+		url.URL{Scheme: "https", Host: proxyLn.Addr().String()},
 	} {
 		rt, err := NewUTLSRoundTripper("HelloFirefox_63", &utls.Config{InsecureSkipVerify: true}, &proxyURL)
 		if err != nil {





More information about the tor-commits mailing list