package main
import (
"bufio"
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"time"
tls "github.com/refraction-networking/utls"
"golang.org/x/net/http2"
)
var (
dialTimeout = time.Duration(15) * time.Second
)
// var requestHostname = "facebook.com" // speaks http2 and TLS 1.3
// var requestAddr = "31.13.72.36:443"
var requestHostname = "cards-na.klarna.com"
var requestAddr = "cards-na.klarna.com: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 nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
tlsConn := tls.Client(dialConn, &config)
defer tlsConn.Close()
return httpGetOverConn(tlsConn, tlsConn.ConnectionState().NegotiatedProtocol)
}
func HttpGetStdLib(hostname string, addr string) (*http.Response, error) {
return http.Get("https://" + hostname + "/")
}
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 nil, fmt.Errorf("net.DialTimeout error: %+v", err)
}
uTlsConn := tls.UClient(dialConn, &config, helloID)
defer uTlsConn.Close()
err = uTlsConn.Handshake()
if err != nil {
return nil, fmt.Errorf("uTlsConn.Handshake() error: %+v", err)
}
return httpGetOverConn(uTlsConn, uTlsConn.ConnectionState().NegotiatedProtocol)
}
func forgeConn() {
// this gets tls connection with google.com
// then replaces underlying connection of that tls connection with an in-memory pipe
// to a forged local in-memory "server-side" connection,
// that uses cryptographic parameters passed by a client
clientTcp, err := net.DialTimeout("tcp", "google.com:443", 10*time.Second)
if err != nil {
fmt.Printf("net.DialTimeout error: %+v", err)
return
}
clientUtls := tls.UClient(clientTcp, nil, tls.HelloGolang)
defer clientUtls.Close()
clientUtls.SetSNI("google.com") // have to set SNI, if config was nil
err = clientUtls.Handshake()
if err != nil {
fmt.Printf("clientUtls.Handshake() error: %+v", err)
}
serverConn, clientConn := net.Pipe()
clientUtls.SetUnderlyingConn(clientConn)
hs := clientUtls.HandshakeState
// TODO: Redesign this part to use TLS 1.3
serverTls := tls.MakeConnWithCompleteHandshake(serverConn, hs.ServerHello.Vers, hs.ServerHello.CipherSuite,
hs.MasterSecret, hs.Hello.Random, hs.ServerHello.Random, false)
if serverTls == nil {
fmt.Printf("tls.MakeConnWithCompleteHandshake error, unsupported TLS protocol?")
return
}
go func() {
clientUtls.Write([]byte("Hello, world!"))
resp := make([]byte, 13)
read, err := clientUtls.Read(resp)
if err != nil {
fmt.Printf("error reading client: %+v\n", err)
}
fmt.Printf("Client read %d bytes: %s\n", read, string(resp))
fmt.Println("Client closing...")
clientUtls.Close()
fmt.Println("client closed")
}()
buf := make([]byte, 13)
read, err := serverTls.Read(buf)
if err != nil {
fmt.Printf("error reading server: %+v\n", err)
}
fmt.Printf("Server read %d bytes: %s\n", read, string(buf))
serverTls.Write([]byte("Test response"))
// Have to do a final read (that will error)
// to consume client's closeNotify
// because net Pipes are weird
serverTls.Read(buf)
fmt.Println("Server closed")
}
func main() {
var response *http.Response
var err error
response, err = HttpGetStdLib(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetStdLib failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetStdLib response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetDefault(requestHostname, requestAddr)
if err != nil {
fmt.Printf("#> HttpGetDefault failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetDefault response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloChrome_120_PQ)
if err != nil {
fmt.Printf("#> HttpGetByHelloID(HelloChrome_120_PQ) failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetByHelloID(HelloChrome_120_PQ) response: %+s\n", dumpResponseNoBody(response))
}
response, err = HttpGetByHelloID(requestHostname, requestAddr, tls.HelloAndroid_11_OkHttp)
if err != nil {
fmt.Printf("#> HttpGetByHelloID(HelloAndroid_11_OkHttp) failed: %+v\n", err)
} else {
fmt.Printf("#> HttpGetByHelloID(HelloAndroid_11_OkHttp) response: %+s\n", dumpResponseNoBody(response))
}
forgeConn()
return
}
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)
}
Oddly enough, uTLS starts working again when using the Android parrot.
When I visit this URL https://cards-na.klarna.com I get the following error:
tls: invalid signature by the server certificate: crypto/rsa: verification errorI tracked it to this and this but that leaves me pretty unsure on why this occurs given the TLS connection is fine in my browser (Brave) as well as std lib Go.
Replicate (modified from the old example in the repo):
Oddly enough, uTLS starts working again when using the Android parrot.