Repository navigation
Pure TLS: offer X25519MLKEM768 by default, accept it on the server (#2483) - #3137
Merged
Merged
Conversation
…server Update the pure-Aether TLS 1.3 client to offer X25519MLKEM768 by default alongside classical key shares (X25519 and SECP256R1), and update the pure TLS server to accept X25519MLKEM768 key shares with FIPS 203 §7.2 encapsulation key checks. Convert connect_pq into a thin wrapper. Co-authored-by: paul-hammant <82182+paul-hammant@users.noreply.github.com>
…to end Review fixes on Jules's commit for #2483. Client (tls13_client.connect_full): - A HelloRetryRequest now ends the handshake. Every group we support already has a key share in the ClientHello, so the server can only name one of them, which RFC 8446 §4.1.4 says to abort on. The retry path this replaces freed the caller's x25519 scalar, then derived the P-256 secret from the wrong key. - Failures close the adopted socket again. std.http hands its descriptor over and relies on the pure client to close it on every failure (aether_http.c); skipping the close leaked the descriptor. - The hybrid key exchange moves into hybrid_shared(). The new conn_group(conn) reports the negotiated group. Server (tls13_server.accept): - An all-zero x25519 result gets illegal_parameter (RFC 8446 §7.4.2), not handshake_failure. - A refused ClientHello frees what parse_client_hello allocated and is answered with an alert that fits: illegal_parameter for a bad hybrid share, handshake_failure when no group or suite is shared, protocol_version, or decode_error. Tests (https_pure_tls_vertical): - The pure client and pure server must negotiate group 4588. - A new bad_share probe sends a share that fails the FIPS 203 §7.2 modulus check, and one of the wrong length. Each must get a fatal illegal_parameter alert. - Negative controls: with main's server the group check fails (29). Without the §7.2 check, the probe gets a ServerHello. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2483.
The pure-Aether TLS client used X25519MLKEM768 only through the opt-in
connect_pq, and the pure server didn't accept it at all. Now:connect(),connect_mtls()and thestd.http.clientfallback in builds without OpenSSL all get PQ from any server that supports it.connect_pq()is now a thin wrapper. The newconn_group(conn)returns the group the server chose.mlkem768_encaps, from ML-KEM: encaps and decaps skip the FIPS 203 input checks, and the Wycheproof invalid branch cannot fail #2482), or has the wrong length, gets a fatalillegal_parameteralert (RFC 10024 §4.2). The reply is the 1120-byte share (ct ‖ x25519).Two commits:
connect_fullclosing an adopted socket on failure.std.httphands the descriptor over and relies on the pure client to close it (aether_http.c:2115), so every failed fallback handshake leaked a descriptor. The close is restored.decode_error,handshake_failure,protocol_versionorillegal_parameter) instead ofillegal_parameterfor everything. An all-zero x25519 result getsillegal_parameter(RFC 8446 §7.4.2).Tests
https_pure_tls_verticalgains:bad_share.aeprobe. One share fails the §7.2 modulus check (every coefficient decodes to 4095 ≥ q). Another is 500 bytes. Each must get alert2:47, and the server keeps serving.Negative controls (backup copies):
Interop with Go 1.24 (local; not in CI):
handshake_failurealert (unchanged: the pure server has no P-256 or HRR)Against OpenSSL 3.0 (this box), the larger ClientHello falls back to x25519, as permutation 1 shows.
Valgrind:
Gates:
fmt,check-docs,check-changelogandcheck-testspass.crypto_tls13_{client,hs,server_hs},https_client_pure_tls,https_windows_trust,http_ipv6andc_callback_multi_tupass.https_pure_tls_verticalpasses 4/4 run in parallel.Not in this PR: pure server socket ownership
pure_tls_server_acceptwraps the connection's fd, and on teardownclose_conncloses it. Thenconn_closeinaether_http_server.ccloses the same number again, on success and on the cert or key load failure paths. That's the double closeaether_http.calready guards against on the client side. On a failed handshake the 8-byte wrapper also leaks. It's pre-existing and lives instd/netC: #3138.🤖 Generated with Claude Code