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