Skip to content

Signature verification error visiting URL #345

@BRUHItsABunny

Description

@BRUHItsABunny

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 error

I 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):

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions