Expose tls.ClientHelloInfo in http.Handler

I’m attempting to implement a paper’s technique which requires matching a HTTP request to its underlying TLS connection, specifically the ClientHelloInfo used in the handshake.

I am wondering if anyone has a good solution for getting the underlying TLS connection (not just the connection state) from an http.Request, or if maybe there’s some way (a custom listener?) to expose the ClientHelloInfo to HTTP handlers…

Hey @matt,

I could be completely misinterpreting what you mean, but are you referring to something like this?

package main

import (
	"crypto/tls"
	"fmt"
	"log"
	"net/http"
)

type tlsHandler struct {
	chi *tls.ClientHelloInfo
}

func (t *tlsHandler) GetClientInfo(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
	t.chi = info
	return nil, nil
}

func (t *tlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, t.chi.CipherSuites)
}

func main() {
	handler := &tlsHandler{}

	s := &http.Server{
		Addr: ":9000",
		TLSConfig: &tls.Config{
			GetCertificate: handler.GetClientInfo,
		},
		Handler: handler,
	}

	log.Fatalln(s.ListenAndServeTLS("cert.pem", "key.pem"))
}
3 Likes

Ah! That’s so elegant! How did I miss this?? I think my Go foo must be weak since I’ve been doing too much JS and Python lately. :frowning:

… maybe it’s because I have already “used” the GetCertificate callback in my code and I just glossed over the fact that it gives access to the ClientHelloInfo and its type can also be used as a Handler. See, that’s nice. That’s real nice.

I’ll have to take care with thread safety but, yeah… Thank you! That should do the trick.

2 Likes

Haha to be honest, I’ve never worked with anything related to what you were asking so I just used my best skill of messing around with stuff to work it out in conjunction with my second best skill of googling what the stuff was that you were asking about :stuck_out_tongue:

It’s probably because I haven’t worked with it before that I came up with a solution that would be simple for you because I wouldn’t overlook things by accident lol!

1 Like

Cheers. I see you’re in Australia but if you ever come to the land (mountains) above in Utah, I’ll buy ya lunch.

1 Like

Haha no worries :stuck_out_tongue:

Hi @radovskyb,

I see the elegance of the solution (hats off!), but I don’t get it completely. :blush:

The code creates a tls.Config with nothing but the GetCertifcate func set and all other fields left at their zero value. Is this really sufficient for handling TLS or did you just leave out all the other stuff for brevity?

2 Likes

Hey @christophberger,

Personally I left out the other stuff because I haven’t worked with that stuff before, but realized that the function could be used just for setting the value.

With that being said, I knew that someone like @matt would be able to work out the middle parts even if I had no idea what needs to normally be there! :stuck_out_tongue:

Sometimes I’m not bad at coming up with ideas, but I don’t always understand the details surrounding them all lol!

3 Likes

Makes sense! :grin:

1 Like

This is indeed the right way to go about it (in general), however I’m still stuck at trying to associate an http.Request with its ClientHelloInfo. The current method as-is will overwrite the ClientHelloInfo at each handshake… Go 1.8 exposes the underlying net.Conn in the ClientHelloInfo so I feel like we must be close! I just don’t know how to map that to the proper request in the handler… is there a way to access the net.Conn from an HTTP request without hijacking it? If so, I think this is a solved problem then, because I can create a map of net.Conn to their ClientHelloInfo.

Edit: Stack Overflow has this possible way, but it uses reflection: http://stackoverflow.com/a/29637419/1048862:

ptrVal := reflect.ValueOf(w)
val := reflect.Indirect(ptrVal)

// w is a "http.response" struct from which we get the 'conn' field
valconn := val.FieldByName("conn")
val1 := reflect.Indirect(valconn)

// which is a http.conn from which we get the 'rwc' field
ptrRwc := val1.FieldByName("rwc").Elem()
rwc := reflect.Indirect(ptrRwc)

but… of course, I don’t want to do this if I don’t have to…

Nevermind! Kale Blankenship gave me a tip in the Gopher slack: we can use the RemoteAddr of the net.Conn as a map key. This will match the RemoteAddr field of the request.

Phew :sweat_smile: Can’t wait to try this out. Thanks everyone!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.