Commit 12a58087a4

Frank Denis <124872+jedisct1@users.noreply.github.com>
2025-08-27 11:18:40
Fix TLS 1.2 client key exchange to use negotiated named group (#25007)
The TLS 1.2 implementation was incorrectly hardcoded to always send the secp256r1 public key in the client key exchange message, regardless of which elliptic curve the server actually negotiated. This caused TLS handshake failures with servers that preferred other curves like X25519. This fix: - Tracks the negotiated named group from the server key exchange message - Dynamically selects the correct public key (X25519, secp256r1, or secp384r1) based on what the server negotiated - Properly constructs the client key exchange message with the appropriate key size for each curve type Fixes TLS 1.2 connections to servers like ziglang.freetls.fastly.net that prefer X25519 over secp256r1.
1 parent ae2622b
Changed files (1)
lib
std
crypto
lib/std/crypto/tls/Client.zig
@@ -320,6 +320,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
     var handshake_state: HandshakeState = .hello;
     var handshake_cipher: tls.HandshakeCipher = undefined;
     var main_cert_pub_key: CertificatePublicKey = undefined;
+    var tls12_negotiated_group: ?tls.NamedGroup = null;
     const now_sec = std.time.timestamp();
 
     var cleartext_fragment_start: usize = 0;
@@ -679,6 +680,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
                         const curve_type = hsd.decode(u8);
                         if (curve_type != 0x03) return error.TlsIllegalParameter; // named_curve
                         const named_group = hsd.decode(tls.NamedGroup);
+                        tls12_negotiated_group = named_group;
                         const key_size = hsd.decode(u8);
                         try hsd.ensure(key_size);
                         const server_pub_key = hsd.slice(key_size);
@@ -691,10 +693,19 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
                         if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
                         if (handshake_state != .server_hello_done) return error.TlsUnexpectedMessage;
 
-                        const client_key_exchange_msg = .{@intFromEnum(tls.ContentType.handshake)} ++
+                        const public_key_bytes: []const u8 = switch (tls12_negotiated_group orelse .secp256r1) {
+                            .secp256r1 => &key_share.secp256r1_kp.public_key.toUncompressedSec1(),
+                            .secp384r1 => &key_share.secp384r1_kp.public_key.toUncompressedSec1(),
+                            .x25519 => &key_share.x25519_kp.public_key,
+                            else => return error.TlsIllegalParameter,
+                        };
+
+                        const client_key_exchange_prefix = .{@intFromEnum(tls.ContentType.handshake)} ++
                             int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
-                            array(u16, u8, .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++
-                                array(u24, u8, array(u8, u8, key_share.secp256r1_kp.public_key.toUncompressedSec1())));
+                            int(u16, @intCast(public_key_bytes.len + 5)) ++ // record length
+                            .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++
+                            int(u24, @intCast(public_key_bytes.len + 1)) ++ // handshake message length
+                            .{@as(u8, @intCast(public_key_bytes.len))}; // public key length
                         const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++
                             int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
                             array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec});
@@ -703,7 +714,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
                             inline else => |*p| {
                                 const P = @TypeOf(p.*).A;
                                 p.transcript_hash.update(wrapped_handshake);
-                                p.transcript_hash.update(client_key_exchange_msg[tls.record_header_len..]);
+                                p.transcript_hash.update(client_key_exchange_prefix[tls.record_header_len..]);
+                                p.transcript_hash.update(public_key_bytes);
                                 const master_secret = hmacExpandLabel(P.Hmac, pre_master_secret, &.{
                                     "master secret",
                                     &client_hello_rand,
@@ -757,8 +769,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
                                     nonce,
                                     pv.app_cipher.client_write_key,
                                 );
-                                var all_msgs_vec: [3][]const u8 = .{
-                                    &client_key_exchange_msg,
+                                var all_msgs_vec: [4][]const u8 = .{
+                                    &client_key_exchange_prefix,
+                                    public_key_bytes,
                                     &client_change_cipher_spec_msg,
                                     &client_verify_msg,
                                 };