forked from elazarl/goproxy
/
utls.go
420 lines (375 loc) · 13.4 KB
/
utls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
// Support code for TLS camouflage using uTLS.
//
// The goal is: provide an http.RoundTripper abstraction that retains the
// features of http.Transport (e.g., persistent connections and HTTP/2 support),
// while making TLS connections using uTLS in place of crypto/tls. The challenge
// is: while http.Transport provides a DialTLS hook, setting it to non-nil
// disables automatic HTTP/2 support in the client. Most of the uTLS
// fingerprints contain an ALPN extension containing "h2"; i.e., they declare
// support for HTTP/2. If the server also supports HTTP/2, then uTLS may
// negotiate an HTTP/2 connection without the http.Transport knowing it, which
// leads to an HTTP/1.1 client speaking to an HTTP/2 server, a protocol error.
//
// The code here uses an idea adapted from meek_lite in obfs4proxy:
// https://gitlab.com/yawning/obfs4/commit/4d453dab2120082b00bf6e63ab4aaeeda6b8d8a3
// Instead of setting DialTLS on an http.Transport and exposing it directly, we
// expose a wrapper type, UTLSRoundTripper, that contains within it either an
// http.Transport or an http2.Transport. The first time a caller calls RoundTrip
// on the wrapper, we initiate a uTLS connection (bootstrapConn), then peek at
// the ALPN-negotiated protocol: if "h2", create an internal http2.Transport;
// otherwise, create an internal http.Transport. In either case, set DialTLS on
// the created Transport to a function that dials using uTLS. As a special case,
// the first time the DialTLS callback is called, it reuses bootstrapConn (the
// one made to peek at the ALPN), rather than make a new connection.
//
// Subsequent calls to RoundTripper on the wrapper just pass the requests though
// the previously created http.Transport or http2.Transport. We assume that in
// future RoundTrips, the ALPN-negotiated protocol will remain the same as it
// was in the initial RoundTrip. At this point it is the http.Transport or
// http2.Transport calling DialTLS, not us, so we can't dynamically swap the
// underlying transport based on the ALPN.
//
// https://bugs.torproject.org/29077
// https://github.com/refraction-networking/utls/issues/16
package goproxy
import (
"bufio"
"crypto/tls"
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"sync"
utls "github.com/refraction-networking/utls"
"golang.org/x/net/http2"
"golang.org/x/net/proxy"
)
var httpRoundTripper *http.Transport = http.DefaultTransport.(*http.Transport).Clone()
type httpProxy struct {
network, addr string
auth *proxy.Auth
forward proxy.Dialer
}
func (pr *httpProxy) Dial(network, addr string) (net.Conn, error) {
connectReq := &http.Request{
Method: "CONNECT",
URL: &url.URL{Opaque: addr},
Host: addr,
Header: make(http.Header),
}
// http.Transport has a ProxyConnectHeader field that we are ignoring
// here.
if pr.auth != nil {
connectReq.Header.Set("Proxy-Authorization", "basic "+
base64.StdEncoding.EncodeToString([]byte(pr.auth.User+":"+pr.auth.Password)))
}
conn, err := pr.forward.Dial(pr.network, pr.addr)
if err != nil {
return nil, err
}
err = connectReq.Write(conn)
if err != nil {
conn.Close()
return nil, err
}
// The Go stdlib says: "Okay to use and discard buffered reader here,
// because TLS server will not speak until spoken to."
br := bufio.NewReader(conn)
resp, err := http.ReadResponse(br, connectReq)
if br.Buffered() != 0 {
panic(br.Buffered())
}
if err != nil {
conn.Close()
return nil, err
}
if resp.StatusCode != 200 {
conn.Close()
return nil, fmt.Errorf("proxy server returned %q", resp.Status)
}
return conn, nil
}
func ProxyHTTP(network, addr string, auth *proxy.Auth, forward proxy.Dialer) (*httpProxy, error) {
return &httpProxy{
network: network,
addr: addr,
auth: auth,
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
}
// Extract a host:port address from a URL, suitable for passing to net.Dial.
func addrForDial(url *url.URL) (string, error) {
host := url.Hostname()
// net/http would use golang.org/x/net/idna here, to convert a possible
// internationalized domain name to ASCII.
port := url.Port()
if port == "" {
// No port? Use the default for the scheme.
switch url.Scheme {
case "http":
port = "80"
case "https":
port = "443"
default:
return "", fmt.Errorf("unsupported URL scheme %q", url.Scheme)
}
}
return net.JoinHostPort(host, port), nil
}
// Analogous to tls.Dial. Connect to the given address and initiate a TLS
// handshake using the given ClientHelloID, returning the resulting connection.
func dialUTLS(network, addr string, cfg *utls.Config, clientHelloID *utls.ClientHelloID, forward proxy.Dialer) (*utls.UConn, error) {
conn, err := forward.Dial(network, addr)
if err != nil {
return nil, err
}
cfg.MaxVersion = utls.VersionTLS13
uconn := utls.UClient(conn, cfg, *clientHelloID)
if cfg == nil || cfg.ServerName == "" {
serverName, _, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
uconn.SetSNI(serverName)
}
err = uconn.Handshake()
if err != nil {
return nil, err
}
return uconn, nil
}
// A http.RoundTripper that uses uTLS (with a specified Client Hello ID) to make
// TLS connections.
//
// Can only be reused among servers which negotiate the same ALPN.
type UTLSRoundTripper struct {
sync.Mutex
clientHelloID *utls.ClientHelloID
config *utls.Config
proxyDialer proxy.Dialer
rt http.RoundTripper
// Transport for HTTP requests, which don't use uTLS.
httpRT *http.Transport
}
func (rt *UTLSRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
switch req.URL.Scheme {
case "http":
// If http, we don't invoke uTLS; just pass it to an ordinary http.Transport.
return rt.httpRT.RoundTrip(req)
case "https":
default:
return nil, fmt.Errorf("unsupported URL scheme %q", req.URL.Scheme)
}
rt.Lock()
defer rt.Unlock()
if rt.rt == nil {
// On the first call, make an http.Transport or http2.Transport
// as appropriate.
var err error
rt.rt, err = makeRoundTripper(req.URL, rt.clientHelloID, rt.config, rt.proxyDialer)
if err != nil {
return nil, err
}
}
// Forward the request to the internal http.Transport or http2.Transport.
return rt.rt.RoundTrip(req)
}
// Unlike when using the native Go net/http (whose built-in proxy support we can
// 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, cfg *utls.Config, clientHelloID *utls.ClientHelloID) (proxy.Dialer, error) {
var proxyDialer proxy.Dialer = proxy.Direct
if proxyURL == nil {
return proxyDialer, nil
}
proxyAddr, err := addrForDial(proxyURL)
if err != nil {
return nil, err
}
var auth *proxy.Auth
if userpass := proxyURL.User; userpass != nil {
auth = &proxy.Auth{
User: userpass.Username(),
}
if password, ok := userpass.Password(); ok {
auth.Password = password
}
}
switch proxyURL.Scheme {
case "socks5":
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)
}
return proxyDialer, err
}
func makeRoundTripper(url *url.URL, clientHelloID *utls.ClientHelloID, cfg *utls.Config, proxyDialer proxy.Dialer) (http.RoundTripper, error) {
addr, err := addrForDial(url)
if err != nil {
return nil, err
}
// Connect to the given address, through a proxy if requested, and
// initiate a TLS handshake using the given ClientHelloID. Return the
// resulting connection.
dial := func(network, addr string) (*utls.UConn, error) {
return dialUTLS(network, addr, cfg, clientHelloID, proxyDialer)
}
bootstrapConn, err := dial("tcp", addr)
if err != nil {
return nil, err
}
// Peek at what protocol we negotiated.
protocol := bootstrapConn.ConnectionState().NegotiatedProtocol
// Protects bootstrapConn.
var lock sync.Mutex
// This is the callback for future dials done by the internal
// http.Transport or http2.Transport.
dialTLS := func(network, addr string) (net.Conn, error) {
lock.Lock()
defer lock.Unlock()
// On the first dial, reuse bootstrapConn.
if bootstrapConn != nil {
uconn := bootstrapConn
bootstrapConn = nil
return uconn, nil
}
// Later dials make a new connection.
uconn, err := dial(network, addr)
if err != nil {
return nil, err
}
if uconn.ConnectionState().NegotiatedProtocol != protocol {
return nil, fmt.Errorf("unexpected switch from ALPN %q to %q",
protocol, uconn.ConnectionState().NegotiatedProtocol)
}
return uconn, nil
}
// Construct an http.Transport or http2.Transport depending on ALPN.
switch protocol {
case http2.NextProtoTLS:
// Unfortunately http2.Transport does not expose the same
// configuration options as http.Transport with regard to
// timeouts, etc., so we are at the mercy of the defaults.
// https://github.com/golang/go/issues/16581
return &http2.Transport{
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
// Ignore the *tls.Config parameter; use our
// static cfg instead.
return dialTLS(network, addr)
},
}, nil
default:
// With http.Transport, copy important default fields from
// http.DefaultTransport, such as TLSHandshakeTimeout and
// IdleConnTimeout, before overriding DialTLS.
tr := httpRoundTripper.Clone()
tr.DialTLS = dialTLS
return tr, nil
}
}
func MakeHelloIDNoALPN() utls.ClientHelloID {
clientHelloID := utls.HelloRandomizedNoALPN
return PatchHelloID(clientHelloID)
}
func MakeHelloIDWithALPN() utls.ClientHelloID {
clientHelloID := utls.HelloRandomizedALPN
return PatchHelloID(clientHelloID)
}
func PatchHelloID(clientHelloID utls.ClientHelloID) utls.ClientHelloID {
var weights = utls.DefaultWeights
weights.TLSVersMax_Set_VersionTLS13 = 1
weights.CipherSuites_Remove_RandomCiphers = 0
clientHelloID.Weights = &weights
return clientHelloID
}
var RandomizedMaxTlsHelloIdNoALPN = MakeHelloIDNoALPN()
var RandomizedMaxTlsHelloIdALPN = MakeHelloIDWithALPN()
// When you update this map, also update the man page in doc/meek-client.1.txt.
// https://github.com/refraction-networking/utls/blob/master/u_common.go
var clientHelloIDMap = map[string]*utls.ClientHelloID{
// No HelloCustom: not useful for external configuration.
// No HelloRandomized: doesn't negotiate consistent ALPN.
"none": nil, // special case: disable uTLS
"hellogolang": nil, // special case: disable uTLS
"hellorandomizedalpn": &utls.HelloRandomizedALPN,
"hellorandomizednoalpn": &utls.HelloRandomizedNoALPN,
"hellorandomizednoalpn_maxtls": &RandomizedMaxTlsHelloIdNoALPN,
"hellofirefox_auto": &utls.HelloFirefox_Auto,
"hellofirefox_55": &utls.HelloFirefox_55,
"hellofirefox_56": &utls.HelloFirefox_56,
"hellofirefox_63": &utls.HelloFirefox_63,
"hellofirefox_65": &utls.HelloFirefox_65,
"hellochrome_auto": &utls.HelloChrome_Auto,
"hellochrome_58": &utls.HelloChrome_58,
"hellochrome_62": &utls.HelloChrome_62,
"hellochrome_70": &utls.HelloChrome_70,
"hellochrome_72": &utls.HelloChrome_72,
"hellochrome_83": &utls.HelloChrome_83,
"helloios_auto": &utls.HelloIOS_Auto,
"helloios_11_1": &utls.HelloIOS_11_1,
"helloios_12_1": &utls.HelloIOS_12_1,
}
func NewUTLSRoundTripper(name string, cfg *utls.Config, proxyURL *url.URL) (http.RoundTripper, error) {
// Lookup is case-insensitive.
clientHelloID, ok := clientHelloIDMap[strings.ToLower(name)]
if !ok {
return nil, fmt.Errorf("no uTLS Client Hello ID named %q", name)
}
if clientHelloID == nil {
// Special case for "none" and HelloGolang.
return httpRoundTripper, nil
}
proxyDialer, err := makeProxyDialer(proxyURL, cfg, clientHelloID)
if err != nil {
return nil, err
}
// This special-case RoundTripper is used for HTTP requests, which don't
// use uTLS but should use the specified proxy.
httpRT := httpRoundTripper.Clone()
httpRT.Proxy = http.ProxyURL(proxyURL)
return &UTLSRoundTripper{
clientHelloID: clientHelloID,
config: cfg,
proxyDialer: proxyDialer,
// rt will be set in the first call to RoundTrip.
httpRT: httpRT,
}, nil
}