From 9c3e09cbeea79a05db965e82e12ac5bec640a66c Mon Sep 17 00:00:00 2001 From: Frank Denis <124872+jedisct1@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:18:40 +0200 Subject: [PATCH] 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. --- lib/std/crypto/tls/Client.zig | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index f9c897e3f0..8cfa942399 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/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, };