1package main
2
3import (
4	"bufio"
5	"encoding/hex"
6	"fmt"
7	"net"
8	"net/http"
9	"net/http/httputil"
10	"net/url"
11	"time"
12
13	"gitlab.com/yawning/utls.git"
14	"golang.org/x/net/http2"
15)
16
17var (
18	dialTimeout   = time.Duration(15) * time.Second
19	sessionTicket = []uint8(`Here goes phony session ticket: phony enough to get into ASCII range
20Ticket could be of any length, but for camouflage purposes it's better to use uniformly random contents
21and common length. See https://tlsfingerprint.io/session-tickets`)
22)
23
24var requestHostname = "facebook.com" // speaks http2 and TLS 1.3
25var requestAddr = "31.13.72.36:443"
26
27func HttpGetDefault(hostname string, addr string) (*http.Response, error) {
28	config := tls.Config{ServerName: hostname}
29	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
30	if err != nil {
31		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
32	}
33	tlsConn := tls.Client(dialConn, &config)
34	defer tlsConn.Close()
35	return httpGetOverConn(tlsConn, tlsConn.ConnectionState().NegotiatedProtocol)
36}
37
38func HttpGetByHelloID(hostname string, addr string, helloID tls.ClientHelloID) (*http.Response, error) {
39	config := tls.Config{ServerName: hostname}
40	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
41	if err != nil {
42		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
43	}
44	uTlsConn := tls.UClient(dialConn, &config, helloID)
45	defer uTlsConn.Close()
46
47	err = uTlsConn.Handshake()
48	if err != nil {
49		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
50	}
51
52	return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
53}
54
55// this example generates a randomized fingeprint, then re-uses it in a follow-up connection
56func HttpGetConsistentRandomized(hostname string, addr string) (*http.Response, error) {
57	config := tls.Config{ServerName: hostname}
58	tcpConn, err := net.DialTimeout("tcp", addr, dialTimeout)
59	if err != nil {
60		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
61	}
62	uTlsConn := tls.UClient(tcpConn, &config, tls.HelloRandomized)
63	defer uTlsConn.Close()
64	err = uTlsConn.Handshake()
65	if err != nil {
66		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
67	}
68	uTlsConn.Close()
69
70	// At this point uTlsConn.ClientHelloID holds a seed that was used to generate
71	// randomized fingerprint. Now we can establish second connection with same fp
72	tcpConn2, err := net.DialTimeout("tcp", addr, dialTimeout)
73	if err != nil {
74		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
75	}
76	uTlsConn2 := tls.UClient(tcpConn2, &config, uTlsConn.ClientHelloID)
77	defer uTlsConn2.Close()
78	err = uTlsConn2.Handshake()
79	if err != nil {
80		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
81	}
82
83	return httpGetOverConn(uTlsConn2, uTlsConn2.HandshakeState.ServerHello.AlpnProtocol)
84}
85
86func HttpGetExplicitRandom(hostname string, addr string) (*http.Response, error) {
87	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
88	if err != nil {
89		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
90	}
91	uTlsConn := tls.UClient(dialConn, nil, tls.HelloGolang)
92	defer uTlsConn.Close()
93
94	uTlsConn.SetSNI(hostname) // have to set SNI, if config was nil
95	err = uTlsConn.BuildHandshakeState()
96	if err != nil {
97		// have to call BuildHandshakeState() first, when using default UClient, to avoid settings' overwriting
98		return nil, fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
99	}
100
101	cRandom := []byte{100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
102		110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
103		120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
104		130, 131}
105	uTlsConn.SetClientRandom(cRandom)
106	err = uTlsConn.Handshake()
107	if err != nil {
108		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
109	}
110	// These fields are accessible regardless of setting client hello explicitly
111	fmt.Printf("#> MasterSecret:\n%s", hex.Dump(uTlsConn.HandshakeState.MasterSecret))
112	fmt.Printf("#> ClientHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.Hello.Random))
113	fmt.Printf("#> ServerHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.ServerHello.Random))
114
115	return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
116}
117
118// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
119func HttpGetTicket(hostname string, addr string) (*http.Response, error) {
120	config := tls.Config{ServerName: hostname}
121	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
122	if err != nil {
123		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
124	}
125	uTlsConn := tls.UClient(dialConn, &config, tls.HelloGolang)
126	defer uTlsConn.Close()
127
128	err = uTlsConn.BuildHandshakeState()
129	if err != nil {
130		// have to call BuildHandshakeState() first, when using default UClient, to avoid settings' overwriting
131		return nil, fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
132	}
133
134	masterSecret := make([]byte, 48)
135	copy(masterSecret, []byte("masterSecret is NOT sent over the wire")) // you may use it for real security
136
137	// Create a session ticket that wasn't actually issued by the server.
138	sessionState := tls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),
139		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
140		masterSecret,
141		nil, nil)
142
143	err = uTlsConn.SetSessionState(sessionState)
144	if err != nil {
145		return nil, err
146	}
147
148	err = uTlsConn.Handshake()
149	if err != nil {
150		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
151	}
152	fmt.Println("#> This is how client hello with session ticket looked:")
153	fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
154
155	return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
156}
157
158// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
159func HttpGetTicketHelloID(hostname string, addr string, helloID tls.ClientHelloID) (*http.Response, error) {
160	config := tls.Config{ServerName: hostname}
161	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
162	if err != nil {
163		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
164	}
165	uTlsConn := tls.UClient(dialConn, &config, helloID)
166	defer uTlsConn.Close()
167
168	masterSecret := make([]byte, 48)
169	copy(masterSecret, []byte("masterSecret is NOT sent over the wire")) // you may use it for real security
170
171	// Create a session ticket that wasn't actually issued by the server.
172	sessionState := tls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),
173		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
174		masterSecret,
175		nil, nil)
176
177	uTlsConn.SetSessionState(sessionState)
178	err = uTlsConn.Handshake()
179	if err != nil {
180		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
181	}
182
183	fmt.Println("#> This is how client hello with session ticket looked:")
184	fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
185
186	return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
187}
188
189func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
190	config := tls.Config{ServerName: hostname}
191	dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
192	if err != nil {
193		return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
194	}
195	uTlsConn := tls.UClient(dialConn, &config, tls.HelloCustom)
196	defer uTlsConn.Close()
197
198	// do not use this particular spec in production
199	// make sure to generate a separate copy of ClientHelloSpec for every connection
200	spec := tls.ClientHelloSpec{
201		TLSVersMax: tls.VersionTLS13,
202		TLSVersMin: tls.VersionTLS10,
203		CipherSuites: []uint16{
204			tls.GREASE_PLACEHOLDER,
205			tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
206			tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
207			tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
208			tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
209			tls.TLS_AES_128_GCM_SHA256, // tls 1.3
210			tls.FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
211			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
212			tls.TLS_RSA_WITH_AES_256_CBC_SHA,
213		},
214		Extensions: []tls.TLSExtension{
215			&tls.SNIExtension{},
216			&tls.SupportedCurvesExtension{Curves: []tls.CurveID{tls.X25519, tls.CurveP256}},
217			&tls.SupportedPointsExtension{SupportedPoints: []byte{0}}, // uncompressed
218			&tls.SessionTicketExtension{},
219			&tls.ALPNExtension{AlpnProtocols: []string{"myFancyProtocol", "http/1.1"}},
220			&tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{
221				tls.ECDSAWithP256AndSHA256,
222				tls.ECDSAWithP384AndSHA384,
223				tls.ECDSAWithP521AndSHA512,
224				tls.PSSWithSHA256,
225				tls.PSSWithSHA384,
226				tls.PSSWithSHA512,
227				tls.PKCS1WithSHA256,
228				tls.PKCS1WithSHA384,
229				tls.PKCS1WithSHA512,
230				tls.ECDSAWithSHA1,
231				tls.PKCS1WithSHA1}},
232			&tls.KeyShareExtension{[]tls.KeyShare{
233				{Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}},
234				{Group: tls.X25519},
235			}},
236			&tls.PSKKeyExchangeModesExtension{[]uint8{1}}, // pskModeDHE
237			&tls.SupportedVersionsExtension{[]uint16{
238				tls.VersionTLS13,
239				tls.VersionTLS12,
240				tls.VersionTLS11,
241				tls.VersionTLS10}},
242		},
243		GetSessionID: nil,
244	}
245	err = uTlsConn.ApplyPreset(&spec)
246
247	if err != nil {
248		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
249	}
250
251	err = uTlsConn.Handshake()
252	if err != nil {
253		return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
254	}
255
256	return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
257}
258
259var roller *tls.Roller
260
261// this example creates a new roller for each function call,
262// however it is advised to reuse the Roller
263func HttpGetGoogleWithRoller() (*http.Response, error) {
264	var err error
265	if roller == nil {
266		roller, err = tls.NewRoller()
267		if err != nil {
268			return nil, err
269		}
270	}
271
272	// As of 2018-07-24 this tries to connect with Chrome, fails due to ChannelID extension
273	// being selected by Google, but not supported by utls, and seamlessly moves on to either
274	// Firefox or iOS fingerprints, which work.
275	c, err := roller.Dial("tcp4", requestHostname+":443", requestHostname)
276	if err != nil {
277		return nil, err
278	}
279
280	return httpGetOverConn(c, c.HandshakeState.ServerHello.AlpnProtocol)
281}
282
283func forgeConn() {
284	// this gets tls connection with google.com
285	// then replaces underlying connection of that tls connection with an in-memory pipe
286	// to a forged local in-memory "server-side" connection,
287	// that uses cryptographic parameters passed by a client
288	clientTcp, err := net.DialTimeout("tcp", "google.com:443", 10*time.Second)
289	if err != nil {
290		fmt.Printf("net.DialTimeout error: %+v", err)
291		return
292	}
293
294	clientUtls := tls.UClient(clientTcp, nil, tls.HelloGolang)
295	defer clientUtls.Close()
296	clientUtls.SetSNI("google.com") // have to set SNI, if config was nil
297	err = clientUtls.Handshake()
298	if err != nil {
299		fmt.Printf("clientUtls.Handshake() error: %+v", err)
300	}
301
302	serverConn, clientConn := net.Pipe()
303
304	clientUtls.SetUnderlyingConn(clientConn)
305
306	hs := clientUtls.HandshakeState
307	serverTls := tls.MakeConnWithCompleteHandshake(serverConn, hs.ServerHello.Vers, hs.ServerHello.CipherSuite,
308		hs.MasterSecret, hs.Hello.Random, hs.ServerHello.Random, false)
309
310	go func() {
311		clientUtls.Write([]byte("Hello, world!"))
312		resp := make([]byte, 13)
313		read, err := clientUtls.Read(resp)
314		if err != nil {
315			fmt.Printf("error reading client: %+v\n", err)
316		}
317		fmt.Printf("Client read %d bytes: %s\n", read, string(resp))
318		fmt.Println("Client closing...")
319		clientUtls.Close()
320		fmt.Println("client closed")
321	}()
322
323	buf := make([]byte, 13)
324	read, err := serverTls.Read(buf)
325	if err != nil {
326		fmt.Printf("error reading server: %+v\n", err)
327	}
328
329	fmt.Printf("Server read %d bytes: %s\n", read, string(buf))
330	serverTls.Write([]byte("Test response"))
331
332	// Have to do a final read (that will error)
333	// to consume client's closeNotify
334	// because net Pipes are weird
335	serverTls.Read(buf)
336	fmt.Println("Server closed")
337
338}
339
340func main() {
341	var response *http.Response
342	var err error
343
344	response, err = HttpGetDefault(requestHostname, requestAddr)
345	if err != nil {
346		fmt.Printf("#> HttpGetDefault failed: %+v\n", err)
347	} else {
348		fmt.Printf("#> HttpGetDefault response: %+s\n", dumpResponseNoBody(response))
349	}
350
351	response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloChrome_62)
352	if err != nil {
353		fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) failed: %+v\n", err)
354	} else {
355		fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) response: %+s\n", dumpResponseNoBody(response))
356	}
357
358	response, err = HttpGetConsistentRandomized(requestHostname, requestAddr)
359	if err != nil {
360		fmt.Printf("#> HttpGetConsistentRandomized() failed: %+v\n", err)
361	} else {
362		fmt.Printf("#> HttpGetConsistentRandomized() response: %+s\n", dumpResponseNoBody(response))
363	}
364
365	response, err = HttpGetExplicitRandom(requestHostname, requestAddr)
366	if err != nil {
367		fmt.Printf("#> HttpGetExplicitRandom failed: %+v\n", err)
368	} else {
369		fmt.Printf("#> HttpGetExplicitRandom response: %+s\n", dumpResponseNoBody(response))
370	}
371
372	response, err = HttpGetTicket(requestHostname, requestAddr)
373	if err != nil {
374		fmt.Printf("#> HttpGetTicket failed: %+v\n", err)
375	} else {
376		fmt.Printf("#> HttpGetTicket response: %+s\n", dumpResponseNoBody(response))
377	}
378
379	response, err = HttpGetTicketHelloID(requestHostname, requestAddr, tls.HelloFirefox_56)
380	if err != nil {
381		fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) failed: %+v\n", err)
382	} else {
383		fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) response: %+s\n", dumpResponseNoBody(response))
384	}
385
386	response, err = HttpGetCustom(requestHostname, requestAddr)
387	if err != nil {
388		fmt.Printf("#> HttpGetCustom() failed: %+v\n", err)
389	} else {
390		fmt.Printf("#> HttpGetCustom() response: %+s\n", dumpResponseNoBody(response))
391	}
392
393	for i := 0; i < 5; i++ {
394		response, err = HttpGetGoogleWithRoller()
395		if err != nil {
396			fmt.Printf("#> HttpGetGoogleWithRoller() #%v failed: %+v\n", i, err)
397		} else {
398			fmt.Printf("#> HttpGetGoogleWithRoller() #%v response: %+s\n",
399				i, dumpResponseNoBody(response))
400		}
401	}
402
403	forgeConn()
404
405	return
406}
407
408func httpGetOverConn(conn net.Conn, alpn string) (*http.Response, error) {
409	req := &http.Request{
410		Method: "GET",
411		URL:    &url.URL{Host: "www." + requestHostname + "/"},
412		Header: make(http.Header),
413		Host:   "www." + requestHostname,
414	}
415
416	switch alpn {
417	case "h2":
418		req.Proto = "HTTP/2.0"
419		req.ProtoMajor = 2
420		req.ProtoMinor = 0
421
422		tr := http2.Transport{}
423		cConn, err := tr.NewClientConn(conn)
424		if err != nil {
425			return nil, err
426		}
427		return cConn.RoundTrip(req)
428	case "http/1.1", "":
429		req.Proto = "HTTP/1.1"
430		req.ProtoMajor = 1
431		req.ProtoMinor = 1
432
433		err := req.Write(conn)
434		if err != nil {
435			return nil, err
436		}
437		return http.ReadResponse(bufio.NewReader(conn), req)
438	default:
439		return nil, fmt.Errorf("unsupported ALPN: %v", alpn)
440	}
441}
442
443func dumpResponseNoBody(response *http.Response) string {
444	resp, err := httputil.DumpResponse(response, false)
445	if err != nil {
446		return fmt.Sprintf("failed to dump response: %v", err)
447	}
448	return string(resp)
449}
450