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  }