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 keeps the choice deterministic: the workspace enables
59/// rustls's `aws-lc-rs` feature (e.g. through `sqlx-core`'s rustls
60/// features), any dependency may also enable its `ring` feature, and rustls
61/// 0.23 panics when it cannot auto-detect a single provider. Conflicts here
62/// are non-fatal: if another provider was installed first, it stays active,
63/// the conflict is logged at `warn!`, and `Ok(())` is returned.
64///
65/// This **must** be called before any TLS configuration, HTTP client, database
66/// connection, or JWT operation is created.
67///
68/// Safe to call multiple times -- only the first invocation has an effect;
69/// subsequent calls return the cached first-call result.
70///
71/// # FIPS-claim caveats
72///
73/// On the resulting provider, `provider.fips() == true` is a **runtime
74/// witness** under the witness-pattern rework — it is `true` only when both
75/// (a) every primitive routes through a CMVP-validated module *and* (b)
76/// the runtime OE check agrees. It is no longer an unconditional design-
77/// intent claim.
78///
79/// **macOS**: the corecrypto crate runs an OE check at first provider
80/// construction (`cf_gears_rustls_corecrypto_provider::oe::fips_witness_ok`).
81/// On a macOS major outside the active corecrypto CMVP cert OE, **every**
82/// `fips()` impl in the provider returns `false` and a single
83/// `tracing::warn!` is emitted. The post-install witness `assert!` then
84/// **panics** — a misconfigured FIPS build must never silently proceed.
85/// Use `CF_GEARS_FIPS_OE_OVERRIDE=1` to force the witness to `true`
86/// for CI on pre-release macOS — never for production. See the
87/// `cf-gears-rustls-corecrypto-provider` README "Runtime FIPS witness" section
88/// and FIPS PRD §8.3.
89///
90/// Downstream TLS configuration code (`toolkit_http::tls::apply_fips_hardening`)
91/// performs its own `config.fips()` check and returns `Err` rather than
92/// panicking — `ClientConfig::fips()` depends on per-config settings
93/// (`require_ems`, protocol versions) beyond the provider itself, so the
94/// TLS-layer check is a defence-in-depth complement to the bootstrap
95/// assertion here.
96///
97/// **Linux / Windows**: runtime OE-validation is not yet implemented; OE
98/// coverage is verified via the release checklist (manual CMVP cert search,
99/// PRD §9.3). Tracked as a follow-up in PRD §10.
100///
101/// # Errors
102///
103/// - [`CryptoProviderError::FipsProviderConflict`] if the `fips` feature is
104/// enabled and another rustls provider was installed first.
105/// - [`CryptoProviderError::SystemFipsModeNotEnabled`] on Windows+`fips` when
106/// the OS is not in system-wide FIPS mode.
107///
108/// # Panics
109///
110/// Panics (via `assert!`) under `--features fips` if the installed crypto
111/// provider does not report `fips() == true`. This is an intentional
112/// fail-closed guard against misconfigured builds (wrong feature flags,
113/// wrong OS, or macOS OE mismatch without the override env-var).
114pub fn init_crypto_provider() -> Result<(), CryptoProviderError> {
115 INIT_RESULT
116 .get_or_init(|| {
117 #[cfg(all(feature = "fips", target_os = "macos"))]
118 {
119 // Under toolkit's `fips` feature the dependency tree
120 // activates `rustls-corecrypto-provider/fips`, which
121 // routes `default_provider()` to the TLS-1.3-only FIPS-
122 // claim variant — same pattern as `rustls-cng-crypto`'s
123 // feature flag. We therefore install the unified entry
124 // point (no need to remember which factory under which
125 // build profile).
126 if let Err(prev) = rustls_corecrypto_provider::default_provider().install_default()
127 {
128 tracing::error!(
129 previous_provider = ?prev,
130 "FIPS crypto provider conflict: another rustls provider was already installed"
131 );
132 return Err(CryptoProviderError::FipsProviderConflict);
133 }
134 tracing::info!("FIPS-140-3 crypto provider installed (Apple corecrypto, macOS, TLS 1.3-only)");
135 }
136
137 #[cfg(all(feature = "fips", target_os = "windows"))]
138 {
139 // Fail-closed: when Windows is not in system-wide FIPS mode,
140 // `rustls_cng_crypto::fips_provider()` returns a CryptoProvider
141 // with empty `cipher_suites` / `kx_groups` (rustls's per-suite
142 // `.fips()` flag is the gate; in non-FIPS-mode Windows none
143 // qualify). The upstream crate's `fips::enabled()` helper is
144 // `pub(crate)`, so we detect the same condition via the
145 // documented empty-provider shape rather than poking
146 // `BCryptGetFipsAlgorithmMode` ourselves. Refuse to install
147 // rather than degrade the FIPS claim with a non-handshakeable
148 // provider.
149 let provider = rustls_cng_crypto::fips_provider();
150 if provider.cipher_suites.is_empty() {
151 tracing::error!(
152 "Windows FIPS mode not enabled (FipsAlgorithmPolicy != 1); \
153 rustls-cng-crypto returned an empty FIPS provider"
154 );
155 return Err(CryptoProviderError::SystemFipsModeNotEnabled);
156 }
157 if let Err(prev) = provider.install_default() {
158 tracing::error!(
159 previous_provider = ?prev,
160 "FIPS crypto provider conflict: another rustls provider was already installed"
161 );
162 return Err(CryptoProviderError::FipsProviderConflict);
163 }
164 tracing::info!("FIPS-140-3 crypto provider installed (Windows CNG)");
165 }
166
167 #[cfg(all(feature = "fips", not(any(target_os = "macos", target_os = "windows"))))]
168 {
169 if let Err(prev) = rustls::crypto::default_fips_provider().install_default() {
170 tracing::error!(
171 previous_provider = ?prev,
172 "FIPS crypto provider conflict: another rustls provider was already installed"
173 );
174 return Err(CryptoProviderError::FipsProviderConflict);
175 }
176 tracing::info!("FIPS-140-3 crypto provider installed (AWS-LC FIPS module)");
177 }
178
179 #[cfg(feature = "fips")]
180 {
181 let Some(provider) = rustls::crypto::CryptoProvider::get_default() else {
182 unreachable!("provider must be installed at this point");
183 };
184 assert!(
185 provider.fips(),
186 "FIPS post-install witness failed: installed provider does not report fips()==true"
187 );
188 tracing::info!("FIPS post-install witness: OK");
189 }
190
191 #[cfg(not(feature = "fips"))]
192 {
193 if let Err(prev) = rustls::crypto::aws_lc_rs::default_provider().install_default() {
194 // Non-fatal: another provider is already active, TLS still works.
195 tracing::warn!(
196 previous_provider = ?prev,
197 "aws-lc-rs crypto provider not installed: another default provider was already set"
198 );
199 } else {
200 tracing::info!("aws-lc-rs crypto provider installed");
201 }
202 }
203
204 Ok(())
205 })
206 .clone()
207}