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  }