Update examples

This commit is contained in:
Sergey Frolov 2019-01-04 19:43:27 -07:00 committed by sergeyfrolov
parent fd72b83e04
commit a89e7e6da4

View file

@ -1,15 +1,16 @@
package main
import (
"bufio"
"encoding/hex"
"fmt"
"net"
"net/http"
"net/http/httputil"
"strings"
"net/url"
"time"
tls "github.com/refraction-networking/utls"
"github.com/refraction-networking/utls"
"golang.org/x/net/http2"
)
@ -17,51 +18,44 @@ var (
dialTimeout = time.Duration(15) * time.Second
sessionTicket = []uint8(`Here goes phony session ticket: phony enough to get into ASCII range
Ticket could be of any length, but for camouflage purposes it's better to use uniformly random contents
and standard length such as 228`)
and common length. See https://tlsfingerprint.io/session-tickets`)
)
func HttpGetDefault(hostname string, addr string) (string, error) {
var requestHostname = "facebook.com" // speaks http2 and TLS 1.3
var requestAddr = "31.13.72.36:443"
func HttpGetDefault(hostname string, addr string) (*http.Response, error) {
config := tls.Config{ServerName: hostname}
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
tlsConn := tls.Client(dialConn, &config)
defer tlsConn.Close()
err = tlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("tlsConn.Handshake() error: %+v", err)
}
tlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
tlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(tlsConn, tlsConn.ConnectionState().NegotiatedProtocol)
}
func HttpGetByHelloID(hostname string, addr string, helloID tls.ClientHelloID) (string, error) {
func HttpGetByHelloID(hostname string, addr string, helloID tls.ClientHelloID) (*http.Response, error) {
config := tls.Config{ServerName: hostname}
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, &config, helloID)
defer uTlsConn.Close()
err = uTlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
uTlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
uTlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
}
func HttpGetExplicitRandom(hostname string, addr string) (string, error) {
func HttpGetExplicitRandom(hostname string, addr string) (*http.Response, error) {
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, nil, tls.HelloGolang)
defer uTlsConn.Close()
@ -70,7 +64,7 @@ func HttpGetExplicitRandom(hostname string, addr string) (string, error) {
err = uTlsConn.BuildHandshakeState()
if err != nil {
// have to call BuildHandshakeState() first, when using default UClient, to avoid settings' overwriting
return "", fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
}
cRandom := []byte{100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
@ -80,25 +74,22 @@ func HttpGetExplicitRandom(hostname string, addr string) (string, error) {
uTlsConn.SetClientRandom(cRandom)
err = uTlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
// These fields are accessible regardless of setting client hello explicitly
fmt.Printf("#> MasterSecret:\n%s", hex.Dump(uTlsConn.HandshakeState.MasterSecret))
fmt.Printf("#> ClientHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.Hello.Random))
fmt.Printf("#> ServerHello Random:\n%s", hex.Dump(uTlsConn.HandshakeState.ServerHello.Random))
uTlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
uTlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
}
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
func HttpGetTicket(hostname string, addr string) (string, error) {
func HttpGetTicket(hostname string, addr string) (*http.Response, error) {
config := tls.Config{ServerName: hostname}
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, &config, tls.HelloGolang)
defer uTlsConn.Close()
@ -106,7 +97,7 @@ func HttpGetTicket(hostname string, addr string) (string, error) {
err = uTlsConn.BuildHandshakeState()
if err != nil {
// have to call BuildHandshakeState() first, when using default UClient, to avoid settings' overwriting
return "", fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.BuildHandshakeState() error: %+v", err)
}
masterSecret := make([]byte, 48)
@ -118,27 +109,27 @@ func HttpGetTicket(hostname string, addr string) (string, error) {
masterSecret,
nil, nil)
uTlsConn.SetSessionState(sessionState)
err = uTlsConn.SetSessionState(sessionState)
if err != nil {
return nil, err
}
err = uTlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
fmt.Println("#> This is how client hello with session ticket looked:")
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
uTlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
uTlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
}
// Note that the server will reject the fake ticket(unless you set up your server to accept them) and do full handshake
func HttpGetTicketHelloID(hostname string, addr string, helloID tls.ClientHelloID) (string, error) {
func HttpGetTicketHelloID(hostname string, addr string, helloID tls.ClientHelloID) (*http.Response, error) {
config := tls.Config{ServerName: hostname}
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, &config, helloID)
defer uTlsConn.Close()
@ -155,34 +146,36 @@ func HttpGetTicketHelloID(hostname string, addr string, helloID tls.ClientHelloI
uTlsConn.SetSessionState(sessionState)
err = uTlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
fmt.Println("#> This is how client hello with session ticket looked:")
fmt.Print(hex.Dump(uTlsConn.HandshakeState.Hello.Raw))
uTlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
uTlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
}
func HttpGetCustom(hostname string, addr string) (string, error) {
func HttpGetCustom(hostname string, addr string) (*http.Response, error) {
config := tls.Config{ServerName: hostname}
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
return nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, &config, tls.HelloCustom)
defer uTlsConn.Close()
// do not use this particular spec in production
// make sure to generate a separate copy of ClientHelloSpec for every connection
spec := tls.ClientHelloSpec{
TLSVersMax: tls.VersionTLS13,
TLSVersMin: tls.VersionTLS10,
CipherSuites: []uint16{
tls.GREASE_PLACEHOLDER,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_AES_128_GCM_SHA256, // tls 1.3
tls.FAKE_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
@ -190,9 +183,9 @@ func HttpGetCustom(hostname string, addr string) (string, error) {
Extensions: []tls.TLSExtension{
&tls.SNIExtension{},
&tls.SupportedCurvesExtension{Curves: []tls.CurveID{tls.X25519, tls.CurveP256}},
&tls.SupportedPointsExtension{SupportedPoints: []byte{0}},
&tls.SupportedPointsExtension{SupportedPoints: []byte{0}}, // uncompressed
&tls.SessionTicketExtension{},
&tls.ALPNExtension{AlpnProtocols: []string{"myFancyProtocol", "h2", "http/1.1"}},
&tls.ALPNExtension{AlpnProtocols: []string{"myFancyProtocol", "http/1.1"}},
&tls.SignatureAlgorithmsExtension{SupportedSignatureAlgorithms: []tls.SignatureScheme{
tls.ECDSAWithP256AndSHA256,
tls.ECDSAWithP384AndSHA384,
@ -204,146 +197,163 @@ func HttpGetCustom(hostname string, addr string) (string, error) {
tls.PKCS1WithSHA384,
tls.PKCS1WithSHA512,
tls.ECDSAWithSHA1,
tls.PKCS1WithSHA1},
},
tls.PKCS1WithSHA1}},
&tls.KeyShareExtension{[]tls.KeyShare{
{Group: tls.CurveID(tls.GREASE_PLACEHOLDER), Data: []byte{0}},
{Group: tls.X25519},
}},
&tls.PSKKeyExchangeModesExtension{[]uint8{1}}, // pskModeDHE
&tls.SupportedVersionsExtension{[]uint16{
tls.VersionTLS13,
tls.VersionTLS12,
tls.VersionTLS11,
tls.VersionTLS10}},
},
GetSessionID: nil,
}
err = uTlsConn.ApplyPreset(&spec)
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
err = uTlsConn.Handshake()
if err != nil {
return "", fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
uTlsConn.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
uTlsConn.Read(buf)
return string(buf), nil
return httpGetOverConn(uTlsConn, uTlsConn.HandshakeState.ServerHello.AlpnProtocol)
}
var roller *tls.Roller
func HttpGetGoogleWithRoller() (string, error) {
// this example creates a new roller for each function call,
// however it is advised to reuse the Roller
func HttpGetGoogleWithRoller() (*http.Response, error) {
var err error
hostname := "www.google.com"
if roller == nil {
roller, err = tls.NewRoller()
if err != nil {
return "", err
return nil, err
}
}
// As of 2018-07-24 this tries to connect with Chrome, fails due to ChannelID extension
// being selected by Google, but not supported by utls, and seamlessly moves on to either
// Firefox or iOS fingerprints, which work.
c, err := roller.Dial("tcp4", hostname+":443", hostname)
c, err := roller.Dial("tcp4", requestHostname+":443", requestHostname)
if err != nil {
return "", err
}
if c.ConnectionState().NegotiatedProtocol == "h2" {
t := http2.Transport{}
h2c, err := t.NewClientConn(c)
if err != nil {
return "", err
}
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
return "", err
}
resp, err := h2c.RoundTrip(req)
if err != nil {
return "", err
}
respbytes, err := httputil.DumpResponse(resp, true)
if err != nil {
return "", err
}
return string(respbytes), nil
} else {
c.Write([]byte("GET / HTTP/1.1\r\nHost: " + hostname + "\r\n\r\n"))
buf := make([]byte, 14096)
c.Read(buf)
return string(buf), nil
return nil, err
}
return httpGetOverConn(c, c.HandshakeState.ServerHello.AlpnProtocol)
}
func main() {
var response string
var response *http.Response
var err error
requestHostname := "tlsfingerprint.io"
requestAddr := "54.145.209.94:443"
response, err = HttpGetDefault(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetDefault failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetDefault response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetDefault response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloChrome_62)
if err != nil {
fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetByHelloID(HelloChrome_62) response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloRandomizedNoALPN)
if err != nil {
fmt.Printf("#> HttpGetByHelloID(Randomized) failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetByHelloID(Randomized) response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetByHelloID(Randomized) response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetExplicitRandom(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetExplicitRandom failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetExplicitRandom response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetExplicitRandom response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetTicket(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetTicket failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetTicket response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetTicket response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetTicketHelloID(requestHostname, requestAddr, tls.HelloFirefox_56)
if err != nil {
fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetTicketHelloID(HelloFirefox_56) response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetCustom(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetCustom() failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetCustom() response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetCustom() response: %+s\n", dumpResponseNoBody(response))
}
for i := 0; i < 5; i++ {
response, err = HttpGetGoogleWithRoller()
if err != nil {
fmt.Printf("#> HttpGetGoogleWithRoller() failed: %+v\n", err)
fmt.Printf("#> HttpGetGoogleWithRoller() #%v failed: %+v\n", i, err)
} else {
fmt.Printf("#> HttpGetGoogleWithRoller() response: %+s\n", getFirstLine(response))
fmt.Printf("#> HttpGetGoogleWithRoller() #%v response: %+s\n",
i, dumpResponseNoBody(response))
}
}
return
}
func getFirstLine(s string) string {
ss := strings.Split(s, "\r\n")
if len(ss) == 0 {
return ""
} else {
return ss[0]
func httpGetOverConn(conn net.Conn, alpn string) (*http.Response, error) {
req := &http.Request{
Method: "GET",
URL: &url.URL{Host: "www." + requestHostname + "/"},
Header: make(http.Header),
Host: "www." + requestHostname,
}
switch alpn {
case "h2":
req.Proto = "HTTP/2.0"
req.ProtoMajor = 2
req.ProtoMinor = 0
tr := http2.Transport{}
cConn, err := tr.NewClientConn(conn)
if err != nil {
return nil, err
}
return cConn.RoundTrip(req)
case "http/1.1", "":
req.Proto = "HTTP/1.1"
req.ProtoMajor = 1
req.ProtoMinor = 1
err := req.Write(conn)
if err != nil {
return nil, err
}
return http.ReadResponse(bufio.NewReader(conn), req)
default:
return nil, fmt.Errorf("unsupported ALPN: %v", alpn)
}
}
func dumpResponseNoBody(response *http.Response) string {
resp, err := httputil.DumpResponse(response, false)
if err != nil {
return fmt.Sprintf("failed to dump response: %v", err)
}
return string(resp)
}