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
320320 var handshake_state: HandshakeState = .hello;
321321 var handshake_cipher: tls.HandshakeCipher = undefined;
322322 var main_cert_pub_key: CertificatePublicKey = undefined;
323 var tls12_negotiated_group: ?tls.NamedGroup = null;
323324 const now_sec = std.time.timestamp();
324325
325326 var cleartext_fragment_start: usize = 0;
......@@ -679,6 +680,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
679680 const curve_type = hsd.decode(u8);
680681 if (curve_type != 0x03) return error.TlsIllegalParameter; // named_curve
681682 const named_group = hsd.decode(tls.NamedGroup);
683 tls12_negotiated_group = named_group;
682684 const key_size = hsd.decode(u8);
683685 try hsd.ensure(key_size);
684686 const server_pub_key = hsd.slice(key_size);
......@@ -691,10 +693,19 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
691693 if (cipher_state != .cleartext) return error.TlsUnexpectedMessage;
692694 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)} ++
695704 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
696 array(u16, u8, .{@intFromEnum(tls.HandshakeType.client_key_exchange)} ++
697 array(u24, u8, array(u8, u8, key_share.secp256r1_kp.public_key.toUncompressedSec1())));
705 int(u16, @intCast(public_key_bytes.len + 5)) ++ // record length
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
698709 const client_change_cipher_spec_msg = .{@intFromEnum(tls.ContentType.change_cipher_spec)} ++
699710 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
700711 array(u16, tls.ChangeCipherSpecType, .{.change_cipher_spec});
......@@ -703,7 +714,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
703714 inline else => |*p| {
704715 const P = @TypeOf(p.*).A;
705716 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);
707719 const master_secret = hmacExpandLabel(P.Hmac, pre_master_secret, &.{
708720 "master secret",
709721 &client_hello_rand,
......@@ -757,8 +769,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
757769 nonce,
758770 pv.app_cipher.client_write_key,
759771 );
760 var all_msgs_vec: [3][]const u8 = .{
761 &client_key_exchange_msg,
772 var all_msgs_vec: [4][]const u8 = .{
773 &client_key_exchange_prefix,
774 public_key_bytes,
762775 &client_change_cipher_spec_msg,
763776 &client_verify_msg,
764777 };