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