toolkit/bootstrap/crypto.rs
1use std::sync::OnceLock;
2
3/// Error returned when the crypto provider cannot be installed.
4// `Clone` required by `OnceLock<Result<_>>` cache in `init_crypto_provider` --
5// the cached result is cloned on every call.
6// `PartialEq`/`Eq` used by tests asserting the cached result is stable.
7#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
8#[non_exhaustive]
9pub enum CryptoProviderError {
10 /// Another crypto provider was already installed (FIPS mode).
11 #[error("failed to install FIPS crypto provider - another provider is already installed")]
12 FipsProviderConflict,
13 /// Windows is not in FIPS mode — the system-wide Group Policy
14 /// `FipsAlgorithmPolicy` is not enabled, so CNG would not enforce the
15 /// FIPS-Approved algorithm subset at runtime. Fail-closed: refuse to
16 /// start rather than silently degrade the FIPS claim.
17 #[cfg(target_os = "windows")]
18 #[error(
19 "Windows is not in FIPS mode (HKLM\\System\\CurrentControlSet\\Control\\Lsa\\FipsAlgorithmPolicy != 1). \
20 Enable system-wide FIPS via Group Policy (Computer Configuration > Windows Settings > \
21 Security Settings > Local Policies > Security Options > 'System cryptography: Use FIPS \
22 compliant algorithms') and reboot before launching this service."
23 )]
24 SystemFipsModeNotEnabled,
25}
26
27static INIT_RESULT: OnceLock<Result<(), CryptoProviderError>> = OnceLock::new();
28
29/// Install the process-wide default rustls [`CryptoProvider`](rustls::crypto::CryptoProvider).
30///
31/// Dispatch:
32///
33/// - **`fips` feature + macOS**: installs the Apple corecrypto-backed provider
34/// from `rustls-corecrypto-provider`, **restricted to TLS 1.3 cipher
35/// suites** via `fips_provider()`. corecrypto is shipped inside macOS and
36/// validated by Apple under FIPS 140-3 per OS release; see
37/// <https://csrc.nist.gov/projects/cryptographic-module-validation-program>
38/// for the cert matching the running macOS version. TLS 1.2 cipher suites
39/// are excluded under `fips` because Apple does not expose a separately
40/// CAVS-validated TLS PRF primitive (unlike aws-lc-fips on Linux); see
41/// the `cf-gears-rustls-corecrypto-provider` README "FIPS claim boundaries"
42/// section. On macOS the `rustls/fips` feature is not activated (see
43/// `rustls-fips-shim`), so the AWS-LC FIPS dylib is not linked.
44/// - **`fips` feature + Windows**: installs the Windows CNG-backed provider
45/// from `rustls-cng-crypto`'s `fips_provider()`. CNG is shipped inside
46/// Windows and validated by Microsoft under FIPS 140-3 per OS release.
47/// Requires Windows to be in system-wide FIPS mode
48/// (`HKLM\System\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy = 1`,
49/// set via Group Policy); fails closed with
50/// [`CryptoProviderError::SystemFipsModeNotEnabled`] otherwise. As with
51/// macOS, `rustls/fips` is not activated on this target, so the AWS-LC
52/// FIPS dylib is not linked.
53/// - **`fips` feature + other** (Linux, etc.): installs the FIPS-validated
54/// AWS-LC provider (`aws-lc-fips-sys`, NIST Certificate #4816). The cert's
55/// OE covers Linux but not Darwin/Windows, which is why those targets use
56/// different providers.
57/// - **Standard mode** (no `fips` feature): installs the `aws-lc-rs` provider
58/// explicitly. This is required because both `ring` and `aws-lc-rs` are
59/// compiled into the binary (ring via `pingora-rustls`), and rustls
60/// 0.23 panics when it cannot auto-detect a single provider. Conflicts here
61/// are non-fatal: if another provider was installed first, it stays active,
62/// the conflict is logged at `warn!`, and `Ok(())` is returned.
63///
64/// This **must** be called before any TLS configuration, HTTP client, database
65/// connection, or JWT operation is created.
66///
67/// Safe to call multiple times -- only the first invocation has an effect;
68/// subsequent calls return the cached first-call result.
69///
70/// # FIPS-claim caveats
71///
72/// On the resulting provider, `provider.fips() == true` is a **runtime
73/// witness** under the witness-pattern rework — it is `true` only when both
74/// (a) every primitive routes through a CMVP-validated module *and* (b)
75/// the runtime OE check agrees. It is no longer an unconditional design-
76/// intent claim.
77///
78/// **macOS**: the corecrypto crate runs an OE check at first provider
79/// construction (`cf_gears_rustls_corecrypto_provider::oe::fips_witness_ok`).
80/// On a macOS major outside the active corecrypto CMVP cert OE, **every**
81/// `fips()` impl in the provider returns `false` and a single
82/// `tracing::warn!` is emitted. The post-install witness `assert!` then
83/// **panics** — a misconfigured FIPS build must never silently proceed.
84/// Use `CF_GEARS_FIPS_OE_OVERRIDE=1` to force the witness to `true`
85/// for CI on pre-release macOS — never for production. See the
86/// `cf-gears-rustls-corecrypto-provider` README "Runtime FIPS witness" section
87/// and FIPS PRD §8.3.
88///
89/// Downstream TLS configuration code (`toolkit_http::tls::apply_fips_hardening`)
90/// performs its own `config.fips()` check and returns `Err` rather than
91/// panicking — `ClientConfig::fips()` depends on per-config settings
92/// (`require_ems`, protocol versions) beyond the provider itself, so the
93/// TLS-layer check is a defence-in-depth complement to the bootstrap
94/// assertion here.
95///
96/// **Linux / Windows**: runtime OE-validation is not yet implemented; OE
97/// coverage is verified via the release checklist (manual CMVP cert search,
98/// PRD §9.3). Tracked as a follow-up in PRD §10.
99///
100/// # Errors
101///
102/// - [`CryptoProviderError::FipsProviderConflict`] if the `fips` feature is
103/// enabled and another rustls provider was installed first.
104/// - [`CryptoProviderError::SystemFipsModeNotEnabled`] on Windows+`fips` when
105/// the OS is not in system-wide FIPS mode.
106///
107/// # Panics
108///
109/// Panics (via `assert!`) under `--features fips` if the installed crypto
110/// provider does not report `fips() == true`. This is an intentional
111/// fail-closed guard against misconfigured builds (wrong feature flags,
112/// wrong OS, or macOS OE mismatch without the override env-var).
113pub fn init_crypto_provider() -> Result<(), CryptoProviderError> {
114 INIT_RESULT
115 .get_or_init(|| {
116 #[cfg(all(feature = "fips", target_os = "macos"))]
117 {
118 // Under toolkit's `fips` feature the dependency tree
119 // activates `rustls-corecrypto-provider/fips`, which
120 // routes `default_provider()` to the TLS-1.3-only FIPS-
121 // claim variant — same pattern as `rustls-cng-crypto`'s
122 // feature flag. We therefore install the unified entry
123 // point (no need to remember which factory under which
124 // build profile).
125 if let Err(prev) = rustls_corecrypto_provider::default_provider().install_default()
126 {
127 tracing::error!(
128 previous_provider = ?prev,
129 "FIPS crypto provider conflict: another rustls provider was already installed"
130 );
131 return Err(CryptoProviderError::FipsProviderConflict);
132 }
133 tracing::info!("FIPS-140-3 crypto provider installed (Apple corecrypto, macOS, TLS 1.3-only)");
134 }
135
136 #[cfg(all(feature = "fips", target_os = "windows"))]
137 {
138 // Fail-closed: when Windows is not in system-wide FIPS mode,
139 // `rustls_cng_crypto::fips_provider()` returns a CryptoProvider
140 // with empty `cipher_suites` / `kx_groups` (rustls's per-suite
141 // `.fips()` flag is the gate; in non-FIPS-mode Windows none
142 // qualify). The upstream crate's `fips::enabled()` helper is
143 // `pub(crate)`, so we detect the same condition via the
144 // documented empty-provider shape rather than poking
145 // `BCryptGetFipsAlgorithmMode` ourselves. Refuse to install
146 // rather than degrade the FIPS claim with a non-handshakeable
147 // provider.
148 let provider = rustls_cng_crypto::fips_provider();
149 if provider.cipher_suites.is_empty() {
150 tracing::error!(
151 "Windows FIPS mode not enabled (FipsAlgorithmPolicy != 1); \
152 rustls-cng-crypto returned an empty FIPS provider"
153 );
154 return Err(CryptoProviderError::SystemFipsModeNotEnabled);
155 }
156 if let Err(prev) = provider.install_default() {
157 tracing::error!(
158 previous_provider = ?prev,
159 "FIPS crypto provider conflict: another rustls provider was already installed"
160 );
161 return Err(CryptoProviderError::FipsProviderConflict);
162 }
163 tracing::info!("FIPS-140-3 crypto provider installed (Windows CNG)");
164 }
165
166 #[cfg(all(feature = "fips", not(any(target_os = "macos", target_os = "windows"))))]
167 {
168 if let Err(prev) = rustls::crypto::default_fips_provider().install_default() {
169 tracing::error!(
170 previous_provider = ?prev,
171 "FIPS crypto provider conflict: another rustls provider was already installed"
172 );
173 return Err(CryptoProviderError::FipsProviderConflict);
174 }
175 tracing::info!("FIPS-140-3 crypto provider installed (AWS-LC FIPS module)");
176 }
177
178 #[cfg(feature = "fips")]
179 {
180 let Some(provider) = rustls::crypto::CryptoProvider::get_default() else {
181 unreachable!("provider must be installed at this point");
182 };
183 assert!(
184 provider.fips(),
185 "FIPS post-install witness failed: installed provider does not report fips()==true"
186 );
187 tracing::info!("FIPS post-install witness: OK");
188 }
189
190 #[cfg(not(feature = "fips"))]
191 {
192 if let Err(prev) = rustls::crypto::aws_lc_rs::default_provider().install_default() {
193 // Non-fatal: another provider is already active, TLS still works.
194 tracing::warn!(
195 previous_provider = ?prev,
196 "aws-lc-rs crypto provider not installed: another default provider was already set"
197 );
198 } else {
199 tracing::info!("aws-lc-rs crypto provider installed");
200 }
201 }
202
203 Ok(())
204 })
205 .clone()
206}