authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2025-08-27 11:18:40+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-08-27 20:53:42+02:00
log9c3e09cbeea79a05db965e82e12ac5bec640a66c
treedda85b69f1ddefb3e6c2ab812e1884cd86715266
parenta4cb63665812384376e7730fb7c394b6a38c57f8
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

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 files changed, 19 insertions(+), 6 deletions(-)

lib/std/crypto/tls/Client.zig+19-6
...@@ -320,6 +320,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -320,6 +320,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
320 var handshake_state: HandshakeState = .hello;320 var handshake_state: HandshakeState = .hello;
321 var handshake_cipher: tls.HandshakeCipher = undefined;321 var handshake_cipher: tls.HandshakeCipher = undefined;
322 var main_cert_pub_key: CertificatePublicKey = undefined;322 var main_cert_pub_key: CertificatePublicKey = undefined;
323 var tls12_negotiated_group: ?tls.NamedGroup = null;
323 const now_sec = std.time.timestamp();324 const now_sec = std.time.timestamp();
324325
325 var cleartext_fragment_start: usize = 0;326 var cleartext_fragment_start: usize = 0;
...@@ -679,6 +680,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -679,6 +680,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
679 const curve_type = hsd.decode(u8);680 const curve_type = hsd.decode(u8);
680 if (curve_type != 0x03) return error.TlsIllegalParameter; // named_curve681 if (curve_type != 0x03) return error.TlsIllegalParameter; // named_curve
681 const named_group = hsd.decode(tls.NamedGroup);682 const named_group = hsd.decode(tls.NamedGroup);
683 tls12_negotiated_group = named_group;
682 const key_size = hsd.decode(u8);684 const key_size = hsd.decode(u8);
683 try hsd.ensure(key_size);685 try hsd.ensure(key_size);
684 const server_pub_key = hsd.slice(key_size);686 const server_pub_key = hsd.slice(key_size);
...@@ -691,10 +693,19 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -691,10 +693,19 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
691 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;693 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
692 if (handshake_state != .server_hello_done) return error.TlsUnexpectedMessage;694 if (handshake_state != .server_hello_done) return error.TlsUnexpectedMessage;
693695
694 const client_key_exchange_msg = .{@intFromEnum(tls.ContentType.handshake)} ++696 const public_key_bytes: []const u8 = switch (tls12_negotiated_group orelse .secp256r1) {
697 .secp256r1 => &key_share.secp256r1_kp.public_key.toUncompressedSec1(),
698 .secp384r1 => &key_share.secp384r1_kp.public_key.toUncompressedSec1(),
699 .x25519 => &key_share.x25519_kp.public_key,
700 else => return error.TlsIllegalParameter,
701 };
702
703 const client_key_exchange_prefix = .{@intFromEnum(tls.ContentType.handshake)} ++
695 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++704 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
696 array(u16, u8, .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++705 int(u16, @intCast(public_key_bytes.len + 5)) ++ // record length
697 array(u24, u8, array(u8, u8, key_share.secp256r1_kp.public_key.toUncompressedSec1())));706 .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++
707 int(u24, @intCast(public_key_bytes.len + 1)) ++ // handshake message length
708 .{@as(u8, @intCast(public_key_bytes.len))}; // public key length
698 const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++709 const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++
699 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++710 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
700 array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec});711 array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec});
...@@ -703,7 +714,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -703,7 +714,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
703 inline else => |*p| {714 inline else => |*p| {
704 const P = @TypeOf(p.*).A;715 const P = @TypeOf(p.*).A;
705 p.transcript_hash.update(wrapped_handshake);716 p.transcript_hash.update(wrapped_handshake);
706 p.transcript_hash.update(client_key_exchange_msg[tls.record_header_len..]);717 p.transcript_hash.update(client_key_exchange_prefix[tls.record_header_len..]);
718 p.transcript_hash.update(public_key_bytes);
707 const master_secret = hmacExpandLabel(P.Hmac, pre_master_secret, &.{719 const master_secret = hmacExpandLabel(P.Hmac, pre_master_secret, &.{
708 "master secret",720 "master secret",
709 &client_hello_rand,721 &client_hello_rand,
...@@ -757,8 +769,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -757,8 +769,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
757 nonce,769 nonce,
758 pv.app_cipher.client_write_key,770 pv.app_cipher.client_write_key,
759 );771 );
760 var all_msgs_vec: [3][]const u8 = .{772 var all_msgs_vec: [4][]const u8 = .{
761 &client_key_exchange_msg,773 &client_key_exchange_prefix,
774 public_key_bytes,
762 &client_change_cipher_spec_msg,775 &client_change_cipher_spec_msg,
763 &client_verify_msg,776 &client_verify_msg,
764 };777 };