|
| 1 | +//! Centralized CryptoProvider for Bottlerocket Rust binaries. |
| 2 | +//! |
| 3 | +//! Provides runtime FIPS detection and TLS algorithm selection. |
| 4 | +//! When the kernel FIPS flag is enabled (`/proc/sys/crypto/fips_enabled` = 1), |
| 5 | +//! the provider restricts TLS to FIPS-approved algorithms only. |
| 6 | +
|
| 7 | +use log::info; |
| 8 | +use rustls::crypto::{aws_lc_rs, CryptoProvider}; |
| 9 | +use rustls::{CipherSuite, NamedGroup}; |
| 10 | + |
| 11 | +/// FIPS-approved TLS cipher suites. |
| 12 | +const FIPS_CIPHER_SUITES: &[CipherSuite] = &[ |
| 13 | + CipherSuite::TLS13_AES_256_GCM_SHA384, |
| 14 | + CipherSuite::TLS13_AES_128_GCM_SHA256, |
| 15 | + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, |
| 16 | + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, |
| 17 | + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, |
| 18 | + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, |
| 19 | +]; |
| 20 | + |
| 21 | +/// FIPS-approved key exchange groups. |
| 22 | +const FIPS_KX_GROUPS: &[NamedGroup] = &[ |
| 23 | + NamedGroup::secp256r1, |
| 24 | + NamedGroup::secp384r1, |
| 25 | + NamedGroup::X25519MLKEM768, |
| 26 | +]; |
| 27 | + |
| 28 | +/// Detect whether the system is running in FIPS mode by reading the kernel flag. |
| 29 | +pub fn fips_enabled() -> bool { |
| 30 | + std::fs::read_to_string("/proc/sys/crypto/fips_enabled") |
| 31 | + .unwrap_or_default() |
| 32 | + .trim() |
| 33 | + == "1" |
| 34 | +} |
| 35 | + |
| 36 | +/// Returns a `CryptoProvider` restricted to FIPS-approved algorithms. |
| 37 | +fn fips_provider() -> CryptoProvider { |
| 38 | + let base = aws_lc_rs::default_provider(); |
| 39 | + CryptoProvider { |
| 40 | + cipher_suites: base |
| 41 | + .cipher_suites |
| 42 | + .into_iter() |
| 43 | + .filter(|s| FIPS_CIPHER_SUITES.contains(&s.suite())) |
| 44 | + .collect(), |
| 45 | + kx_groups: base |
| 46 | + .kx_groups |
| 47 | + .into_iter() |
| 48 | + .filter(|g| FIPS_KX_GROUPS.contains(&g.name())) |
| 49 | + .collect(), |
| 50 | + ..base |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +/// Returns the default `CryptoProvider` with all algorithms available. |
| 55 | +fn default_provider() -> CryptoProvider { |
| 56 | + aws_lc_rs::default_provider() |
| 57 | +} |
| 58 | + |
| 59 | +/// Returns the appropriate `CryptoProvider` based on runtime FIPS detection. |
| 60 | +pub fn provider() -> CryptoProvider { |
| 61 | + if fips_enabled() { |
| 62 | + fips_provider() |
| 63 | + } else { |
| 64 | + default_provider() |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +/// Detect FIPS mode and install the appropriate `CryptoProvider` as the global default. |
| 69 | +/// |
| 70 | +/// This should be called once at the start of `main()` before any TLS connections are made. |
| 71 | +/// If a provider is already installed (by another component), this is a no-op. |
| 72 | +pub fn install_provider() { |
| 73 | + if CryptoProvider::get_default().is_some() { |
| 74 | + return; |
| 75 | + } |
| 76 | + let mode = if fips_enabled() { "FIPS" } else { "default" }; |
| 77 | + info!("Installing {} CryptoProvider", mode); |
| 78 | + let _ = provider().install_default(); |
| 79 | +} |
| 80 | + |
| 81 | +#[cfg(test)] |
| 82 | +mod tests { |
| 83 | + use super::*; |
| 84 | + |
| 85 | + #[test] |
| 86 | + fn fips_provider_excludes_chacha20() { |
| 87 | + let p = fips_provider(); |
| 88 | + assert!(p.cipher_suites.iter().all(|s| { |
| 89 | + s.suite() != CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 |
| 90 | + && s.suite() != CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 |
| 91 | + && s.suite() != CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 |
| 92 | + })); |
| 93 | + } |
| 94 | + |
| 95 | + #[test] |
| 96 | + fn fips_provider_excludes_x25519() { |
| 97 | + let p = fips_provider(); |
| 98 | + assert!(p.kx_groups.iter().all(|g| g.name() != NamedGroup::X25519)); |
| 99 | + } |
| 100 | + |
| 101 | + #[test] |
| 102 | + fn fips_provider_has_aes_gcm() { |
| 103 | + let p = fips_provider(); |
| 104 | + assert!(!p.cipher_suites.is_empty()); |
| 105 | + assert!(p |
| 106 | + .cipher_suites |
| 107 | + .iter() |
| 108 | + .any(|s| s.suite() == CipherSuite::TLS13_AES_256_GCM_SHA384)); |
| 109 | + } |
| 110 | + |
| 111 | + #[test] |
| 112 | + fn fips_provider_has_p256_p384() { |
| 113 | + let p = fips_provider(); |
| 114 | + assert!(!p.kx_groups.is_empty()); |
| 115 | + assert!(p |
| 116 | + .kx_groups |
| 117 | + .iter() |
| 118 | + .any(|g| g.name() == NamedGroup::secp256r1)); |
| 119 | + assert!(p |
| 120 | + .kx_groups |
| 121 | + .iter() |
| 122 | + .any(|g| g.name() == NamedGroup::secp384r1)); |
| 123 | + } |
| 124 | + |
| 125 | + #[test] |
| 126 | + fn default_provider_includes_chacha20() { |
| 127 | + let p = default_provider(); |
| 128 | + assert!(p |
| 129 | + .cipher_suites |
| 130 | + .iter() |
| 131 | + .any(|s| s.suite() == CipherSuite::TLS13_CHACHA20_POLY1305_SHA256)); |
| 132 | + } |
| 133 | + |
| 134 | + #[test] |
| 135 | + fn default_provider_includes_x25519() { |
| 136 | + let p = default_provider(); |
| 137 | + assert!(p.kx_groups.iter().any(|g| g.name() == NamedGroup::X25519)); |
| 138 | + } |
| 139 | + |
| 140 | + #[test] |
| 141 | + fn fips_enabled_defaults_to_false() { |
| 142 | + // On non-FIPS systems (or when /proc/sys/crypto/fips_enabled is missing/0), |
| 143 | + // fips_enabled() should return false |
| 144 | + let enabled = fips_enabled(); |
| 145 | + assert!( |
| 146 | + !enabled, |
| 147 | + "Expected fips_enabled() to be false on non-FIPS system" |
| 148 | + ); |
| 149 | + } |
| 150 | +} |
0 commit comments