3 // Support code for TLS camouflage using uTLS.
15 utls
"github.com/refraction-networking/utls"
16 "golang.org/x/net/http2"
19 // utlsClientHelloIDMap is a correspondence between human-readable labels and
20 // supported utls.ClientHelloIDs.
21 var utlsClientHelloIDMap
= []struct {
23 ID
*utls
.ClientHelloID
25 {"random", &utls
.HelloRandomizedALPN
},
26 {"Firefox", &utls
.HelloFirefox_Auto
},
27 {"Firefox_55", &utls
.HelloFirefox_55
},
28 {"Firefox_56", &utls
.HelloFirefox_56
},
29 {"Firefox_63", &utls
.HelloFirefox_63
},
30 {"Firefox_65", &utls
.HelloFirefox_65
},
31 {"Firefox_99", &utls
.HelloFirefox_99
},
32 {"Firefox_102", &utls
.HelloFirefox_102
},
33 {"Firefox_105", &utls
.HelloFirefox_105
},
34 {"Firefox_120", &utls
.HelloFirefox_120
},
35 {"Chrome", &utls
.HelloChrome_Auto
},
36 {"Chrome_58", &utls
.HelloChrome_58
},
37 {"Chrome_62", &utls
.HelloChrome_62
},
38 {"Chrome_70", &utls
.HelloChrome_70
},
39 {"Chrome_72", &utls
.HelloChrome_72
},
40 {"Chrome_83", &utls
.HelloChrome_83
},
41 {"Chrome_87", &utls
.HelloChrome_87
},
42 {"Chrome_96", &utls
.HelloChrome_96
},
43 {"Chrome_100", &utls
.HelloChrome_100
},
44 {"Chrome_102", &utls
.HelloChrome_102
},
45 {"Chrome_120", &utls
.HelloChrome_120
},
46 {"iOS", &utls
.HelloIOS_Auto
},
47 {"iOS_11_1", &utls
.HelloIOS_11_1
},
48 {"iOS_12_1", &utls
.HelloIOS_12_1
},
49 {"iOS_13", &utls
.HelloIOS_13
},
50 {"iOS_14", &utls
.HelloIOS_14
},
53 // utlsLookup returns a *utls.ClientHelloID from utlsClientHelloIDMap by a
54 // case-insensitive label match, or nil if there is no match.
55 func utlsLookup(label
string) *utls
.ClientHelloID
{
56 for _
, entry
:= range utlsClientHelloIDMap
{
57 if strings
.ToLower(label
) == strings
.ToLower(entry
.Label
) {
64 // utlsDialContext connects to the given network address and initiates a TLS
65 // handshake with the provided ClientHelloID, and returns the resulting TLS
67 func utlsDialContext(ctx context
.Context
, network
, addr
string, config
*utls
.Config
, id
*utls
.ClientHelloID
) (*utls
.UConn
, error
) {
68 // Set the SNI from addr, if not already set.
70 config
= &utls
.Config
{}
72 if config
.ServerName
== "" {
73 config
= config
.Clone()
74 host
, _
, err
:= net
.SplitHostPort(addr
)
78 config
.ServerName
= host
80 dialer
:= &net
.Dialer
{}
81 conn
, err
:= dialer
.DialContext(ctx
, network
, addr
)
85 uconn
:= utls
.UClient(conn
, config
, *id
)
86 // We must call Handshake before returning, or else the UConn may not
87 // actually use the selected ClientHelloID. It depends on whether a Read
88 // or a Write happens first. If a Read happens first, the connection
89 // will use the normal crypto/tls fingerprint. If a Write happens first,
90 // it will use the selected fingerprint as expected.
91 // https://github.com/refraction-networking/utls/issues/75
92 err
= uconn
.Handshake()
100 // The goal of utlsRoundTripper is: provide an http.RoundTripper abstraction
101 // that retains the features of http.Transport (e.g., persistent connections and
102 // HTTP/2 support), while making TLS connections using uTLS in place of
103 // crypto/tls. The challenge is: while http.Transport provides a DialTLSContext
104 // hook, setting it to non-nil disables automatic HTTP/2 support in the client.
105 // Most of the uTLS fingerprints contain an ALPN extension containing "h2";
106 // i.e., they declare support for HTTP/2. If the server also supports HTTP/2,
107 // then uTLS may negotiate an HTTP/2 connection without the http.Transport
108 // knowing it, which leads to an HTTP/1.1 client speaking to an HTTP/2 server, a
111 // The code here uses an idea adapted from meek_lite in obfs4proxy:
112 // https://gitlab.com/yawning/obfs4/commit/4d453dab2120082b00bf6e63ab4aaeeda6b8d8a3
113 // Instead of setting DialTLSContext on an http.Transport and exposing it
114 // directly, we expose a wrapper type, utlsRoundTripper, which contains within
115 // it either an http.Transport or an http2.Transport. The first time a caller
116 // calls RoundTrip on the wrapper, we initiate a uTLS connection
117 // (bootstrapConn), then peek at the ALPN-negotiated protocol: if "h2", create
118 // an internal http2.Transport; otherwise, create an internal http.Transport. In
119 // either case, set DialTLSContext (or DialTLS for http2.Transport) on the
120 // created Transport to a function that dials using uTLS. As a special case, the
121 // first time the DialTLS callback is called, it reuses bootstrapConn (the one
122 // made to peek at the ALPN), rather than make a new connection.
124 // Subsequent calls to RoundTripper on the wrapper just pass the requests though
125 // the previously created http.Transport or http2.Transport. We assume that in
126 // future RoundTrips, the ALPN-negotiated protocol will remain the same as it
127 // was in the initial RoundTrip. At this point it is the http.Transport or
128 // http2.Transport calling DialTLSContext, not us, so we cannot dynamically swap
129 // the underlying transport based on the ALPN.
131 // https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/meek/29077
132 // https://github.com/refraction-networking/utls/issues/16
134 // utlsRoundTripper is an http.RoundTripper that uses uTLS (with a specified
135 // ClientHelloID) to make TLS connections.
137 // Can only be reused among servers which negotiate the same ALPN.
138 type utlsRoundTripper
struct {
139 clientHelloID
*utls
.ClientHelloID
142 inner http
.RoundTripper
145 // NewUTLSRoundTripper creates a utlsRoundTripper with the given TLS
146 // configuration and ClientHelloID.
147 func NewUTLSRoundTripper(config
*utls
.Config
, id
*utls
.ClientHelloID
) *utlsRoundTripper
{
148 return &utlsRoundTripper
{
151 // inner will be set in the first call to RoundTrip.
155 func (rt
*utlsRoundTripper
) RoundTrip(req
*http
.Request
) (*http
.Response
, error
) {
156 switch req
.URL
.Scheme
{
158 // If http, don't invoke uTLS; just pass it to an ordinary http.Transport.
159 return http
.DefaultTransport
.RoundTrip(req
)
162 return nil, fmt
.Errorf("unsupported URL scheme %q", req
.URL
.Scheme
)
168 // On the first call, make an http.Transport or http2.Transport
170 rt
.inner
, err
= makeRoundTripper(req
, rt
.config
, rt
.clientHelloID
)
172 rt
.innerLock
.Unlock()
177 // Forward the request to the inner http.Transport or http2.Transport.
178 return rt
.inner
.RoundTrip(req
)
181 // makeRoundTripper makes a bootstrap TLS configuration using the given TLS
182 // configuration and ClientHelloID, and creates an http.Transport or
183 // http2.Transport, depending on the negotated ALPN. The Transport is set up to
184 // make future TLS connections using the same TLS configuration and
186 func makeRoundTripper(req
*http
.Request
, config
*utls
.Config
, id
*utls
.ClientHelloID
) (http
.RoundTripper
, error
) {
187 addr
, err
:= addrForDial(req
.URL
)
192 bootstrapConn
, err
:= utlsDialContext(req
.Context(), "tcp", addr
, config
, id
)
197 // Peek at the ALPN-negotiated protocol.
198 protocol
:= bootstrapConn
.ConnectionState().NegotiatedProtocol
200 // Protects bootstrapConn.
202 // This is the callback for future dials done by the inner
203 // http.Transport or http2.Transport.
204 dialTLSContext
:= func(ctx context
.Context
, network
, addr
string) (net
.Conn
, error
) {
208 // On the first dial, reuse bootstrapConn.
209 if bootstrapConn
!= nil {
210 uconn
:= bootstrapConn
215 // Later dials make a new connection.
216 uconn
, err
:= utlsDialContext(ctx
, "tcp", addr
, config
, id
)
220 if uconn
.ConnectionState().NegotiatedProtocol
!= protocol
{
221 return nil, fmt
.Errorf("unexpected switch from ALPN %q to %q",
222 protocol
, uconn
.ConnectionState().NegotiatedProtocol
)
228 // Construct an http.Transport or http2.Transport depending on ALPN.
230 case http2
.NextProtoTLS
:
231 // Unfortunately http2.Transport does not expose the same
232 // configuration options as http.Transport with regard to
233 // timeouts, etc., so we are at the mercy of the defaults.
234 // https://github.com/golang/go/issues/16581
235 return &http2
.Transport
{
236 DialTLS
: func(network
, addr
string, _
*tls
.Config
) (net
.Conn
, error
) {
237 // Ignore the *tls.Config parameter; use our
238 // static config instead.
239 return dialTLSContext(context
.Background(), network
, addr
)
243 // With http.Transport, copy important default fields from
244 // http.DefaultTransport, such as TLSHandshakeTimeout and
245 // IdleConnTimeout, before overriding DialTLSContext.
246 tr
:= http
.DefaultTransport
.(*http
.Transport
).Clone()
247 tr
.DialTLSContext
= dialTLSContext
252 // addrForDial extracts a host:port address from a URL, suitable for dialing.
253 func addrForDial(url
*url
.URL
) (string, error
) {
254 host
:= url
.Hostname()
255 // net/http would use golang.org/x/net/idna here, to convert a possible
256 // internationalized domain name to ASCII.
259 // No port? Use the default for the scheme.
266 return "", fmt
.Errorf("unsupported URL scheme %q", url
.Scheme
)
269 return net
.JoinHostPort(host
, port
), nil