Skip to content

Commit e5af7ed

Browse files
committed
sources: add bottlerocket-crypto-provider crate
Add a centralized CryptoProvider crate that provides runtime FIPS detection and TLS algorithm selection for Bottlerocket Rust binaries. When the kernel FIPS flag is enabled (/proc/sys/crypto/fips_enabled = 1), the provider restricts TLS to FIPS-approved algorithms only (AES-GCM cipher suites, P-256/P-384 key exchange). On non-FIPS systems, the full algorithm set is available. This crate exposes three public functions: * fips_enabled() - detect kernel FIPS mode * provider() - return the appropriate CryptoProvider based on runtime detection * install_provider() - install the provider as the rustls global default Signed-off-by: Jingwei Wang <jweiw@amazon.com>
1 parent c11e0c3 commit e5af7ed

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

sources/Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sources/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ members = [
8585
"xfscli",
8686

8787
"whippet",
88+
"bottlerocket-crypto-provider",
8889
]
8990

9091
[workspace.dependencies]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "bottlerocket-crypto-provider"
3+
version = "0.1.0"
4+
license = "Apache-2.0 OR MIT"
5+
edition = "2021"
6+
publish = false
7+
8+
[dependencies]
9+
aws-lc-rs = { workspace = true }
10+
log = { workspace = true }
11+
rustls = { workspace = true }
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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

Comments
 (0)