From 2007866a6afea8bbb096b3418a2ad54f219bcf2f Mon Sep 17 00:00:00 2001 From: Reda Chouk Date: Fri, 23 Oct 2026 18:49:41 +0200 Subject: [PATCH] Support the FIPS v5.2.3 module: leave out Ed25519 and adjust tests to its limits --- rustls-wolfcrypt-provider/build.rs | 6 ++ .../src/aead/aes128gcm.rs | 2 +- .../src/aead/aes256gcm.rs | 2 +- .../src/aead/chacha20.rs | 2 +- rustls-wolfcrypt-provider/src/aead/quic.rs | 25 ++++---- rustls-wolfcrypt-provider/src/hkdf.rs | 19 +++++- rustls-wolfcrypt-provider/src/lib.rs | 3 + rustls-wolfcrypt-provider/src/verify.rs | 4 ++ rustls-wolfcrypt-provider/tests/e2e.rs | 64 ++++++++++++++----- 9 files changed, 93 insertions(+), 34 deletions(-) diff --git a/rustls-wolfcrypt-provider/build.rs b/rustls-wolfcrypt-provider/build.rs index 32948d0..864546f 100644 --- a/rustls-wolfcrypt-provider/build.rs +++ b/rustls-wolfcrypt-provider/build.rs @@ -7,4 +7,10 @@ fn main() { if std::env::var("DEP_WOLFSSL_FIPS").as_deref() == Ok("1") { println!("cargo::rustc-cfg=fips"); } + // The FIPS v5.2.3 module has no Ed25519, published as FIPS_V5_2_3=1. + println!("cargo::rustc-check-cfg=cfg(fips_v5_2_3)"); + println!("cargo::rerun-if-env-changed=DEP_WOLFSSL_FIPS_V5_2_3"); + if std::env::var("DEP_WOLFSSL_FIPS_V5_2_3").as_deref() == Ok("1") { + println!("cargo::rustc-cfg=fips_v5_2_3"); + } } diff --git a/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs b/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs index 948fc89..24222bc 100644 --- a/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs +++ b/rustls-wolfcrypt-provider/src/aead/aes128gcm.rs @@ -389,7 +389,7 @@ mod tests { match &test.result { TestResult::Invalid => { - if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + if test.flags.contains(&TestFlag::ModifiedTag) { assert_ne!( actual_tag[..], test.tag[..], diff --git a/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs b/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs index 42185f3..566b3ba 100644 --- a/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs +++ b/rustls-wolfcrypt-provider/src/aead/aes256gcm.rs @@ -395,7 +395,7 @@ mod tests { match &test.result { TestResult::Invalid => { - if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + if test.flags.contains(&TestFlag::ModifiedTag) { assert_ne!( actual_tag[..], test.tag[..], diff --git a/rustls-wolfcrypt-provider/src/aead/chacha20.rs b/rustls-wolfcrypt-provider/src/aead/chacha20.rs index 577e3e8..194630b 100644 --- a/rustls-wolfcrypt-provider/src/aead/chacha20.rs +++ b/rustls-wolfcrypt-provider/src/aead/chacha20.rs @@ -377,7 +377,7 @@ mod tests { match &test.result { TestResult::Invalid => { - if test.flags.iter().any(|flag| *flag == TestFlag::ModifiedTag) { + if test.flags.contains(&TestFlag::ModifiedTag) { assert_ne!( actual_tag[..], test.tag[..], diff --git a/rustls-wolfcrypt-provider/src/aead/quic.rs b/rustls-wolfcrypt-provider/src/aead/quic.rs index ad02a54..8132ece 100644 --- a/rustls-wolfcrypt-provider/src/aead/quic.rs +++ b/rustls-wolfcrypt-provider/src/aead/quic.rs @@ -125,6 +125,8 @@ fn init_hp_aes_cipher(key: &[u8]) -> Result { } fn generate_mask_aes(hp_cipher: &Cipher, sample: &[u8]) -> Result<[u8; MASK_LEN], Error> { + // FIPS builds have AES only, so the match has one arm there. + #[cfg_attr(fips, allow(clippy::infallible_destructuring_match))] let aes_cipher = match hp_cipher { Cipher::Aes(c) => c, #[cfg(not(fips))] @@ -346,6 +348,7 @@ fn encrypt_aes_gcm( aad: &[u8], in_out: &mut [u8], ) -> Result { + #[cfg_attr(fips, allow(clippy::infallible_destructuring_match))] let aes_cipher = match packet_cipher { Cipher::Aes(c) => c, #[cfg(not(fips))] @@ -360,6 +363,7 @@ pub(super) fn decrypt_aes_gcm( aad: &[u8], in_out: &mut [u8], ) -> Result<(), Error> { + #[cfg_attr(fips, allow(clippy::infallible_destructuring_match))] let aes_cipher = match packet_cipher { Cipher::Aes(aes_key) => aes_key, #[cfg(not(fips))] @@ -832,12 +836,11 @@ mod tests { let root_store = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); - let config = rustls::ClientConfig::builder_with_provider(default_provider().into()) + rustls::ClientConfig::builder_with_provider(default_provider().into()) .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_store) - .with_no_client_auth(); - config + .with_no_client_auth() } fn make_default_server_config() -> ServerConfig { @@ -970,7 +973,7 @@ mod tests { rustls::ProtocolVersion::TLSv1_2.encode(&mut ch); ch.extend_from_slice(&[0u8; 32]); // Encode random ch.extend_from_slice(&[0u8; 1]); // Encode session_id - vec![ + [ rustls::CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, rustls::CipherSuite::TLS13_AES_128_GCM_SHA256, ] @@ -1023,7 +1026,7 @@ mod tests { #[cfg(not(fips))] fn hkdf_expand_label( - expander: &Box, + expander: &dyn HkdfExpander, label: &[u8], context: &[u8], n: usize, @@ -1075,7 +1078,7 @@ mod tests { .expander_for_okm(&secret); //Derive hp key hkdf_expand_label( - &expander, + &*expander, match version { rustls::quic::Version::V1Draft | rustls::quic::Version::V1 => b"quic hp", rustls::quic::Version::V2 => b"quicv2 hp", @@ -1086,7 +1089,7 @@ mod tests { &mut output, ); - let hp_aead_key = AeadKey::from(output.clone()); + let hp_aead_key = AeadKey::from(output); let header_protection_key = TLS13_CHACHA20_POLY1305_SHA256 .tls13() .unwrap() @@ -1096,7 +1099,7 @@ mod tests { // Derive packet protection key and iv hkdf_expand_label( - &expander, + &*expander, match version { rustls::quic::Version::V1Draft | rustls::quic::Version::V1 => b"quic key", rustls::quic::Version::V2 => b"quicv2 key", @@ -1110,7 +1113,7 @@ mod tests { let pkt_aead_key = AeadKey::from(output); hkdf_expand_label( - &expander, + &*expander, match version { rustls::quic::Version::V1Draft | rustls::quic::Version::V1 => b"quic iv", rustls::quic::Version::V2 => b"quicv2 iv", @@ -1392,7 +1395,7 @@ mod tests { let header_len = PLAIN_HEADER.len(); let tag_len = client_keys.local.packet.tag_len(); let padding_len = 1200 - header_len - PAYLOAD.len() - tag_len; - buf.extend(std::iter::repeat(0).take(padding_len)); + buf.extend(std::iter::repeat_n(0, padding_len)); let (header, payload) = buf.split_at_mut(header_len); let tag = client_keys .local @@ -1610,7 +1613,7 @@ mod tests { #[cfg(not(fips))] &aead::quic::CHACHA20, ]; - let mut first = vec![0u8; 1]; + let mut first = [0u8; 1]; let mut packet_number = vec![0u8; 4]; for alg in hp_algs { let key_len = alg.key_len(); diff --git a/rustls-wolfcrypt-provider/src/hkdf.rs b/rustls-wolfcrypt-provider/src/hkdf.rs index 8616678..a2073ba 100644 --- a/rustls-wolfcrypt-provider/src/hkdf.rs +++ b/rustls-wolfcrypt-provider/src/hkdf.rs @@ -137,7 +137,9 @@ mod tests { /// Tests the HKDF implementation against RFC 5869 test vector A.1 /// This is the primary compliance test using SHA-256 + /// Its 13 byte salt is under FIPS v5.2.3's 14 byte HMAC key minimum. #[test] + #[cfg(not(fips_v5_2_3))] fn test_hkdf_sha256() { // Test vectors from RFC 5869 Appendix A.1 let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); @@ -164,7 +166,8 @@ mod tests { fn test_hkdf_sha384() { // Test with SHA384 let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"); - let salt = hex!("000102030405060708090a0b0c"); + // At least 14 bytes, FIPS v5.2.3's HMAC key minimum. + let salt = hex!("000102030405060708090a0b0c0d0e0f"); let info = hex!("f0f1f2f3f4f5f6f7f8f9"); let hkdf = WCHkdfUsingHmac(WCShaHmac::Sha384); @@ -199,7 +202,8 @@ mod tests { #[test] fn test_hkdf_zero_ikm() { let hkdf = WCHkdfUsingHmac(WCShaHmac::Sha256); - let salt = hex!("000102030405060708090a0b0c"); + // At least 14 bytes, FIPS v5.2.3's HMAC key minimum. + let salt = hex!("000102030405060708090a0b0c0d0e0f"); let info = hex!("f0f1f2f3f4f5f6f7f8f9"); let expander = hkdf.extract_from_zero_ikm(Some(&salt)); @@ -220,7 +224,8 @@ mod tests { #[test] fn test_hkdf_multiple_info_components() { let hkdf = WCHkdfUsingHmac(WCShaHmac::Sha256); - let salt = hex!("000102030405060708090a0b0c"); + // At least 14 bytes, FIPS v5.2.3's HMAC key minimum. + let salt = hex!("000102030405060708090a0b0c0d0e0f"); let info1 = hex!("f0f1f2f3"); let info2 = hex!("f4f5f6f7"); let info3 = hex!("f8f9"); @@ -266,6 +271,10 @@ mod tests { for test_group in test_groups { let tests = &test_group.tests; for test in tests { + // FIPS v5.2.3 refuses HMAC keys, so HKDF salts, under 14 bytes. + if cfg!(fips_v5_2_3) && test.salt.len() < 14 { + continue; + } let pseudorandom_key_expander = hkdf_provider.extract_from_secret(Some(&test.salt), &test.ikm); let mut outputkey_material = vec![0; test.size]; @@ -307,6 +316,10 @@ mod tests { for test_group in test_groups { let tests = &test_group.tests; for test in tests { + // FIPS v5.2.3 refuses HMAC keys, so HKDF salts, under 14 bytes. + if cfg!(fips_v5_2_3) && test.salt.len() < 14 { + continue; + } let pseudorandom_key_expander = hkdf_provider.extract_from_secret(Some(&test.salt), &test.ikm); let mut outputkey_material = vec![0; test.size]; diff --git a/rustls-wolfcrypt-provider/src/lib.rs b/rustls-wolfcrypt-provider/src/lib.rs index c362c97..0f20795 100644 --- a/rustls-wolfcrypt-provider/src/lib.rs +++ b/rustls-wolfcrypt-provider/src/lib.rs @@ -28,6 +28,7 @@ pub mod aead { } pub mod sign { pub mod ecdsa; + #[cfg(not(fips_v5_2_3))] pub mod eddsa; pub mod rsa; } @@ -137,6 +138,7 @@ impl rustls::crypto::KeyProvider for Provider { let algorithms: SigningAlgorithms = vec![ Box::new(|key| sign::ecdsa::EcdsaSigningKey::try_from(key).map(|x| Arc::new(x) as _)), Box::new(|key| sign::rsa::RsaPrivateKey::try_from(key).map(|x| Arc::new(x) as _)), + #[cfg(not(fips_v5_2_3))] Box::new(|key| sign::eddsa::Ed25519PrivateKey::try_from(key).map(|x| Arc::new(x) as _)), ]; @@ -182,6 +184,7 @@ static ALL_ECDSA_SCHEMES: &[rustls::SignatureScheme] = &[ rustls::SignatureScheme::ECDSA_NISTP256_SHA256, rustls::SignatureScheme::ECDSA_NISTP384_SHA384, rustls::SignatureScheme::ECDSA_NISTP521_SHA512, + #[cfg(not(fips_v5_2_3))] rustls::SignatureScheme::ED25519, ]; diff --git a/rustls-wolfcrypt-provider/src/verify.rs b/rustls-wolfcrypt-provider/src/verify.rs index df88ff1..7e5c592 100644 --- a/rustls-wolfcrypt-provider/src/verify.rs +++ b/rustls-wolfcrypt-provider/src/verify.rs @@ -4,6 +4,7 @@ use rustls::SignatureScheme; use wolfssl_wolfcrypt::sha::{SHA256, SHA384, SHA512}; mod ecdsa; +#[cfg(not(fips_v5_2_3))] pub mod eddsa; mod rsapkcs1; mod rsapss; @@ -61,6 +62,7 @@ pub static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { ECDSA_P256_SHA256, ECDSA_P384_SHA384, ECDSA_P521_SHA512, + #[cfg(not(fips_v5_2_3))] ED25519, ], mapping: &[ @@ -73,6 +75,7 @@ pub static ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { (SignatureScheme::ECDSA_NISTP256_SHA256, &[ECDSA_P256_SHA256]), (SignatureScheme::ECDSA_NISTP384_SHA384, &[ECDSA_P384_SHA384]), (SignatureScheme::ECDSA_NISTP521_SHA512, &[ECDSA_P521_SHA512]), + #[cfg(not(fips_v5_2_3))] (SignatureScheme::ED25519, &[ED25519]), ], }; @@ -86,4 +89,5 @@ static RSA_PKCS1_SHA512: &dyn SignatureVerificationAlgorithm = &rsapkcs1::RsaPkc static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &ecdsa::EcdsaVerifier::P256_SHA256; static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &ecdsa::EcdsaVerifier::P384_SHA384; static ECDSA_P521_SHA512: &dyn SignatureVerificationAlgorithm = &ecdsa::EcdsaVerifier::P521_SHA512; +#[cfg(not(fips_v5_2_3))] static ED25519: &dyn SignatureVerificationAlgorithm = &eddsa::Ed25519; diff --git a/rustls-wolfcrypt-provider/tests/e2e.rs b/rustls-wolfcrypt-provider/tests/e2e.rs index 27d4478..2d6afea 100644 --- a/rustls-wolfcrypt-provider/tests/e2e.rs +++ b/rustls-wolfcrypt-provider/tests/e2e.rs @@ -25,6 +25,7 @@ use std::sync::Once; use std::sync::{Arc, Mutex}; use std::thread; use wolfssl_wolfcrypt::ecc::ECC; +#[cfg(not(fips_v5_2_3))] use wolfssl_wolfcrypt::ed25519::Ed25519; use wolfssl_wolfcrypt::random::RNG; use wolfssl_wolfcrypt::sys::*; @@ -436,6 +437,7 @@ mod tests { /// DER-encodes an Ed25519 private key. The crate has no wrapper for /// `wc_Ed25519PrivateKeyToDer`, so this uses the raw bindings. + #[cfg(not(fips_v5_2_3))] fn ed25519_private_key_to_der(priv_key: &[u8], pub_key: &[u8]) -> Vec { let mut der = vec![0u8; 200]; // Adjust size if needed unsafe { @@ -449,7 +451,7 @@ mod tests { &mut key, ); let der_len = if ret == 0 { - wc_Ed25519PrivateKeyToDer(&mut key, der.as_mut_ptr(), der.len() as word32) + wc_Ed25519PrivateKeyToDer(&key, der.as_mut_ptr(), der.len() as word32) } else { ret }; @@ -488,6 +490,11 @@ mod tests { let rng = RNG::new().unwrap(); let mut key = ECC::generate_ex(key_size as i32, &rng, curve_id, None, None).unwrap(); + // FIPS v5.2.3 locks the public key export too, not only the + // private one. + #[cfg(fips)] + let _read = wolfssl_wolfcrypt::fips::PrivateKeyRead::enable().unwrap(); + // Export public key (0x04 || X || Y) let mut pub_key_bytes = vec![0u8; 1 + 2 * key_size]; let pub_len = key.export_x963(&mut pub_key_bytes).unwrap(); @@ -495,8 +502,6 @@ mod tests { // Export private key in DER format let mut priv_key_raw = vec![0u8; key_size]; - #[cfg(fips)] - let _read = wolfssl_wolfcrypt::fips::PrivateKeyRead::enable().unwrap(); key.export_private(&mut priv_key_raw).unwrap(); let der_ecc_key = ecc_private_key_to_der(&priv_key_raw, &pub_key_bytes, curve_id); @@ -525,6 +530,7 @@ mod tests { } #[test] + #[cfg(not(fips_v5_2_3))] fn eddsa_sign_and_verify() { let wolfcrypt_default_provider = rustls_wolfcrypt_provider::default_provider(); @@ -572,15 +578,23 @@ mod tests { .collect(); test_cases.par_iter().for_each(|&(scheme, key_size)| { - generate_and_test_rsa_pkcs8_key(&wolfcrypt_default_provider, scheme, key_size).expect( - &format!("Failed for scheme {:?} with key size {}", scheme, key_size), - ); + generate_and_test_rsa_pkcs8_key(&wolfcrypt_default_provider, scheme, key_size) + .unwrap_or_else(|e| { + panic!( + "Failed for scheme {:?} with key size {}, {:?}", + scheme, key_size, e + ) + }); }); test_cases.par_iter().for_each(|&(scheme, key_size)| { - generate_and_test_rsa_pkcs1_key(&wolfcrypt_default_provider, scheme, key_size).expect( - &format!("Failed for scheme {:?} with key size {}", scheme, key_size), - ); + generate_and_test_rsa_pkcs1_key(&wolfcrypt_default_provider, scheme, key_size) + .unwrap_or_else(|e| { + panic!( + "Failed for scheme {:?} with key size {}, {:?}", + scheme, key_size, e + ) + }); }); } @@ -601,7 +615,11 @@ mod tests { let rng_object = WCRngObject::new(&mut rng_c_type); rng_object.init().unwrap(); - unsafe { wc_RsaSetRNG(rsa_key_object.as_ptr(), rng_object.as_ptr()) }; + // FIPS v5.2.3 does not export wc_RsaSetRNG(). + #[cfg(not(fips_v5_2_3))] + unsafe { + wc_RsaSetRNG(rsa_key_object.as_ptr(), rng_object.as_ptr()) + }; let ret = unsafe { wc_MakeRsaKey( @@ -661,15 +679,23 @@ mod tests { .collect(); test_cases.par_iter().for_each(|&(scheme, key_size)| { - generate_and_test_rsa_pkcs1_key(&wolfcrypt_default_provider, scheme, key_size).expect( - &format!("Failed for scheme {:?} with key size {}", scheme, key_size), - ); + generate_and_test_rsa_pkcs1_key(&wolfcrypt_default_provider, scheme, key_size) + .unwrap_or_else(|e| { + panic!( + "Failed for scheme {:?} with key size {}, {:?}", + scheme, key_size, e + ) + }); }); test_cases.par_iter().for_each(|&(scheme, key_size)| { - generate_and_test_rsa_pkcs8_key(&wolfcrypt_default_provider, scheme, key_size).expect( - &format!("Failed for scheme {:?} with key size {}", scheme, key_size), - ); + generate_and_test_rsa_pkcs8_key(&wolfcrypt_default_provider, scheme, key_size) + .unwrap_or_else(|e| { + panic!( + "Failed for scheme {:?} with key size {}, {:?}", + scheme, key_size, e + ) + }); }); } @@ -701,7 +727,11 @@ mod tests { let rng_object = WCRngObject::new(&mut rng_c_type); rng_object.init().unwrap(); - unsafe { wc_RsaSetRNG(rsa_key_object.as_ptr(), rng_object.as_ptr()) }; + // FIPS v5.2.3 does not export wc_RsaSetRNG(). + #[cfg(not(fips_v5_2_3))] + unsafe { + wc_RsaSetRNG(rsa_key_object.as_ptr(), rng_object.as_ptr()) + }; ret = unsafe { wc_MakeRsaKey(