github.com/3andne/restls-client-go@v0.1.6/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 tls "github.com/3andne/restls-client-go" 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 308 // TODO: Redesign this part to use TLS 1.3 309 serverTls := tls.MakeConnWithCompleteHandshake(serverConn, hs.ServerHello.Vers, hs.ServerHello.CipherSuite, 310 hs.MasterSecret, hs.Hello.Random, hs.ServerHello.Random, false) 311 if serverTls == nil { 312 fmt.Printf("tls.MakeConnWithCompleteHandshake error, unsupported TLS protocol?") 313 return 314 } 315 316 go func() { 317 clientUtls.Write([]byte("Hello, world!")) 318 resp := make([]byte, 13) 319 read, err := clientUtls.Read(resp) 320 if err != nil { 321 fmt.Printf("error reading client: %+v\n", err) 322 } 323 fmt.Printf("Client read %d bytes: %s\n", read, string(resp)) 324 fmt.Println("Client closing...") 325 clientUtls.Close() 326 fmt.Println("client closed") 327 }() 328 329 buf := make([]byte, 13) 330 read, err := serverTls.Read(buf) 331 332 if err != nil { 333 fmt.Printf("error reading server: %+v\n", err) 334 } 335 336 fmt.Printf("Server read %d bytes: %s\n", read, string(buf)) 337 serverTls.Write([]byte("Test response")) 338 339 // Have to do a final read (that will error) 340 // to consume client's closeNotify 341 // because net Pipes are weird 342 serverTls.Read(buf) 343 fmt.Println("Server closed") 344 345 } 346 347 func main() { 348 var response *http.Response 349 var err error 350 351 response, err = HttpGetDefault(requestHostname, requestAddr) 352 if err != nil { 353 fmt.Printf("#> HttpGetDefault failed: %+v\n", err) 354 } else { 355 fmt.Printf("#> HttpGetDefault response: %+s\n", dumpResponseNoBody(response)) 356 } 357 358 response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloChrome_62) 359 if err != nil { 360 fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) failed: %+v\n", err) 361 } else { 362 fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) response: %+s\n", dumpResponseNoBody(response)) 363 } 364 365 response, err = HttpGetConsistentRandomized(requestHostname, requestAddr) 366 if err != nil { 367 fmt.Printf("#> HttpGetConsistentRandomized() failed: %+v\n", err) 368 } else { 369 fmt.Printf("#> HttpGetConsistentRandomized() response: %+s\n", dumpResponseNoBody(response)) 370 } 371 372 response, err = HttpGetExplicitRandom(requestHostname, requestAddr) 373 if err != nil { 374 fmt.Printf("#> HttpGetExplicitRandom failed: %+v\n", err) 375 } else { 376 fmt.Printf("#> HttpGetExplicitRandom response: %+s\n", dumpResponseNoBody(response)) 377 } 378 379 response, err = HttpGetTicket(requestHostname, requestAddr) 380 if err != nil { 381 fmt.Printf("#> HttpGetTicket failed: %+v\n", err) 382 } else { 383 fmt.Printf("#> HttpGetTicket response: %+s\n", dumpResponseNoBody(response)) 384 } 385 386 response, err = HttpGetTicketHelloID(requestHostname, requestAddr, tls.HelloFirefox_56) 387 if err != nil { 388 fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) failed: %+v\n", err) 389 } else { 390 fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) response: %+s\n", dumpResponseNoBody(response)) 391 } 392 393 response, err = HttpGetCustom(requestHostname, requestAddr) 394 if err != nil { 395 fmt.Printf("#> HttpGetCustom() failed: %+v\n", err) 396 } else { 397 fmt.Printf("#> HttpGetCustom() response: %+s\n", dumpResponseNoBody(response)) 398 } 399 400 for i := 0; i < 5; i++ { 401 response, err = HttpGetGoogleWithRoller() 402 if err != nil { 403 fmt.Printf("#> HttpGetGoogleWithRoller() #%v failed: %+v\n", i, err) 404 } else { 405 fmt.Printf("#> HttpGetGoogleWithRoller() #%v response: %+s\n", 406 i, dumpResponseNoBody(response)) 407 } 408 } 409 410 forgeConn() 411 412 return 413 } 414 415 func httpGetOverConn(conn net.Conn, alpn string) (*http.Response, error) { 416 req := &http.Request{ 417 Method: "GET", 418 URL: &url.URL{Host: "www." + requestHostname + "/"}, 419 Header: make(http.Header), 420 Host: "www." + requestHostname, 421 } 422 423 switch alpn { 424 case "h2": 425 req.Proto = "HTTP/2.0" 426 req.ProtoMajor = 2 427 req.ProtoMinor = 0 428 429 tr := http2.Transport{} 430 cConn, err := tr.NewClientConn(conn) 431 if err != nil { 432 return nil, err 433 } 434 return cConn.RoundTrip(req) 435 case "http/1.1", "": 436 req.Proto = "HTTP/1.1" 437 req.ProtoMajor = 1 438 req.ProtoMinor = 1 439 440 err := req.Write(conn) 441 if err != nil { 442 return nil, err 443 } 444 return http.ReadResponse(bufio.NewReader(conn), req) 445 default: 446 return nil, fmt.Errorf("unsupported ALPN: %v", alpn) 447 } 448 } 449 450 func dumpResponseNoBody(response *http.Response) string { 451 resp, err := httputil.DumpResponse(response, false) 452 if err != nil { 453 return fmt.Sprintf("failed to dump response: %v", err) 454 } 455 return string(resp) 456 }