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()
}
pub fn get_crypto_provider() -> Arc<rustls::crypto::CryptoProvider> {
rustls::crypto::CryptoProvider::get_default()
.cloned()
.unwrap_or_else(|| {
#[cfg(feature = "fips")]
{
Arc::new(rustls::crypto::default_fips_provider())
}
#[cfg(not(feature = "fips"))]
{
Arc::new(rustls::crypto::aws_lc_rs::default_provider())
}
})
}
pub fn native_roots_client_config() -> Result<rustls::ClientConfig, String> {
let certs = native_root_certs();
let mut root_store = rustls::RootCertStore::empty();
if certs.is_empty() {
return Err("no native root CA certificates found in OS certificate store".to_owned());
}
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(format!(
"no valid native root CA certificates parsed (found {}, all {} failed to parse)",
certs.len(),
ignored
));
}
let provider = get_crypto_provider();
let config = rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.map_err(|e| format!("failed to set TLS protocol versions: {e}"))?
.with_root_certificates(root_store)
.with_no_client_auth();
#[cfg(feature = "fips")]
assert!(
config.fips(),
"TLS ClientConfig is NOT in FIPS mode - this indicates the FIPS crypto provider \
was not installed before TLS configuration was created"
);
Ok(config)
}
#[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)");
}
}
}
}