github.com/Carcraftz/utls@v0.0.0-20220413235215-6b7c52fd78b6/examples/examples.go (about) 1 package main 2 3 import ( 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 17 var ( 18 dialTimeout = time.Duration(15) * time.Second 19 sessionTicket = []uint8(`Here goes phony session ticket: phony enough to get into ASCII range 20 Ticket could be of any length, but for camouflage purposes it's better to use uniformly random contents 21 and common length. See https://tlsfingerprint.io/session-tickets`) 22 ) 23 24 var requestHostname = "facebook.com" // speaks http2 and TLS 1.3 25 var requestAddr = "31.13.72.36:443" 26 27 func 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 38 func 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 56 func 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 86 func 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 119 func 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 159 func 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 189 func 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 259 var roller *tls.Roller 260 261 // this example creates a new roller for each function call, 262 // however it is advised to reuse the Roller 263 func 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 283 func 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 340 func 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 408 func 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 443 func 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 }