use rustls_pki_types::CertificateDer;
use std::sync::{Arc, OnceLock};
static NATIVE_ROOTS_CACHE: OnceLock<Vec<CertificateDer<'static>>> = OnceLock::new();
#[cfg(test)]
static LOAD_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
fn load_native_certs_inner() -> Vec<CertificateDer<'static>> {
#[cfg(test)]
LOAD_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let result = rustls_native_certs::load_native_certs();
if !result.errors.is_empty() {
for err in &result.errors {
tracing::warn!(error = %err, "error loading native root certificate");
}
}
let certs: Vec<CertificateDer<'static>> = result.certs;
if certs.is_empty() {
tracing::warn!("no native root CA certificates found");
} else {
tracing::debug!(count = certs.len(), "loaded native root certificates");
}
certs
}
pub fn native_root_certs() -> &'static [CertificateDer<'static>] {
NATIVE_ROOTS_CACHE
.get_or_init(load_native_certs_inner)
.as_slice()
}
#[cfg_attr(feature = "fips", allow(dead_code))]
pub fn get_crypto_provider() -> Arc<rustls::crypto::CryptoProvider> {
rustls::crypto::CryptoProvider::get_default()
.cloned()
.unwrap_or_else(|| {
#[cfg(all(feature = "fips", target_os = "macos"))]
{
Arc::new(rustls_corecrypto_provider::default_provider())
}
#[cfg(all(feature = "fips", target_os = "windows"))]
{
let provider = rustls_cng_crypto::fips_provider();
assert!(
!provider.cipher_suites.is_empty(),
"Windows is not in FIPS mode (FipsAlgorithmPolicy != 1). \
Enable system-wide FIPS via Group Policy and reboot, \
or call toolkit::bootstrap::init_crypto_provider() first \
for the canonical fail-closed path."
);
Arc::new(provider)
}
#[cfg(all(feature = "fips", not(any(target_os = "macos", target_os = "windows"))))]
{
Arc::new(rustls::crypto::default_fips_provider())
}
#[cfg(not(feature = "fips"))]
{
Arc::new(rustls::crypto::aws_lc_rs::default_provider())
}
})
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TlsConfigError {
#[error(
"rustls CryptoProvider has not been installed; call \
toolkit::bootstrap::init_crypto_provider() before building any TLS \
config under --features fips"
)]
NoCryptoProvider,
#[error("{0}")]
FipsHardeningFailed(String),
#[error(transparent)]
Other(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
}
#[cfg(all(test, feature = "fips"))]
mod fips_test_provider {
use std::sync::LazyLock;
pub(super) static INSTALL: LazyLock<()> = LazyLock::new(|| {
#[cfg(target_os = "macos")]
drop(rustls_corecrypto_provider::default_provider().install_default());
#[cfg(target_os = "windows")]
drop(rustls_cng_crypto::fips_provider().install_default());
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
drop(rustls::crypto::default_fips_provider().install_default());
});
}
fn build_client_config(
root_store: rustls::RootCertStore,
) -> Result<rustls::ClientConfig, TlsConfigError> {
#[cfg(all(test, feature = "fips"))]
std::sync::LazyLock::force(&fips_test_provider::INSTALL);
#[cfg(feature = "fips")]
let provider = rustls::crypto::CryptoProvider::get_default()
.cloned()
.ok_or(TlsConfigError::NoCryptoProvider)?;
#[cfg(not(feature = "fips"))]
let provider = get_crypto_provider();
#[allow(unused_mut)]
let mut config = rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| TlsConfigError::Other(Box::new(e)))?
.with_root_certificates(root_store)
.with_no_client_auth();
#[cfg(feature = "fips")]
{
apply_fips_hardening(&mut config)?;
}
Ok(config)
}
#[cfg(feature = "fips")]
fn apply_fips_hardening(cfg: &mut rustls::ClientConfig) -> Result<(), TlsConfigError> {
cfg.require_ems = true;
if !cfg.fips() {
return Err(TlsConfigError::FipsHardeningFailed(
"TLS ClientConfig does not advertise FIPS after enabling require_ems. \
The bootstrap assert in init_crypto_provider should have caught a \
non-FIPS provider at startup; if you see this error the provider is \
FIPS-OK but a per-config setting (protocol versions, require_ems) is \
preventing ClientConfig::fips() from reporting true. \
If init_crypto_provider was not called, call it before building any \
TLS config."
.to_owned(),
));
}
Ok(())
}
pub fn native_roots_client_config() -> Result<rustls::ClientConfig, TlsConfigError> {
let certs = native_root_certs();
let mut root_store = rustls::RootCertStore::empty();
if certs.is_empty() {
return Err(TlsConfigError::Other(
"no native root CA certificates found in OS certificate store".into(),
));
}
let (added, ignored) = root_store.add_parsable_certificates(certs.iter().cloned());
if ignored > 0 {
tracing::warn!(
added = added,
ignored = ignored,
"some native root certificates could not be parsed"
);
}
if added == 0 {
return Err(TlsConfigError::Other(
format!(
"no valid native root CA certificates parsed (found {}, all {} failed to parse)",
certs.len(),
ignored
)
.into(),
));
}
build_client_config(root_store)
}
pub fn webpki_roots_client_config() -> Result<rustls::ClientConfig, TlsConfigError> {
let mut root_store = rustls::RootCertStore::empty();
root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
build_client_config(root_store)
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::*;
use std::sync::atomic::Ordering;
#[test]
fn test_native_roots_cached() {
let initial_count = LOAD_COUNT.load(Ordering::SeqCst);
let result1 = native_root_certs();
let result2 = native_root_certs();
let result3 = native_root_certs();
let final_count = LOAD_COUNT.load(Ordering::SeqCst);
assert!(
final_count <= initial_count + 1,
"loader should run at most once, but ran {} times since test start",
final_count - initial_count
);
assert_eq!(result1.len(), result2.len());
assert_eq!(result2.len(), result3.len());
assert!(std::ptr::eq(result1, result2), "should return same slice");
assert!(std::ptr::eq(result2, result3), "should return same slice");
}
#[test]
fn test_native_roots_client_config() {
let result = native_roots_client_config();
match &result {
Ok(_) => tracing::debug!("native_roots_client_config succeeded"),
Err(e) => {
tracing::debug!(error = %e, "native_roots_client_config failed (expected on minimal containers)");
}
}
}
#[test]
fn test_webpki_roots_client_config_builds() {
let cfg = webpki_roots_client_config().expect("webpki roots must always build");
let provider = cfg.crypto_provider();
assert!(
!provider.cipher_suites.is_empty(),
"TLS client config must carry a non-empty cipher-suite list"
);
assert!(
!provider.kx_groups.is_empty(),
"TLS client config must carry a non-empty kx-group list"
);
}
#[test]
#[cfg(feature = "fips")]
fn fips_client_config_requires_ems_and_advertises_fips() {
let cfg = webpki_roots_client_config().expect("build under fips");
assert!(cfg.require_ems, "fips build must set require_ems = true");
assert!(
cfg.fips(),
"fips build must yield ClientConfig::fips() == true (full provider chain)"
);
}
}