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.
This commit is contained in:
Frank Denis 2025-08-27 11:18:40 +02:00 committed by Alex Rønne Petersen
parent a4cb636658
commit 9c3e09cbee
No known key found for this signature in database

View file

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