Ty Scales

Listening for TLS Handshakes

Golang’s standard library has support for TLS servers out of the box. For testing purposes, you can generate a cert with openssl

openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out server.crt -keyout server.key

and use http.ListenAndServeTLS to spin up a sever

package main

import (
	"fmt"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /", func(w http.ResponseWriter, req *http.Request) {
		fmt.Fprintf(w, "welcome to my secure server\n")
	})

	fmt.Println("starting server")
	err := http.ListenAndServeTLS(":443", "server.crt", "server.key", mux)
	if err != nil {
		fmt.Printf("Server failed: %s\n", err)
	}
}

confirm that it works with curl.

curl -k https://localhost:443

You can also pass a Config from the crypto/tls package for fine grained control of what ciphers you want to use. Here is a minimal example where we tell the server to only support X25519 for key exchange.

	server := &http.Server{
		Addr:    ":443",
		Handler: mux,
		TLSConfig: &tls.Config{
			MinVersion:       tls.VersionTLS13,
			CurvePreferences: []tls.CurveID{tls.X25519},
			CipherSuites:     []uint16{tls.TLS_AES_128_GCM_SHA256},
		},
	}

Listening for ClientHello

The tls.Config struct allows you to define a function that runs every time a ClientHello is received by the server.

&http.Server{
    ...
    TlsConfig: &tls.Config{
        GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
            fmt.Printf("received client hello from: %s\n", hello.Conn.RemoteAddr())
			fmt.Printf("client supports the following cipherSuites: %x", hello.CipherSuites)
            return nil, nil
        }
    },
}

Or if you prefer, you can listen at the byte level with our own custom listener. Following the types from the TLS 1.3 RFC: look for a handshake record type, starting with 22 (hex: 0x16), and then checking that the message type is of type client_hello (0x01)

const (
    ContentTypeHandshake = 0x16
    HandshakeClientHello = 0x01

)

type handshakeConn struct {
    net.Conn
}

func (c *handshakeConn) Read(b []byte) (n int, err error) {
    n, err = c.Conn.Read(b)
    	n, err = c.Conn.Read(b)
	if n > 0 && b[0] == ContentTypeHandshake {
		handshake := b[5:]
		if handshake[0] == HandshakeClientHello {
			fmt.Println("received clientHello!")
		}

	}
	return n, err
}

type handshakeListener struct {
    net.Listener
}

func (l *handshakeListener) Accept() (net.Conn, error) {
    c, err := l.Listener.Accept()
    if err != nil {
        return nil, err
    }
    return &handshakeConn{c}, nil
}

Then manually define your listener and pass to server.ServeTLS


func main() {
    listener, err := net.Listen("tcp", ":443")
    if err != nil {
        panic(err)
    }

    handshakeListener := &handshakeListener{listener}

    server := &http.Server{
		Addr:    ":443",
		Handler: mux,
		TLSConfig: &tls.Config{
			MinVersion:       tls.VersionTLS13,
			CurvePreferences: []tls.CurveID{tls.X25519},
			CipherSuites:     []uint16{tls.TLS_AES_128_GCM_SHA256},
		},
	}

    server.ServeTLS(handshakeListener, "server.crt", "server.key")
}

#Golang