Skip to content

fix(uconn): drop the key share private keys after the handshake - #431

Open
fabifont wants to merge 1 commit into
refraction-networking:masterfrom
fabifont:fix/drop-key-share-keys-after-handshake
Open

fabifont wants to merge 1 commit into
refraction-networking:masterfrom
fabifont:fix/drop-key-share-keys-after-handshake

Conversation

@fabifont

Copy link
Copy Markdown

After the handshake, clientHandshake copies the whole private handshake state into UConn.HandshakeState so callers can read what was negotiated. That copy includes State13.KeyShareKeys, the private keys behind the ClientHello's key shares, which nothing reads once the shared secret is derived: no code path consults them after the handshake, HelloRetryRequest is handled before the copy, and resumption and key updates use the Conn's own secrets. With X25519MLKEM768 the ML-KEM decapsulation key is 7.8 KiB (the FIPS implementation keeps the expanded matrix), so every idle connection holds about 8 KiB of spent private key material for its whole lifetime.

Measured on this branch's base, live heap per idle client connection after a TLS 1.3 handshake, servers closed and collected (300 connections, runtime.MemStats):

Client Before After
crypto/tls Client 7.0 KiB 7.0 KiB
UClient with HelloGolang 20.8 KiB 12.6 KiB
UClient with HelloChrome_155 27.4 KiB 19.0 KiB

This change clears State13.KeyShareKeys right after the public state is refreshed, and documents on the field that it is set before the handshake and nil after it. The rest of the handshake state stays readable, so HandshakeState.Hello, ServerHello, MasterSecret and State13.UsingPSK, which the examples and downstream code read after the handshake, are unchanged. The remaining difference to crypto/tls is the public copies of both hellos and the applied extensions, which is the state uTLS documents as readable.

The test builds the handshake state with both hello builders, checks the keys are present before the handshake, and checks they are gone after it while the rest of the state remains.

Verified with Go 1.27.3: go test ./..., go test -race on the handshake tests, go vet, gofmt -s, go fix -diff, staticcheck and golangci-lint report nothing.

clientHandshake copies the private handshake state into
UConn.HandshakeState once the handshake is done, so callers can read
what was negotiated. The copy included State13.KeyShareKeys, the
private keys behind the ClientHello's key shares, which nothing reads
after the shared secret is derived. With X25519MLKEM768 the ML-KEM
decapsulation key is 7.8 KiB, so every idle connection kept about 8 KiB
of spent private key material for its lifetime: 27.4 KiB of live heap
per idle Chrome-spec connection against 7.0 KiB for a crypto/tls
client.

Clear the keys right after the public state is refreshed and say so on
the field. The rest of the handshake state stays readable. Per idle
connection this saves 8.4 KiB with the Go hello and 8.4 KiB with the
Chrome 155 spec.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant