Skip to main content

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}