From dfea7ef80023f685861ff678fb0a4cf53b26a7e1 Mon Sep 17 00:00:00 2001 From: Jack Cutrara Date: Tue, 4 Aug 2026 08:48:49 -0400 Subject: [PATCH 1/2] fix: initialize `send_ticket_request` in the impit client config builder The merge of upstream v/0.23.43 added `ClientConfig::send_ticket_request` and updated the default builder to initialize it, but the parallel `ClientConfig` initializer guarded by `#[cfg(feature = "impit")]` was not updated, so building with the `impit` feature fails: error[E0063]: missing field `send_ticket_request` in initializer of `ClientConfig` --> rustls/src/client/builder.rs:309 Defaults to `None`, matching both the field's documented default and the non-impit builder a few lines above, so no ticket_request extension is sent and emulated ClientHellos are unchanged. Co-Authored-By: Claude Fable 5 --- rustls/src/client/builder.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rustls/src/client/builder.rs b/rustls/src/client/builder.rs index af48c8b3e23..ce871e88c49 100644 --- a/rustls/src/client/builder.rs +++ b/rustls/src/client/builder.rs @@ -327,6 +327,7 @@ impl ConfigBuilder { cert_decompressors, cert_compression_cache: Arc::new(compress::CompressionCache::default()), ech_mode: self.state.client_ech_mode, + send_ticket_request: None, } } } From a4a314ea0c2749cd5b4f6763b5b7d537303b0aa2 Mon Sep 17 00:00:00 2001 From: Jack Cutrara Date: Mon, 3 Aug 2026 13:39:41 -0400 Subject: [PATCH 2/2] feat: add ML-DSA signature algorithms to fingerprint emulation Chrome 150+ leads its ClientHello signature_algorithms list with the three ML-DSA codepoints from draft-ietf-tls-mldsa: 0x0904 (mldsa44), 0x0905 (mldsa65) and 0x0906 (mldsa87). The SignatureScheme constants already existed, but FingerprintSignatureAlgorithm had no variants for them, so no emulated fingerprint could put them on the wire. Two things here are easy to get wrong, hence the comments in the code. The mapping returned by to_mapping_entry() doubles as the list of schemes advertised in the ClientHello, so ML-DSA needs an entry there rather than the None used for Ed448 - returning None compiles but silently omits the codepoints, and to_signature_scheme() has no callers. That entry also has to be non-empty: verify_tls13_signature indexes the first element of the returned slice, and supported_in_tls13() is a denylist that lets ML-DSA through, so an empty slice would panic on attacker-reachable input. The placeholder verifier cannot validate an ML-DSA signature, so a server that selects one fails the handshake cleanly instead. Note this is advertise-only: webpki has no ML-DSA verifier, so unlike real Chrome we cannot complete a handshake against a server that presents an ML-DSA certificate. No publicly-trusted CA issues those today. Co-Authored-By: Claude Fable 5 --- rustls/src/crypto/emulation/mod.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rustls/src/crypto/emulation/mod.rs b/rustls/src/crypto/emulation/mod.rs index 3e195706ab8..7aeb906a9e1 100644 --- a/rustls/src/crypto/emulation/mod.rs +++ b/rustls/src/crypto/emulation/mod.rs @@ -254,6 +254,10 @@ pub enum FingerprintSignatureAlgorithm { // EdDSA algorithms Ed25519, Ed448, + // ML-DSA algorithms (draft-ietf-tls-mldsa) + MlDsa44, + MlDsa65, + MlDsa87, // Legacy EcdsaSha1Legacy, } @@ -274,6 +278,9 @@ impl FingerprintSignatureAlgorithm { Self::RsaPkcs1Sha1 => SignatureScheme::RSA_PKCS1_SHA1, Self::Ed25519 => SignatureScheme::ED25519, Self::Ed448 => SignatureScheme::ED448, + Self::MlDsa44 => SignatureScheme::ML_DSA_44, + Self::MlDsa65 => SignatureScheme::ML_DSA_65, + Self::MlDsa87 => SignatureScheme::ML_DSA_87, Self::EcdsaSha1Legacy => SignatureScheme::ECDSA_SHA1_Legacy, } } @@ -416,6 +423,9 @@ impl FingerprintSignatureAlgorithm { Self::Ed25519 => ED25519_ALGS, // Ed448 is not supported by webpki, SHA1 legacy uses fallback in mapping Self::Ed448 | Self::RsaPkcs1Sha1 | Self::EcdsaSha1Legacy => EMPTY, + // ML-DSA is advertised to match browsers that offer it, but webpki has + // no verifier for it, so it contributes nothing to certificate validation. + Self::MlDsa44 | Self::MlDsa65 | Self::MlDsa87 => EMPTY, } } @@ -457,6 +467,21 @@ impl FingerprintSignatureAlgorithm { static ECDSA_SHA1_FALLBACK: &[&dyn pki_types::SignatureVerificationAlgorithm] = &[webpki_algs::ECDSA_P256_SHA256]; static ED25519: &[&dyn pki_types::SignatureVerificationAlgorithm] = &[webpki_algs::ED25519]; + // ML-DSA is advertised for fingerprint accuracy but webpki has no verifier + // for it. The mapping doubles as the list of schemes we offer, so the entry + // has to exist; it must also be non-empty, because the TLS 1.3 verify path + // indexes the first element. This placeholder can never validate an ML-DSA + // signature, so a server that actually selects one fails the handshake + // cleanly instead of panicking. + // + // Ed25519 rather than a P-256 verifier: the placeholder does get run, so a + // server whose certificate key matches it could have a CertificateVerify + // mislabelled as ML-DSA accepted. Only the legitimate key holder can produce + // such a signature, but P-256 is the common case for publicly-trusted server + // certificates whereas Ed25519 is not issued by public CAs, so this keeps the + // window as small as the fallback approach allows. + static ML_DSA_FALLBACK: &[&dyn pki_types::SignatureVerificationAlgorithm] = + &[webpki_algs::ED25519]; match self { Self::EcdsaSecp256r1Sha256 => { @@ -481,6 +506,9 @@ impl FingerprintSignatureAlgorithm { Self::Ed25519 => Some((SignatureScheme::ED25519, ED25519)), // Ed448 is not supported Self::Ed448 => None, + Self::MlDsa44 => Some((SignatureScheme::ML_DSA_44, ML_DSA_FALLBACK)), + Self::MlDsa65 => Some((SignatureScheme::ML_DSA_65, ML_DSA_FALLBACK)), + Self::MlDsa87 => Some((SignatureScheme::ML_DSA_87, ML_DSA_FALLBACK)), } } }