Skip to content

Remove custom fields in tls.Config #146

@Lekensteyn

Description

@Lekensteyn

quic-go uses qtls which requires a match between its data structures and the standard library to match. This includes tls.Config, tls.ConnectionState, tls.ClientHelloInfos, and some others. See https://github.com/quic-go/qtls-go1-20/blob/master/unsafe.go

It would be nice if we can reduce custom fields unless there is no other way. This can be done through contexts.

In commit 01665a5 ("crypto/tls: add CFControl parameter to Config"), we add a tls.Config#CFControl field to propagate information from the TLS handshake (tls.Config#GetConfigForClient) to a HTTP request handler (http.Request#TLS.CFControl). To replace this with contexts:

  1. Set http.Server#ConnContext to add an empty structure to the context of the connection.
  2. In tls.Config#GetConfigForClient, extract the previous structure pointer from the context through tls.ClientHelloInfos#Context and initialize it. Note that while modifications to the context itself are not propagated, any changes to the context value (a pointer to a structure) will be preserved. This is a crucial detail.
  3. In a HTTP handler, extract the structure pointer again from the context through http.Request#Context.

To demonstrate the above conversion:

type contextKey struct{ name string }

var controlContextKey = &contextKey{"connection-metadata"}

type ConnState struct {
	ExampleValue []byte
}

func main() {
	srv := http.Server{
		ConnContext: func(ctx context.Context, c net.Conn) context.Context {
			// 1. allocate context value in the connection context
			return context.WithValue(ctx, controlContextKey, &ConnState{})
		},
	}
	cert, err := tls.LoadX509KeyPair("example-cert.pem", "example-key.pem")
	if err != nil {
		log.Fatal(err)
	}
	tlsConfig := &tls.Config{
		GetConfigForClient: func(chi *tls.ClientHelloInfo) (*tls.Config, error) {
			// 2. initialize state in context value
			cs := chi.Context().Value(controlContextKey).(*ConnState)
			cs.ExampleValue = []byte("hello world\n")
			return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
		},
	}

	http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
		// 3. extract value from context
		cs := req.Context().Value(controlContextKey).(*ConnState)
		rw.Write(cs.ExampleValue)
	})

	// Setup HTTPS server
	netListener, err := net.Listen("tcp", "localhost:4433")
	if err != nil {
		log.Fatal(err)
	}
	ln := tls.NewListener(netListener, tlsConfig)
	if err := srv.Serve(ln); err != http.ErrServerClosed {
		log.Fatal(err)
	}
}

I'll work on changes to the cf branch to remove the CFControl field and investigate how other fields can similarly be removed.

Metadata

Metadata

Assignees

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