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.
This commit is contained in:
Joseph Birr-Pixton 2023-10-06 12:56:55 +01:00
parent afe43b0213
commit 327444fdb8
23 changed files with 240 additions and 3 deletions

View File

@ -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.

View File

@ -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<Arc<dyn SigningKey>, 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()
}

View File

@ -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()
}
}

View File

@ -106,6 +106,10 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead {
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
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<ConnectionTrafficSecrets, UnsupportedOperationError> {
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<ConnectionTrafficSecrets, UnsupportedOperationError> {
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 {

View File

@ -31,6 +31,11 @@ pub trait Tls13AeadAlgorithm: Send + Sync {
key: AeadKey,
iv: Iv,
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError>;
/// 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<ConnectionTrafficSecrets, UnsupportedOperationError>;
/// 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.

View File

@ -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.

View File

@ -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.

View File

@ -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<Arc<dyn SigningKey>, 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`].

View File

@ -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);

View File

@ -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);

View File

@ -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 {

View File

@ -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
}

View File

@ -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)]

View File

@ -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.

View File

@ -94,6 +94,10 @@ impl Tls13AeadAlgorithm for Chacha20Poly1305Aead {
) -> Result<ConnectionTrafficSecrets, UnsupportedOperationError> {
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<ConnectionTrafficSecrets, UnsupportedOperationError> {
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<ConnectionTrafficSecrets, UnsupportedOperationError> {
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 {

View File

@ -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]) {

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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 {

View File

@ -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 {

View File

@ -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 {

View File

@ -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);
}