mirror of
https://github.com/refraction-networking/utls.git
synced 2025-04-03 20:17:36 +03:00
I tested all fingerprints and confirmed that Chrome and Firefox are working as intended. Android fingerprints were grossly unpopular, which could a result of incorrect merge, but either way we'll remove them for now.
224 lines
7.5 KiB
Go
224 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
tls "github.com/refraction-networking/utls"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
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`)
|
|
)
|
|
|
|
func HttpGetDefault(hostname string, addr string) (string, error) {
|
|
config := tls.Config{ServerName: hostname}
|
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
|
if err != nil {
|
|
return "", 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
|
|
}
|
|
|
|
func HttpGetByHelloID(hostname string, addr string, helloID tls.ClientHelloID) (string, error) {
|
|
config := tls.Config{ServerName: hostname}
|
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
|
if err != nil {
|
|
return "", 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)
|
|
}
|
|
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
|
|
}
|
|
|
|
func HttpGetExplicitRandom(hostname string, addr string) (string, error) {
|
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
|
if err != nil {
|
|
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
|
|
}
|
|
uTlsConn := tls.UClient(dialConn, nil, tls.HelloGolang)
|
|
defer uTlsConn.Close()
|
|
|
|
uTlsConn.SetSNI(hostname) // have to set SNI, if config was nil
|
|
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)
|
|
}
|
|
|
|
cRandom := []byte{100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
|
|
110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
|
120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
|
|
130, 131}
|
|
uTlsConn.SetClientRandom(cRandom)
|
|
err = uTlsConn.Handshake()
|
|
if err != nil {
|
|
return "", 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
|
|
}
|
|
|
|
// 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) {
|
|
config := tls.Config{ServerName: hostname}
|
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
|
if err != nil {
|
|
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
|
|
}
|
|
uTlsConn := tls.UClient(dialConn, &config, tls.HelloGolang)
|
|
defer uTlsConn.Close()
|
|
|
|
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)
|
|
}
|
|
|
|
masterSecret := make([]byte, 48)
|
|
copy(masterSecret, []byte("masterSecret is NOT sent over the wire")) // you may use it for real security
|
|
|
|
// Create a session ticket that wasn't actually issued by the server.
|
|
sessionState := tls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
masterSecret,
|
|
nil, nil)
|
|
|
|
uTlsConn.SetSessionState(sessionState)
|
|
|
|
err = uTlsConn.Handshake()
|
|
if err != nil {
|
|
return "", 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
|
|
}
|
|
|
|
// 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) {
|
|
config := tls.Config{ServerName: hostname}
|
|
dialConn, err := net.DialTimeout("tcp", addr, dialTimeout)
|
|
if err != nil {
|
|
return "", fmt.Errorf("net.DialTimeout error: %+v", err)
|
|
}
|
|
uTlsConn := tls.UClient(dialConn, &config, helloID)
|
|
defer uTlsConn.Close()
|
|
|
|
masterSecret := make([]byte, 48)
|
|
copy(masterSecret, []byte("masterSecret is NOT sent over the wire")) // you may use it for real security
|
|
|
|
// Create a session ticket that wasn't actually issued by the server.
|
|
sessionState := tls.MakeClientSessionState(sessionTicket, uint16(tls.VersionTLS12),
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
masterSecret,
|
|
nil, nil)
|
|
|
|
uTlsConn.SetSessionState(sessionState)
|
|
err = uTlsConn.Handshake()
|
|
if err != nil {
|
|
return "", 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
|
|
}
|
|
|
|
func main() {
|
|
var response string
|
|
var err error
|
|
requestHostname := "www.google.com"
|
|
requestAddr := "172.217.11.46: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))
|
|
}
|
|
|
|
response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloAndroid_5_1_Browser)
|
|
if err != nil {
|
|
fmt.Printf("#> HttpGetByHelloID(Android_5_1) failed: %+v\n", err)
|
|
} else {
|
|
fmt.Printf("#> HttpGetByHelloID(Android_5_1) response: %+s\n", getFirstLine(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))
|
|
}
|
|
|
|
response, err = HttpGetExplicitRandom(requestHostname, requestAddr)
|
|
if err != nil {
|
|
fmt.Printf("#> HttpGetExplicitRandom failed: %+v\n", err)
|
|
} else {
|
|
fmt.Printf("#> HttpGetExplicitRandom response: %+s\n", getFirstLine(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))
|
|
}
|
|
|
|
response, err = HttpGetTicketHelloID(requestHostname, requestAddr, tls.HelloAndroid_5_1_Browser)
|
|
if err != nil {
|
|
fmt.Printf("#> HttpGetTicketHelloID(Android_5_1) failed: %+v\n", err)
|
|
} else {
|
|
fmt.Printf("#> HttpGetTicketHelloID(Android_5_1) response: %+s\n", getFirstLine(response))
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func getFirstLine(s string) string {
|
|
ss := strings.Split(s, "\r\n")
|
|
if len(ss) == 0 {
|
|
return ""
|
|
} else {
|
|
return ss[0]
|
|
}
|
|
}
|