-
Notifications
You must be signed in to change notification settings - Fork 734
Description
Checklist
- I've searched the issue tracker for similar bugs.
Describe the bug
After updating rustls to 0.23.27 which enables prefer-post-quantum
by default, connections to FIPS TLS servers that don't support the post-quantum algorithms have to go through a HelloRetryRequest to negotiate down to FIPS non-PQ KeyShare algorithms like SECP256R1
.
We still have to deal with a lot of servers that don't support post-quantum algorithms. It would be nice if the extra round trip wasn't required in the default configuration.
It might make more sense to use a post-quantum algorithm that has a FIPS compliant "fallback" like SECP256R1MLKEM768
when rustls is running in FIPS mode. That way in the handshake rustls will send both the PQ and non-PQ KeyShare options, instead of just the PQ option like it does now. That would prevent a round trip even when talking to non-PQ FIPS servers.
In the meantime it's easy enough to mitigate by just disabling prefer-post-quantum
.
To Reproduce
- Use rustls with
fips
feature enabled to connect to a server that supports only secp256r1. - Observe that with rustls 0.23.26 there's no HelloRetryRequest, while in 0.23.27 there is.
Example code
Cargo.toml
:
[package]
name = "repro"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.8.4"
axum-server = { version = "0.7.2", features = ["tls-rustls"] }
rcgen = "0.13.2"
reqwest = { version = "0.12.20", features = ["rustls-tls"] }
rustls = { version = "0.23.27", features = ["fips"] }
tokio = { version = "1.45.1", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
client.rs
:
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
rustls::crypto::default_fips_provider().install_default().unwrap();
let client = reqwest::ClientBuilder::new()
.use_rustls_tls()
.danger_accept_invalid_certs(true)
.build()
.unwrap();
let resp = client.get("https://127.0.0.1:8000")
.send().await.unwrap()
.text().await.unwrap();
println!("{}", resp);
}
server.rs
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let rcgen::CertifiedKey { cert, key_pair } = rcgen::generate_simple_self_signed(["localhost".to_string()]).unwrap();
let mut crypto_provider = rustls::crypto::default_fips_provider();
crypto_provider.kx_groups = vec![rustls::crypto::aws_lc_rs::kx_group::SECP256R1];
crypto_provider.install_default().unwrap();
let config = axum_server::tls_rustls::RustlsConfig::from_der(vec![cert.der().to_vec()], key_pair.serialized_der().to_vec()).await.unwrap();
let app = axum::Router::new().route("/", axum::routing::get(|| async { "Hello, World!" }));
axum_server::bind_rustls(std::net::SocketAddr::from(([127, 0, 0, 1], 8000)), config)
.serve(app.into_make_service())
.await
.unwrap()
}
Running:
RUST_LOG=trace cargo run --bin client
Client output:
2025-06-15T06:45:48.540799Z DEBUG rustls::client::hs: No cached session for IpAddress(V4(Ipv4Addr([127, 0, 0, 1])))
2025-06-15T06:45:48.542694Z DEBUG rustls::client::hs: Not resuming any session
2025-06-15T06:45:48.542947Z TRACE rustls::client::hs: Sending ClientHello Message {
version: TLSv1_0,
payload: Handshake {
parsed: HandshakeMessagePayload {
typ: ClientHello,
payload: ClientHello(
ClientHelloPayload {
client_version: TLSv1_2,
random: cca9a4792c34535cbe8e2161805406230ab8873e83ee428a69cf47deb0630d57,
session_id: 53af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516,
cipher_suites: [
TLS13_AES_256_GCM_SHA384,
TLS13_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
],
compression_methods: [
Null,
],
extensions: [
EcPointFormats(
[
Uncompressed,
],
),
ExtendedMasterSecretRequest,
SignatureAlgorithms(
[
RSA_PKCS1_SHA1,
ECDSA_SHA1_Legacy,
RSA_PKCS1_SHA256,
ECDSA_NISTP256_SHA256,
RSA_PKCS1_SHA384,
ECDSA_NISTP384_SHA384,
RSA_PKCS1_SHA512,
ECDSA_NISTP521_SHA512,
RSA_PSS_SHA256,
RSA_PSS_SHA384,
RSA_PSS_SHA512,
ED25519,
ED448,
],
),
Protocols(
[
ProtocolName(
6832,
),
ProtocolName(
687474702f312e31,
),
],
),
SessionTicket(
Request,
),
KeyShare(
[
KeyShareEntry {
group: X25519MLKEM768,
payload: 9797772a163597426453baa15ac109033bb9915bca01c6699cf26c34e7254ff9b867877e88ac08ccb1c1c09332691b022475051356720085ad94928eecd02f0b9c12536065c02754041c1d6524b1578bc7527773f2641dac30b5897a6e5df3c667d94df24b6e6fb630f97783114b14cc1231efb07224e37097199b31185216c685e7381ecdd93d21d730d82765f519a2f6105a78147e856a73d7e685c73909bc77a8c9995f7e810e1ccc0c841c8c618b9183543c98c85a93f091a7e52c631a1b547888c2f9961ec5a362808727806cffc04809e2ce88737219c1478b13426eb151aecb971da6306f86971f8a4c9b27c0d5464c7b16cd44c5a3ee60058004880492634fcac66968cfc9275f821940fbe26bdcf934a5a085489ab4018a6f835657211a95c5eaabc4d90368110be0a18b1f526e53bb5b6898cd28c3ccdc425a6260291b1991124c7ae35a5cd51098104a30efb97c932aafd7843e4329513503a7c2428d4db2568f10b8f096c74b24c2765b3801e61e186904f638a4e5f557a972520d4c1196a1513c909eb55861cf496889c811c47087f69339e30373fc486a73d0be7afb95f29b5e2395414b1a57bc91576315b3b7d35d294c5425f90ffeb731a0f3751d6a91d0c07ff997a160a1ce54858790a2858082589240518b21360056ad0c4cbe9b70a348e751aa920259793ce73033e275025bc6668b9cb820014ac4d7621d05ca9ee236f6820d323526e2c207da1619c282063e07c3ef36963eb7b4aba2c98653cb28ea173b6456696598416bc8812211d7e55f9dc57abf80957b37bf7dc059e398885afb35ca9782bb7212de54877bd6b1fb8b4873932a59cc45f6e0834be8c0e7fc75b75c4a4089b25ad2a746a5a081ab75f0944ff3489177bb9e8e1b524ce0bd7cba7e9f90bf0004341af637ea66c7e122ae8256146ab315fc2b9dcd9116db5207578bb109f09564f1388e063575048b0af087ba5795e5a07a969a8c3ee988fd02bf8029a10de7ca7962bce5974651a420206319725a488bf55bda5111ef5b75daa479bdcba36713bdfa733455114fec4889ae4b5a654c34366964a4853694289aae006c38ac6ab70192ddd46db263559d8a130eb76bf664836ca5464d6a02aca39f37a8838cd9767410b76537a39355cfcae6268bf125e78cac5774b079b75f644917a2e094c2e618be2c5dad3c7e561502b0475687a2781889ad23e442d946b37d09be4bd863ab09adb893415dc749c104592dba265e6a587b3b1f3a3a5ea3b2626e06566e02002c35cc135a7f17003fa387cd98aa08ebe901ef2672bc42b3e74a4e1e2592dfb3cc51bbb3fc0c031cfcb6e1f7a468b0b339c9485e07c3c5f878dcc56594014ce6cb04aa5a19da055574755b5c159eef6630a6129f38c7cd957882b1465d6b185c46d4a737e9005fc501917799d128b08cb8a3ca02b70049acddb917ded103e941527c1b6f8b620393a67b827b13a3b5bfd9ac2f319c116d7087ebe7821ce9ac64a46c73621c9f81b77a0b1410ca249bb54fd8437185f36176fa8b0dfa3c6b7971395144e2eb703eb9c3a1fbb98eb744492726f61107182c5e317aa6a0127aa5d5ae1ce60d09d22347bbb62d996145b87059eaaac1641837803e3740c3c45adbc3d5285c736f686efa72a6fb78857f2a23a8853730cef5de559f200b8829c031111878e18286312ad755f5ab47477d3c217e7b69,
},
],
),
CertificateStatusRequest(
Ocsp(
OcspCertificateStatusRequest {
responder_ids: [],
extensions: ,
},
),
),
NamedGroups(
[
X25519MLKEM768,
secp256r1,
secp384r1,
],
),
SupportedVersions(
SupportedProtocolVersions {
tls13: true,
tls12: true,
},
),
PresharedKeyModes(
[
PSK_DHE_KE,
],
),
],
},
),
},
encoded: 010005850303cca9a4792c34535cbe8e2161805406230ab8873e83ee428a69cf47deb0630d572053af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516000e13021301c02cc02bc030c02f00ff0100052e000b0002010000170000000d001c001a02010203040104030501050306010603080408050806080708080010000e000c02683208687474702f312e3100230000003304c604c411ec04c09797772a163597426453baa15ac109033bb9915bca01c6699cf26c34e7254ff9b867877e88ac08ccb1c1c09332691b022475051356720085ad94928eecd02f0b9c12536065c02754041c1d6524b1578bc7527773f2641dac30b5897a6e5df3c667d94df24b6e6fb630f97783114b14cc1231efb07224e37097199b31185216c685e7381ecdd93d21d730d82765f519a2f6105a78147e856a73d7e685c73909bc77a8c9995f7e810e1ccc0c841c8c618b9183543c98c85a93f091a7e52c631a1b547888c2f9961ec5a362808727806cffc04809e2ce88737219c1478b13426eb151aecb971da6306f86971f8a4c9b27c0d5464c7b16cd44c5a3ee60058004880492634fcac66968cfc9275f821940fbe26bdcf934a5a085489ab4018a6f835657211a95c5eaabc4d90368110be0a18b1f526e53bb5b6898cd28c3ccdc425a6260291b1991124c7ae35a5cd51098104a30efb97c932aafd7843e4329513503a7c2428d4db2568f10b8f096c74b24c2765b3801e61e186904f638a4e5f557a972520d4c1196a1513c909eb55861cf496889c811c47087f69339e30373fc486a73d0be7afb95f29b5e2395414b1a57bc91576315b3b7d35d294c5425f90ffeb731a0f3751d6a91d0c07ff997a160a1ce54858790a2858082589240518b21360056ad0c4cbe9b70a348e751aa920259793ce73033e275025bc6668b9cb820014ac4d7621d05ca9ee236f6820d323526e2c207da1619c282063e07c3ef36963eb7b4aba2c98653cb28ea173b6456696598416bc8812211d7e55f9dc57abf80957b37bf7dc059e398885afb35ca9782bb7212de54877bd6b1fb8b4873932a59cc45f6e0834be8c0e7fc75b75c4a4089b25ad2a746a5a081ab75f0944ff3489177bb9e8e1b524ce0bd7cba7e9f90bf0004341af637ea66c7e122ae8256146ab315fc2b9dcd9116db5207578bb109f09564f1388e063575048b0af087ba5795e5a07a969a8c3ee988fd02bf8029a10de7ca7962bce5974651a420206319725a488bf55bda5111ef5b75daa479bdcba36713bdfa733455114fec4889ae4b5a654c34366964a4853694289aae006c38ac6ab70192ddd46db263559d8a130eb76bf664836ca5464d6a02aca39f37a8838cd9767410b76537a39355cfcae6268bf125e78cac5774b079b75f644917a2e094c2e618be2c5dad3c7e561502b0475687a2781889ad23e442d946b37d09be4bd863ab09adb893415dc749c104592dba265e6a587b3b1f3a3a5ea3b2626e06566e02002c35cc135a7f17003fa387cd98aa08ebe901ef2672bc42b3e74a4e1e2592dfb3cc51bbb3fc0c031cfcb6e1f7a468b0b339c9485e07c3c5f878dcc56594014ce6cb04aa5a19da055574755b5c159eef6630a6129f38c7cd957882b1465d6b185c46d4a737e9005fc501917799d128b08cb8a3ca02b70049acddb917ded103e941527c1b6f8b620393a67b827b13a3b5bfd9ac2f319c116d7087ebe7821ce9ac64a46c73621c9f81b77a0b1410ca249bb54fd8437185f36176fa8b0dfa3c6b7971395144e2eb703eb9c3a1fbb98eb744492726f61107182c5e317aa6a0127aa5d5ae1ce60d09d22347bbb62d996145b87059eaaac1641837803e3740c3c45adbc3d5285c736f686efa72a6fb78857f2a23a8853730cef5de559f200b8829c031111878e18286312ad755f5ab47477d3c217e7b69000500050100000000000a0008000611ec00170018002b00050403040303002d00020101,
},
}
2025-06-15T06:45:48.544361Z TRACE rustls::client::hs: Got HRR HelloRetryRequest { legacy_version: TLSv1_2, session_id: 53af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516, cipher_suite: TLS13_AES_256_GCM_SHA384, extensions: [KeyShare(secp256r1), SupportedVersions(TLSv1_3)] }
2025-06-15T06:45:48.544875Z TRACE rustls::client::hs: Sending ClientHello Message {
version: TLSv1_2,
payload: Handshake {
parsed: HandshakeMessagePayload {
typ: ClientHello,
payload: ClientHello(
ClientHelloPayload {
client_version: TLSv1_2,
random: cca9a4792c34535cbe8e2161805406230ab8873e83ee428a69cf47deb0630d57,
session_id: 53af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516,
cipher_suites: [
TLS13_AES_256_GCM_SHA384,
TLS13_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
],
compression_methods: [
Null,
],
extensions: [
EcPointFormats(
[
Uncompressed,
],
),
ExtendedMasterSecretRequest,
SignatureAlgorithms(
[
RSA_PKCS1_SHA1,
ECDSA_SHA1_Legacy,
RSA_PKCS1_SHA256,
ECDSA_NISTP256_SHA256,
RSA_PKCS1_SHA384,
ECDSA_NISTP384_SHA384,
RSA_PKCS1_SHA512,
ECDSA_NISTP521_SHA512,
RSA_PSS_SHA256,
RSA_PSS_SHA384,
RSA_PSS_SHA512,
ED25519,
ED448,
],
),
Protocols(
[
ProtocolName(
6832,
),
ProtocolName(
687474702f312e31,
),
],
),
SessionTicket(
Request,
),
KeyShare(
[
KeyShareEntry {
group: secp256r1,
payload: 048d90ee7fbb14b245b5d30e385d1a9ae1bed286a7c1d77d8ce1bf2353a6ef3dc80cb452ff0c0519b1f1ab31f1d571d64e43513344bdedb3888b021740f7aadadd,
},
],
),
CertificateStatusRequest(
Ocsp(
OcspCertificateStatusRequest {
responder_ids: [],
extensions: ,
},
),
),
NamedGroups(
[
X25519MLKEM768,
secp256r1,
secp384r1,
],
),
SupportedVersions(
SupportedProtocolVersions {
tls13: true,
tls12: true,
},
),
PresharedKeyModes(
[
PSK_DHE_KE,
],
),
],
},
),
},
encoded: 010001060303cca9a4792c34535cbe8e2161805406230ab8873e83ee428a69cf47deb0630d572053af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516000e13021301c02cc02bc030c02f00ff010000af000b0002010000170000000d001c001a02010203040104030501050306010603080408050806080708080010000e000c02683208687474702f312e310023000000330047004500170041048d90ee7fbb14b245b5d30e385d1a9ae1bed286a7c1d77d8ce1bf2353a6ef3dc80cb452ff0c0519b1f1ab31f1d571d64e43513344bdedb3888b021740f7aadadd000500050100000000000a0008000611ec00170018002b00050403040303002d00020101,
},
}
2025-06-15T06:45:48.545067Z TRACE rustls::conn: Dropping CCS
2025-06-15T06:45:48.546164Z TRACE rustls::client::hs: We got ServerHello ServerHelloPayload {
extensions: [
KeyShare(
KeyShareEntry {
group: secp256r1,
payload: 04404abd137d7f80e54ee43d3764e7f2ab9d544150f2b23af961e843faaf451406d28d184ce79d36dafcae31deff103c25e31e0d763978820112ef1144cb6b0208,
},
),
SupportedVersions(
TLSv1_3,
),
],
legacy_version: TLSv1_2,
random: 71848961bd81ebafde9a6b72ef2e7bb703a41ffbf8d4ddea86e2ad615be65e88,
session_id: 53af4dd2f8df6913d3ca531b271e136f42bb95f52649566f895315061a26e516,
cipher_suite: TLS13_AES_256_GCM_SHA384,
compression_method: Null,
}
2025-06-15T06:45:48.546206Z DEBUG rustls::client::hs: Using ciphersuite TLS13_AES_256_GCM_SHA384
2025-06-15T06:45:48.546224Z DEBUG rustls::client::tls13: Not resuming
2025-06-15T06:45:48.546232Z TRACE rustls::client::client_conn: EarlyData rejected
2025-06-15T06:45:48.546461Z DEBUG rustls::client::tls13: TLS1.3 encrypted extensions: [Protocols(SingleProtocolName(ProtocolName(6832)))]
2025-06-15T06:45:48.546477Z DEBUG rustls::client::hs: ALPN protocol is Some(b"h2")
2025-06-15T06:45:48.546541Z TRACE rustls::client::tls13: Server cert is CertificateChain([CertificateDer(0x3082015e30820104a003020102021443f6ff1d1133a7fba690faf7acf23be9062eb3ac300a06082a8648ce3d0403023021311f301d06035504030c16726367656e2073656c66207369676e656420636572743020170d3735303130313030303030305a180f34303936303130313030303030305a3021311f301d06035504030c16726367656e2073656c66207369676e656420636572743059301306072a8648ce3d020106082a8648ce3d03010703420004aa707876af817c015dd0a9e35a55f3eb69760ab0e271995bc5675db2f9256d850d0b6383c89b08a81a8e4b0f2f3cfe1cdc05784aa7c7b16ec3230f4546db65afa318301630140603551d11040d300b82096c6f63616c686f7374300a06082a8648ce3d040302034800304502202af2319af32606301358b8630c3ba3bffeaffc236b2977c477d8ced0d38a06a6022100974f9d16806b355796dc3975cd9f951be09f35bb886d45693110ab755d91d754)])
The issue was introduced in 0.23.27 with the enablement of the use of PQ algorithms by default.
Expected behavior
This is an expected behavior based on how rustls is configured right now.
Maybe this shouldn't be a bug report, but it seemed like the best template for starting a discussion on this.
Additional context
X25519 is not FIPS: #1992
So when X25519MLKEM768
is used in a handshake, the bare X25519
KeyShare cannot be used, like it would be for non-FIPS handshakes: https://github.com/rustls/rustls/blob/main/rustls/src/client/hs.rs#L328-L331