From c71d5bbfd83f7ae2471419f252b29787103d9de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20R=C3=BCth?= <1324490+janrueth@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:58:19 +0200 Subject: [PATCH] Drop X448, P-521, FFDHE2048 to match boring's default group list --- Readme.md | 16 +--- boring-rustls-provider/src/kx/dh.rs | 123 --------------------------- boring-rustls-provider/src/kx/ex.rs | 34 +++----- boring-rustls-provider/src/kx/mod.rs | 55 ++---------- boring-rustls-provider/src/lib.rs | 43 +++------- boring-rustls-provider/tests/e2e.rs | 30 +++++-- 6 files changed, 57 insertions(+), 244 deletions(-) delete mode 100644 boring-rustls-provider/src/kx/dh.rs diff --git a/Readme.md b/Readme.md index 6c86f82..578f0b6 100644 --- a/Readme.md +++ b/Readme.md @@ -42,23 +42,13 @@ ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 ### Key Exchange Groups -Post-quantum hybrid (requires `mlkem` feature, TLS 1.3 only): -``` -X25519MLKEM768 (0x11ec) -``` +Matches boring's default supported group list: -ECDHE: ``` +X25519MLKEM768 (0x11ec, requires mlkem feature, TLS 1.3 only) X25519 -X448 secp256r1 (P-256) secp384r1 (P-384) -secp521r1 (P-521) -``` - -FFDHE: -``` -ffdhe2048 ``` When `mlkem` is enabled, X25519MLKEM768 is the preferred (first) group in both @@ -108,7 +98,7 @@ boring's `fips202205` compliance policy: - **Cipher suites**: AES-GCM only (no ChaCha20-Poly1305). - **Key exchange groups**: X25519MLKEM768 (preferred), P-256, and P-384 only - (no standalone X25519, X448, P-521, or FFDHE). + (no standalone X25519). - **Signature algorithms**: RSA PKCS#1 / PSS and ECDSA with P-256 or P-384 only (no P-521, Ed25519, or Ed448). diff --git a/boring-rustls-provider/src/kx/dh.rs b/boring-rustls-provider/src/kx/dh.rs deleted file mode 100644 index 8e7839d..0000000 --- a/boring-rustls-provider/src/kx/dh.rs +++ /dev/null @@ -1,123 +0,0 @@ -use boring::{dh::Dh, error::ErrorStack, pkey::Private}; -use foreign_types::ForeignType; -use rustls::crypto; - -use crate::helper::{cvt, cvt_p, log_and_map}; - -use super::DhKeyType; - -/// This type can be used to perform a -/// Diffie-Hellman key exchange. -pub struct KeyExchange { - dh: Dh, - pub_bytes: Vec, - key_type: DhKeyType, -} - -impl KeyExchange { - // Generate a new KeyExchange with a random FFDHE_2048 private key - pub fn generate_ffdhe_2048() -> Result { - let mut me = Self { - dh: unsafe { Dh::from_ptr(cvt_p(boring_sys::DH_get_rfc7919_2048())?) }, - pub_bytes: Vec::new(), - key_type: DhKeyType::FFDHE2048, - }; - - me.pub_bytes = unsafe { - // generate a new key pair - cvt(boring_sys::DH_generate_key(me.dh.as_ptr()))?; - - // get a reference to the pub key - let pubkey = boring_sys::DH_get0_pub_key(me.dh.as_ptr()); - - // figure out how many bytes we need, round up to the next full byte - let size = (boring_sys::BN_num_bits(pubkey) as usize).div_ceil(8); - - // alloc a vector with enough capacity - let mut v = Vec::with_capacity(size); - - // convert to binary representation - let after_size = boring_sys::BN_bn2bin(pubkey, v.as_mut_ptr()); - // size should be what we calculated before - assert_eq!(size, after_size); - - // ensure those bytes are accessible in the vec - v.set_len(size); - v - }; - - Ok(me) - } - - /// Generate a shared secret with the other's raw public key - fn diffie_hellman(&self, raw_public_key: &[u8]) -> Result, ErrorStack> { - let peer = boring::bn::BigNum::from_slice(raw_public_key)?; - - let secret_len = unsafe { cvt(boring_sys::DH_size(self.dh.as_ptr()))? } as usize; - let mut secret = vec![0u8; secret_len]; - - let secret_len = unsafe { - cvt(boring_sys::DH_compute_key_padded( - secret.as_mut_ptr(), - peer.as_ptr(), - self.dh.as_ptr(), - ))? - } as usize; - - secret.truncate(secret_len); - Ok(secret) - } -} - -impl crypto::ActiveKeyExchange for KeyExchange { - fn complete( - self: Box, - peer_pub_key: &[u8], - ) -> Result { - let expected_len = self.pub_bytes.len(); - - if peer_pub_key.len() != expected_len { - return Err(rustls::Error::from(rustls::PeerMisbehaved::InvalidKeyShare)); - } - - Ok(crypto::SharedSecret::from( - self.diffie_hellman(peer_pub_key) - .map_err(|e| { - log_and_map( - "dh::KeyExchange::diffie_hellman", - e, - rustls::PeerMisbehaved::InvalidKeyShare, - ) - })? - .as_ref(), - )) - } - - fn pub_key(&self) -> &[u8] { - self.pub_bytes.as_ref() - } - - fn group(&self) -> rustls::NamedGroup { - match self.key_type { - DhKeyType::FFDHE2048 => rustls::NamedGroup::FFDHE2048, - _ => unimplemented!(), - } - } -} - -#[cfg(test)] -mod tests { - use crate::kx::dh::KeyExchange; - use rustls::crypto::ActiveKeyExchange; - - #[test] - fn test_derive_dh() { - let alice = KeyExchange::generate_ffdhe_2048().unwrap(); - let bob = KeyExchange::generate_ffdhe_2048().unwrap(); - - let shared_secret1 = alice.diffie_hellman(bob.pub_key()).unwrap(); - let shared_secret2 = bob.diffie_hellman(alice.pub_key()).unwrap(); - - assert_eq!(shared_secret1, shared_secret2) - } -} diff --git a/boring-rustls-provider/src/kx/ex.rs b/boring-rustls-provider/src/kx/ex.rs index cd628a5..10fe271 100644 --- a/boring-rustls-provider/src/kx/ex.rs +++ b/boring-rustls-provider/src/kx/ex.rs @@ -1,7 +1,5 @@ -use std::{ - mem::MaybeUninit, - ptr::{self}, -}; +#[cfg(not(feature = "fips"))] +use std::{mem::MaybeUninit, ptr}; use boring::{ ec::{EcGroup, EcKey}, @@ -9,12 +7,16 @@ use boring::{ nid::Nid, pkey::{PKey, PKeyRef, Private}, }; +#[cfg(not(feature = "fips"))] use boring_additions::evp::EvpPkeyCtx; +#[cfg(not(feature = "fips"))] use foreign_types::ForeignType; use rustls::crypto; use spki::der::Decode; -use crate::helper::{cvt, cvt_p, log_and_map}; +use crate::helper::log_and_map; +#[cfg(not(feature = "fips"))] +use crate::helper::{cvt, cvt_p}; use super::DhKeyType; @@ -30,16 +32,11 @@ pub struct KeyExchange { impl KeyExchange { /// Creates a new `KeyExchange` using a random /// private key for the `X25519` Edwards curve + #[cfg(not(feature = "fips"))] pub fn with_x25519() -> Result { Self::ed_from_curve(Nid::from_raw(boring_sys::NID_X25519)) } - /// Creates a new `KeyExchange` using a random - /// private key for the `X448` Edwards curve - pub fn with_x448() -> Result { - Self::ed_from_curve(Nid::from_raw(boring_sys::NID_X448)) - } - /// Creates a new `KeyExchange` using a random /// private key for `sepc256r1` curve /// Also known as `X9_62_PRIME256V1` @@ -53,12 +50,6 @@ impl KeyExchange { Self::ec_from_curve(Nid::SECP384R1) } - /// Creates a new `KeyExchange` using a random - /// private key for `sep521r1` curve - pub fn with_secp521r1() -> Result { - Self::ec_from_curve(Nid::SECP521R1) - } - /// Allows getting a new `KeyExchange` using Eliptic Curves /// on the specified curve fn ec_from_curve(nid: Nid) -> Result { @@ -76,6 +67,7 @@ impl KeyExchange { /// Allows getting a new `KeyExchange` using Edwards Curves /// on the specified curve + #[cfg(not(feature = "fips"))] fn ed_from_curve(nid: Nid) -> Result { let pkey_ctx = unsafe { EvpPkeyCtx::from_ptr(cvt_p(boring_sys::EVP_PKEY_CTX_new_id( @@ -129,8 +121,8 @@ impl KeyExchange { crate::verify::ec::create_public_key(group, point.as_ref())? } + #[cfg(not(feature = "fips"))] DhKeyType::ED(nid) => crate::verify::ed::public_key(peer_pub_key, Nid::from_raw(*nid))?, - _ => unimplemented!(), }; let mut deriver = boring::derive::Deriver::new(&self.own_key)?; @@ -163,12 +155,11 @@ impl crypto::ActiveKeyExchange for KeyExchange { fn group(&self) -> rustls::NamedGroup { match self.key_type { + #[cfg(not(feature = "fips"))] DhKeyType::ED(boring_sys::NID_X25519) => rustls::NamedGroup::X25519, - DhKeyType::ED(boring_sys::NID_X448) => rustls::NamedGroup::X448, DhKeyType::EC((_, boring_sys::NID_X9_62_prime256v1)) => rustls::NamedGroup::secp256r1, DhKeyType::EC((_, boring_sys::NID_secp384r1)) => rustls::NamedGroup::secp384r1, - DhKeyType::EC((_, boring_sys::NID_secp521r1)) => rustls::NamedGroup::secp521r1, - _ => unimplemented!(), + _ => unreachable!("unsupported key type"), } } } @@ -190,6 +181,7 @@ mod tests { } #[test] + #[cfg(not(feature = "fips"))] fn test_derive_ed() { let alice = Box::new(KeyExchange::with_x25519().unwrap()); let bob = KeyExchange::with_x25519().unwrap(); diff --git a/boring-rustls-provider/src/kx/mod.rs b/boring-rustls-provider/src/kx/mod.rs index 9baf602..e38c295 100644 --- a/boring-rustls-provider/src/kx/mod.rs +++ b/boring-rustls-provider/src/kx/mod.rs @@ -2,23 +2,26 @@ use rustls::crypto::{self, ActiveKeyExchange}; use crate::helper::log_and_map; -mod dh; mod ex; #[cfg(feature = "mlkem")] mod pq; #[cfg(feature = "mlkem")] pub(crate) use pq::X25519MlKem768; +/// Key type discriminant used by [`ex::KeyExchange`] to select the +/// appropriate peer key parsing and DH derivation logic. enum DhKeyType { EC((boring::ec::EcGroup, i32)), + #[cfg(not(feature = "fips"))] ED(i32), - FFDHE2048, } /// A X25519-based key exchange +#[cfg(not(feature = "fips"))] #[derive(Debug)] pub struct X25519; +#[cfg(not(feature = "fips"))] impl crypto::SupportedKxGroup for X25519 { fn start(&self) -> Result, rustls::Error> { Ok(Box::new(ex::KeyExchange::with_x25519().map_err(|e| { @@ -31,22 +34,6 @@ impl crypto::SupportedKxGroup for X25519 { } } -/// A X448-based key exchange -#[derive(Debug)] -pub struct X448; - -impl crypto::SupportedKxGroup for X448 { - fn start(&self) -> Result, rustls::Error> { - Ok(Box::new(ex::KeyExchange::with_x448().map_err(|e| { - log_and_map("X448.start", e, crypto::GetRandomFailed) - })?)) - } - - fn name(&self) -> rustls::NamedGroup { - rustls::NamedGroup::X448 - } -} - /// A secp256r1-based key exchange #[derive(Debug)] pub struct Secp256r1; @@ -78,35 +65,3 @@ impl crypto::SupportedKxGroup for Secp384r1 { rustls::NamedGroup::secp384r1 } } - -/// A secp521r1-based key exchange -#[derive(Debug)] -pub struct Secp521r1; - -impl crypto::SupportedKxGroup for Secp521r1 { - fn start(&self) -> Result, rustls::Error> { - Ok(Box::new(ex::KeyExchange::with_secp521r1().map_err( - |e| log_and_map("Secp521r1.start", e, crypto::GetRandomFailed), - )?)) - } - - fn name(&self) -> rustls::NamedGroup { - rustls::NamedGroup::secp521r1 - } -} - -/// A ffedhe2048-based key exchange -#[derive(Debug)] -pub struct FfDHe2048; - -impl crypto::SupportedKxGroup for FfDHe2048 { - fn start(&self) -> Result, rustls::Error> { - Ok(Box::new(dh::KeyExchange::generate_ffdhe_2048().map_err( - |e| log_and_map("FfDHe2048.start", e, crypto::GetRandomFailed), - )?)) - } - - fn name(&self) -> rustls::NamedGroup { - rustls::NamedGroup::FFDHE2048 - } -} diff --git a/boring-rustls-provider/src/lib.rs b/boring-rustls-provider/src/lib.rs index fe822f6..6908297 100644 --- a/boring-rustls-provider/src/lib.rs +++ b/boring-rustls-provider/src/lib.rs @@ -66,7 +66,7 @@ impl rustls::crypto::KeyProvider for Provider { } } -#[allow(unused)] +#[cfg(feature = "fips")] static ALL_FIPS_CIPHER_SUITES: &[SupportedCipherSuite] = &[ SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256), @@ -80,7 +80,7 @@ static ALL_FIPS_CIPHER_SUITES: &[SupportedCipherSuite] = &[ SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256), ]; -#[allow(unused)] +#[cfg(not(feature = "fips"))] static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[ SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256), SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), @@ -102,47 +102,28 @@ static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[ /// Allowed KX groups for FIPS per [SP 800-52r2](https://doi.org/10.6028/NIST.SP.800-52r2), /// aligned with boring's `fips202205` compliance policy. /// -/// X25519MLKEM768 is preferred when the `mlkem` feature is enabled. -/// The `fips` feature implies `mlkem`, so the PQ hybrid is always -/// available in FIPS mode. -#[cfg(feature = "mlkem")] -#[allow(unused)] +/// The `fips` feature implies `mlkem`, so X25519MLKEM768 is always +/// available and preferred in FIPS mode. +#[cfg(feature = "fips")] static ALL_FIPS_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ &kx::X25519MlKem768 as _, // PQ hybrid preferred &kx::Secp256r1 as _, // P-256 &kx::Secp384r1 as _, // P-384 ]; -/// See [`ALL_FIPS_KX_GROUPS`] (mlkem variant). -#[cfg(not(feature = "mlkem"))] -#[allow(unused)] -static ALL_FIPS_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ - &kx::Secp256r1 as _, // P-256 - &kx::Secp384r1 as _, // P-384 -]; - /// All supported KX groups, ordered by preference. -/// Matches boring's default group preference order. -#[cfg(feature = "mlkem")] -#[allow(unused)] +/// +/// Matches boring's default supported group list exactly: +/// X25519MLKEM768 (when mlkem enabled), X25519, P-256, P-384. +#[cfg(all(not(feature = "fips"), feature = "mlkem"))] static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ &kx::X25519MlKem768 as _, // PQ hybrid preferred &kx::X25519 as _, - &kx::X448 as _, &kx::Secp256r1 as _, &kx::Secp384r1 as _, - &kx::Secp521r1 as _, - &kx::FfDHe2048 as _, ]; /// See [`ALL_KX_GROUPS`] (mlkem variant). -#[cfg(not(feature = "mlkem"))] -#[allow(unused)] -static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ - &kx::X25519 as _, - &kx::Secp256r1 as _, - &kx::Secp384r1 as _, - &kx::Secp521r1 as _, - &kx::X448 as _, - &kx::FfDHe2048 as _, -]; +#[cfg(not(any(feature = "fips", feature = "mlkem")))] +static ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = + &[&kx::X25519 as _, &kx::Secp256r1 as _, &kx::Secp384r1 as _]; diff --git a/boring-rustls-provider/tests/e2e.rs b/boring-rustls-provider/tests/e2e.rs index 68aadb1..fa5bd5a 100644 --- a/boring-rustls-provider/tests/e2e.rs +++ b/boring-rustls-provider/tests/e2e.rs @@ -22,7 +22,13 @@ async fn test_tls13_crypto() { let root_store = pki.client_root_store(); let server_config = pki.server_config(); - let ciphers = [ + #[cfg(feature = "fips")] + let ciphers = vec![ + SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256), + SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), + ]; + #[cfg(not(feature = "fips"))] + let ciphers = vec![ SupportedCipherSuite::Tls13(&tls13::AES_128_GCM_SHA256), SupportedCipherSuite::Tls13(&tls13::AES_256_GCM_SHA384), SupportedCipherSuite::Tls13(&tls13::CHACHA20_POLY1305_SHA256), @@ -324,12 +330,12 @@ fn non_fips_provider_includes_pq_group() { assert_eq!( groups[1], NamedGroup::X25519, - "X25519 should remain the first classical fallback" + "X25519 should be the first classical fallback" ); assert_eq!( groups[2], - NamedGroup::X448, - "X448 should remain ahead of NIST P-curves in non-FIPS mode" + NamedGroup::secp256r1, + "P-256 should follow X25519, matching boring's default order" ); } @@ -341,7 +347,13 @@ async fn test_tls12_ec_crypto() { let root_store = pki.client_root_store(); let server_config = pki.server_config(); - let ciphers = [ + #[cfg(feature = "fips")] + let ciphers = vec![ + SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256), + SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384), + ]; + #[cfg(not(feature = "fips"))] + let ciphers = vec![ SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES128_GCM_SHA256), SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_AES256_GCM_SHA384), SupportedCipherSuite::Tls12(&tls12::ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256), @@ -368,7 +380,13 @@ async fn test_tls12_rsa_crypto() { let root_store = pki.client_root_store(); let server_config = pki.server_config(); - let ciphers = [ + #[cfg(feature = "fips")] + let ciphers = vec![ + SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256), + SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES256_GCM_SHA384), + ]; + #[cfg(not(feature = "fips"))] + let ciphers = vec![ SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES128_GCM_SHA256), SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_AES256_GCM_SHA384), SupportedCipherSuite::Tls12(&tls12::ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256),