From 327444fdb8d8bfd74fe88533ed2b0d7fa8cfcfcb Mon Sep 17 00:00:00 2001 From: Joseph Birr-Pixton Date: Fri, 6 Oct 2023 12:56:55 +0100 Subject: [PATCH] Expose FIPS "service indicator" This means a `ClientConfig` and `ServerConfig` can be asked whether it is in fips mode, and it answers by asking the same of all its constituent cryptography. --- rustls/src/client/client_conn.rs | 6 +++++ rustls/src/crypto/aws_lc_rs/mod.rs | 29 +++++++++++++++++++++--- rustls/src/crypto/aws_lc_rs/tls12.rs | 12 ++++++++++ rustls/src/crypto/aws_lc_rs/tls13.rs | 16 ++++++++++++++ rustls/src/crypto/cipher.rs | 10 +++++++++ rustls/src/crypto/hash.rs | 5 +++++ rustls/src/crypto/hmac.rs | 5 +++++ rustls/src/crypto/mod.rs | 33 ++++++++++++++++++++++++++++ rustls/src/crypto/ring/hash.rs | 4 ++++ rustls/src/crypto/ring/hmac.rs | 4 ++++ rustls/src/crypto/ring/kx.rs | 4 ++++ rustls/src/crypto/ring/mod.rs | 4 ++++ rustls/src/crypto/ring/quic.rs | 4 ++++ rustls/src/crypto/ring/tls12.rs | 8 +++++++ rustls/src/crypto/ring/tls13.rs | 16 ++++++++++++++ rustls/src/crypto/tls12.rs | 5 +++++ rustls/src/crypto/tls13.rs | 5 +++++ rustls/src/quic.rs | 5 +++++ rustls/src/server/server_conn.rs | 6 +++++ rustls/src/suites.rs | 18 +++++++++++++++ rustls/src/tls12/mod.rs | 7 ++++++ rustls/src/tls13/mod.rs | 13 +++++++++++ rustls/tests/api.rs | 24 ++++++++++++++++++++ 23 files changed, 240 insertions(+), 3 deletions(-) diff --git a/rustls/src/client/client_conn.rs b/rustls/src/client/client_conn.rs index dd11959f..f39c1017 100644 --- a/rustls/src/client/client_conn.rs +++ b/rustls/src/client/client_conn.rs @@ -284,6 +284,12 @@ impl ClientConfig { } } + /// Return true if connections made with this `ClientConfig` will + /// operate in FIPS mode. + pub fn fips_mode(&self) -> bool { + self.provider.fips_mode() + } + /// We support a given TLS version if it's quoted in the configured /// versions *and* at least one ciphersuite for this version is /// also configured. diff --git a/rustls/src/crypto/aws_lc_rs/mod.rs b/rustls/src/crypto/aws_lc_rs/mod.rs index 95fcdff5..1b864f36 100644 --- a/rustls/src/crypto/aws_lc_rs/mod.rs +++ b/rustls/src/crypto/aws_lc_rs/mod.rs @@ -36,8 +36,17 @@ pub(crate) mod tls13; /// A `CryptoProvider` backed by aws-lc-rs. pub fn default_provider() -> CryptoProvider { CryptoProvider { - cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(), - kx_groups: ALL_KX_GROUPS.to_vec(), + // TODO: make this filtering conditional on fips feature + cipher_suites: DEFAULT_CIPHER_SUITES + .iter() + .filter(|cs| cs.fips_mode()) + .copied() + .collect(), + kx_groups: ALL_KX_GROUPS + .iter() + .filter(|kx| kx.fips_mode()) + .copied() + .collect(), signature_verification_algorithms: SUPPORTED_SIG_ALGS, secure_random: &AwsLcRs, key_provider: &AwsLcRs, @@ -55,6 +64,10 @@ impl SecureRandom for AwsLcRs { .fill(buf) .map_err(|_| GetRandomFailed) } + + fn fips_mode(&self) -> bool { + aws_lc_rs::try_fips_mode().is_ok() + } } impl KeyProvider for AwsLcRs { @@ -64,6 +77,10 @@ impl KeyProvider for AwsLcRs { ) -> Result, Error> { sign::any_supported_type(&key_der) } + + fn fips_mode(&self) -> bool { + aws_lc_rs::try_fips_mode().is_ok() + } } /// The cipher suite configuration that an application should use by default. @@ -207,4 +224,10 @@ mod ring_shim { } /// AEAD algorithm that is used by `mod ticketer`. -pub(super) static TICKETER_AEAD: &'static ring_like::aead::Algorithm = &ring_like::aead::AES_256_GCM; +pub(super) static TICKETER_AEAD: &'static ring_like::aead::Algorithm = + &ring_like::aead::AES_256_GCM; + +/// Are we in FIPS mode? +pub(super) fn fips_mode() -> bool { + aws_lc_rs::try_fips_mode().is_ok() +} diff --git a/rustls/src/crypto/aws_lc_rs/tls12.rs b/rustls/src/crypto/aws_lc_rs/tls12.rs index 66d90611..28f5cf53 100644 --- a/rustls/src/crypto/aws_lc_rs/tls12.rs +++ b/rustls/src/crypto/aws_lc_rs/tls12.rs @@ -188,6 +188,10 @@ impl Tls12AeadAlgorithm for GcmAlgorithm { iv: gcm_iv(write_iv, explicit), }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } pub(crate) struct ChaCha20Poly1305; @@ -234,6 +238,10 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { iv: Iv::new(iv[..].try_into().unwrap()), }) } + + fn fips_mode(&self) -> bool { + false // not FIPS approved + } } /// A `MessageEncrypter` for AES-GCM AEAD ciphersuites. TLS 1.2 only. @@ -433,4 +441,8 @@ impl Prf for Tls12Prf { ); Ok(()) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } diff --git a/rustls/src/crypto/aws_lc_rs/tls13.rs b/rustls/src/crypto/aws_lc_rs/tls13.rs index a1b750cf..840ed761 100644 --- a/rustls/src/crypto/aws_lc_rs/tls13.rs +++ b/rustls/src/crypto/aws_lc_rs/tls13.rs @@ -106,6 +106,10 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { ) -> Result { Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) } + + fn fips_mode(&self) -> bool { + false // not FIPS approved + } } struct Aes256GcmAead(AeadAlgorithm); @@ -130,6 +134,10 @@ impl Tls13AeadAlgorithm for Aes256GcmAead { ) -> Result { Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct Aes128GcmAead(AeadAlgorithm); @@ -154,6 +162,10 @@ impl Tls13AeadAlgorithm for Aes128GcmAead { ) -> Result { Ok(ConnectionTrafficSecrets::Aes128Gcm { key, iv }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } // common encrypter/decrypter/key_len items for above Tls13AeadAlgorithm impls @@ -346,6 +358,10 @@ impl Hkdf for RingHkdf { fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> crypto::hmac::Tag { crypto::hmac::Tag::new(hmac::sign(&hmac::Key::new(self.1, key.as_ref()), message).as_ref()) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct RingHkdfExpander { diff --git a/rustls/src/crypto/cipher.rs b/rustls/src/crypto/cipher.rs index d1ac707c..e8da19ab 100644 --- a/rustls/src/crypto/cipher.rs +++ b/rustls/src/crypto/cipher.rs @@ -31,6 +31,11 @@ pub trait Tls13AeadAlgorithm: Send + Sync { key: AeadKey, iv: Iv, ) -> Result; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// Factory trait for building `MessageEncrypter` and `MessageDecrypter` for a TLS1.2 cipher suite. @@ -72,6 +77,11 @@ pub trait Tls12AeadAlgorithm: Send + Sync + 'static { iv: &[u8], explicit: &[u8], ) -> Result; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// An error indicating that the AEAD algorithm does not support the requested operation. diff --git a/rustls/src/crypto/hash.rs b/rustls/src/crypto/hash.rs index 7f645f54..1b47436a 100644 --- a/rustls/src/crypto/hash.rs +++ b/rustls/src/crypto/hash.rs @@ -18,6 +18,11 @@ pub trait Hash: Send + Sync { /// Which hash function this is, eg, `HashAlgorithm::SHA256`. fn algorithm(&self) -> HashAlgorithm; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// A hash output, stored as a value. diff --git a/rustls/src/crypto/hmac.rs b/rustls/src/crypto/hmac.rs index 16cca43a..ceab9abf 100644 --- a/rustls/src/crypto/hmac.rs +++ b/rustls/src/crypto/hmac.rs @@ -12,6 +12,11 @@ pub trait Hmac: Send + Sync { /// Give the length of the underlying hash function. In RFC2104 terminology this is `L`. fn hash_output_len(&self) -> usize; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// A HMAC tag, stored as a value. diff --git a/rustls/src/crypto/mod.rs b/rustls/src/crypto/mod.rs index 563005e5..2a8829b1 100644 --- a/rustls/src/crypto/mod.rs +++ b/rustls/src/crypto/mod.rs @@ -186,6 +186,21 @@ pub struct CryptoProvider { pub key_provider: &'static dyn KeyProvider, } +impl CryptoProvider { + /// Returns if this `CryptoProvider` is operating in FIPS mode. + pub fn fips_mode(&self) -> bool { + self.cipher_suites + .iter() + .all(|cs| cs.fips_mode()) + && self + .kx_groups + .iter() + .all(|kx| kx.fips_mode()) + && self.secure_random.fips_mode() + && self.key_provider.fips_mode() + } +} + /// A source of cryptographically secure randomness. pub trait SecureRandom: Send + Sync + Debug { /// Fill the given buffer with random bytes. @@ -199,6 +214,11 @@ pub trait SecureRandom: Send + Sync + Debug { /// an ephemeral key exchange key, but this is not included in the interface with /// rustls: it is assumed that the cryptography library provides for this itself. fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed>; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// A mechanism for loading private [SigningKey]s from [PrivateKeyDer]. @@ -214,6 +234,14 @@ pub trait KeyProvider: Send + Sync + Debug { &self, key_der: PrivateKeyDer<'static>, ) -> Result, Error>; + + /// Return true if this is backed by a FIPS-approved implementation. + /// + /// If this returns true, that must be the case for all possible key types + /// supported by [`load_private_key()`]. + fn fips_mode(&self) -> bool { + false + } } /// A supported key exchange group. @@ -240,6 +268,11 @@ pub trait SupportedKxGroup: Send + Sync + Debug { /// If the `NamedGroup` enum does not have a name for the algorithm you are implementing, /// you can use [`NamedGroup::Unknown`]. fn name(&self) -> NamedGroup; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// An in-progress key exchange originating from a [`SupportedKxGroup`]. diff --git a/rustls/src/crypto/ring/hash.rs b/rustls/src/crypto/ring/hash.rs index f97c1011..cc619815 100644 --- a/rustls/src/crypto/ring/hash.rs +++ b/rustls/src/crypto/ring/hash.rs @@ -29,6 +29,10 @@ impl crypto::hash::Hash for Hash { fn algorithm(&self) -> HashAlgorithm { self.1 } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct Context(digest::Context); diff --git a/rustls/src/crypto/ring/hmac.rs b/rustls/src/crypto/ring/hmac.rs index 47c7ec50..58c1bd56 100644 --- a/rustls/src/crypto/ring/hmac.rs +++ b/rustls/src/crypto/ring/hmac.rs @@ -23,6 +23,10 @@ impl crypto::hmac::Hmac for Hmac { fn hash_output_len(&self) -> usize { self.0.digest_algorithm().output_len() } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct Key(ring_like::hmac::Key); diff --git a/rustls/src/crypto/ring/kx.rs b/rustls/src/crypto/ring/kx.rs index 67e9f491..7bf472fc 100644 --- a/rustls/src/crypto/ring/kx.rs +++ b/rustls/src/crypto/ring/kx.rs @@ -44,6 +44,10 @@ impl SupportedKxGroup for KxGroup { fn name(&self) -> NamedGroup { self.name } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } impl fmt::Debug for KxGroup { diff --git a/rustls/src/crypto/ring/mod.rs b/rustls/src/crypto/ring/mod.rs index 157dbbba..71e265d2 100644 --- a/rustls/src/crypto/ring/mod.rs +++ b/rustls/src/crypto/ring/mod.rs @@ -205,3 +205,7 @@ mod ring_shim { /// AEAD algorithm that is used by `mod ticketer`. pub(super) static TICKETER_AEAD: &'static ring_like::aead::Algorithm = &ring_like::aead::CHACHA20_POLY1305; + +pub(super) fn fips_mode() -> bool { + false +} diff --git a/rustls/src/crypto/ring/quic.rs b/rustls/src/crypto/ring/quic.rs index 2d6669ca..99ae8321 100644 --- a/rustls/src/crypto/ring/quic.rs +++ b/rustls/src/crypto/ring/quic.rs @@ -177,6 +177,10 @@ impl crate::quic::Algorithm for KeyBuilder { fn aead_key_len(&self) -> usize { self.0.key_len() } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } #[cfg(test)] diff --git a/rustls/src/crypto/ring/tls12.rs b/rustls/src/crypto/ring/tls12.rs index 5e2f030c..11cdf9e2 100644 --- a/rustls/src/crypto/ring/tls12.rs +++ b/rustls/src/crypto/ring/tls12.rs @@ -173,6 +173,10 @@ impl Tls12AeadAlgorithm for GcmAlgorithm { iv: gcm_iv(write_iv, explicit), }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } pub(crate) struct ChaCha20Poly1305; @@ -219,6 +223,10 @@ impl Tls12AeadAlgorithm for ChaCha20Poly1305 { iv: Iv::new(iv[..].try_into().unwrap()), }) } + + fn fips_mode(&self) -> bool { + false // not fips approved + } } /// A `MessageEncrypter` for AES-GCM AEAD ciphersuites. TLS 1.2 only. diff --git a/rustls/src/crypto/ring/tls13.rs b/rustls/src/crypto/ring/tls13.rs index 73d93d64..4a6afeae 100644 --- a/rustls/src/crypto/ring/tls13.rs +++ b/rustls/src/crypto/ring/tls13.rs @@ -94,6 +94,10 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead { ) -> Result { Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) } + + fn fips_mode(&self) -> bool { + false // chacha20poly1305 not FIPS approved + } } struct Aes256GcmAead(AeadAlgorithm); @@ -118,6 +122,10 @@ impl Tls13AeadAlgorithm for Aes256GcmAead { ) -> Result { Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct Aes128GcmAead(AeadAlgorithm); @@ -142,6 +150,10 @@ impl Tls13AeadAlgorithm for Aes128GcmAead { ) -> Result { Ok(ConnectionTrafficSecrets::Aes128Gcm { key, iv }) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } // common encrypter/decrypter/key_len items for above Tls13AeadAlgorithm impls @@ -263,6 +275,10 @@ impl Hkdf for RingHkdf { fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> crypto::hmac::Tag { crypto::hmac::Tag::new(hmac::sign(&hmac::Key::new(self.1, key.as_ref()), message).as_ref()) } + + fn fips_mode(&self) -> bool { + super::fips_mode() + } } struct RingHkdfExpander { diff --git a/rustls/src/crypto/tls12.rs b/rustls/src/crypto/tls12.rs index ea01b68d..600b2897 100644 --- a/rustls/src/crypto/tls12.rs +++ b/rustls/src/crypto/tls12.rs @@ -63,6 +63,11 @@ pub trait Prf: Send + Sync { /// /// The caller guarantees that `secret`, `label`, and `seed` are non-empty. fn for_secret(&self, output: &mut [u8], secret: &[u8], label: &[u8], seed: &[u8]); + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } pub(crate) fn prf(out: &mut [u8], hmac_key: &dyn hmac::Key, label: &[u8], seed: &[u8]) { diff --git a/rustls/src/crypto/tls13.rs b/rustls/src/crypto/tls13.rs index 3130faea..63470b46 100644 --- a/rustls/src/crypto/tls13.rs +++ b/rustls/src/crypto/tls13.rs @@ -176,6 +176,11 @@ pub trait Hkdf: Send + Sync { /// See [RFC2104](https://datatracker.ietf.org/doc/html/rfc2104) for the /// definition of HMAC. fn hmac_sign(&self, key: &OkmBlock, message: &[u8]) -> hmac::Tag; + + /// Return true if this is backed by a FIPS-approved implementation. + fn fips_mode(&self) -> bool { + false + } } /// `HKDF-Expand(PRK, info, L)` to construct any type from a byte array. diff --git a/rustls/src/quic.rs b/rustls/src/quic.rs index bdc69013..ccafd581 100644 --- a/rustls/src/quic.rs +++ b/rustls/src/quic.rs @@ -584,6 +584,11 @@ pub trait Algorithm: Send + Sync { /// /// This controls the size of `AeadKey`s presented to `packet_key()` and `header_protection_key()`. fn aead_key_len(&self) -> usize; + + /// Whether this algorithm is FIPS-approved. + fn fips_mode(&self) -> bool { + false + } } /// A QUIC header protection key diff --git a/rustls/src/server/server_conn.rs b/rustls/src/server/server_conn.rs index 4c6e9f5e..ff1cd4e1 100644 --- a/rustls/src/server/server_conn.rs +++ b/rustls/src/server/server_conn.rs @@ -387,6 +387,12 @@ impl ServerConfig { } } + /// Return true if connections made with this `ServerConfig` will + /// operate in FIPS mode. + pub fn fips_mode(&self) -> bool { + self.provider.fips_mode() + } + /// We support a given TLS version if it's quoted in the configured /// versions *and* at least one ciphersuite for this version is /// also configured. diff --git a/rustls/src/suites.rs b/rustls/src/suites.rs index 575acf75..0b8367e6 100644 --- a/rustls/src/suites.rs +++ b/rustls/src/suites.rs @@ -41,6 +41,15 @@ pub struct CipherSuiteCommon { pub integrity_limit: u64, } +impl CipherSuiteCommon { + /// Return true if this is backed by a FIPS-approved implementation. + /// + /// This means all the constituent parts that do cryptography return true for `fips_mode()`. + pub fn fips_mode(&self) -> bool { + self.hash_provider.fips_mode() + } +} + /// A cipher suite supported by rustls. /// /// This type carries both configuration and implementation. Compare with @@ -117,6 +126,15 @@ impl SupportedCipherSuite { .is_some(), } } + + /// Return true if this is backed by a FIPS-approved implementation. + pub fn fips_mode(&self) -> bool { + match self { + #[cfg(feature = "tls12")] + Self::Tls12(cs) => cs.fips_mode(), + Self::Tls13(cs) => cs.fips_mode(), + } + } } impl fmt::Debug for SupportedCipherSuite { diff --git a/rustls/src/tls12/mod.rs b/rustls/src/tls12/mod.rs index 23dbcb34..13848098 100644 --- a/rustls/src/tls12/mod.rs +++ b/rustls/src/tls12/mod.rs @@ -62,6 +62,13 @@ impl Tls12CipherSuite { .cloned() .collect() } + + /// Return true if this is backed by a FIPS-approved implementation. + /// + /// This means all the constituent parts that do cryptography return true for `fips_mode()`. + pub fn fips_mode(&self) -> bool { + self.common.fips_mode() && self.prf_provider.fips_mode() && self.aead_alg.fips_mode() + } } impl From<&'static Tls12CipherSuite> for SupportedCipherSuite { diff --git a/rustls/src/tls13/mod.rs b/rustls/src/tls13/mod.rs index b4e38f0f..fc0b31ec 100644 --- a/rustls/src/tls13/mod.rs +++ b/rustls/src/tls13/mod.rs @@ -42,6 +42,19 @@ impl Tls13CipherSuite { (prev.common.hash_provider.algorithm() == self.common.hash_provider.algorithm()) .then(|| prev) } + + /// Return true if this is backed by a FIPS-approved implementation. + /// + /// This means all the constituent parts that do cryptography return true for `fips_mode()`. + pub fn fips_mode(&self) -> bool { + self.common.fips_mode() + && self.hkdf_provider.fips_mode() + && self.aead_alg.fips_mode() + && self + .quic + .map(|q| q.fips_mode()) + .unwrap_or(true) + } } impl From<&'static Tls13CipherSuite> for SupportedCipherSuite { diff --git a/rustls/tests/api.rs b/rustls/tests/api.rs index 4d3fbea1..cd43b3ff 100644 --- a/rustls/tests/api.rs +++ b/rustls/tests/api.rs @@ -5707,3 +5707,27 @@ fn test_client_removes_tls12_session_if_server_sends_undecryptable_first_message ClientStorageOp::RemoveTls12Session(_) )); } + +#[cfg(feature = "ring")] +#[test] +fn test_client_fips_service_indicator() { + assert_eq!(make_client_config(KeyType::Rsa).fips_mode(), false); +} + +#[cfg(feature = "ring")] +#[test] +fn test_server_fips_service_indicator() { + assert_eq!(make_server_config(KeyType::Rsa).fips_mode(), false); +} + +#[cfg(all(not(feature = "ring"), feature = "aws_lc_rs"))] +#[test] +fn test_client_fips_service_indicator() { + assert_eq!(make_client_config(KeyType::Rsa).fips_mode(), true); +} + +#[cfg(all(not(feature = "ring"), feature = "aws_lc_rs"))] +#[test] +fn test_server_fips_service_indicator() { + assert_eq!(make_server_config(KeyType::Rsa).fips_mode(), true); +}