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 noreply@github.comGitHub <noreply@github.com> 2025-08-27 11:18:40+02:00
log12a58087a423e33dd5fc8daa5c9fb556fe93f7a9
treee941d6830db99ccba93546c626594f2a0b7e3010
parentae2622bf828cd0b2abd7cc3e6b1294de0b167dc6
signaturebadge-check Signed by PGP key B5690EEEBB952194

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 };