use std::sync::{Arc, OnceLock};
#[derive(Debug, Clone)]
pub struct NativeRootsError {
pub message: String,
}
impl std::fmt::Display for NativeRootsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for NativeRootsError {}
static NATIVE_ROOTS: OnceLock<Result<Arc<rustls::RootCertStore>, NativeRootsError>> =
OnceLock::new();
pub fn native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
NATIVE_ROOTS.get_or_init(load_native_roots).as_ref().map(Arc::clone).map_err(Clone::clone)
}
pub fn warm_native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
native_roots()
}
const LOAD_RETRIES: usize = 3;
const LOAD_RETRY_BACKOFF: std::time::Duration = std::time::Duration::from_millis(50);
fn load_native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
let started = std::time::Instant::now();
let mut last_err: Option<NativeRootsError> = None;
for attempt in 0..LOAD_RETRIES {
match try_load_native_roots() {
Ok(store) => {
tracing::info!(
anchors = store.len(),
elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
attempts = attempt + 1,
"native trust store loaded",
);
return Ok(store);
}
Err(e) => {
last_err = Some(e);
if attempt + 1 < LOAD_RETRIES {
std::thread::sleep(LOAD_RETRY_BACKOFF);
}
}
}
}
let err = last_err.expect("at least one attempt always populates last_err on the failure path");
tracing::error!(
error = %err,
elapsed_ms = u64::try_from(started.elapsed().as_millis()).unwrap_or(u64::MAX),
attempts = LOAD_RETRIES,
"native trust store load failed",
);
Err(err)
}
fn try_load_native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
let native = rustls_native_certs::load_native_certs();
if !native.errors.is_empty() {
return Err(NativeRootsError { message: format!("load native certs: {:?}", native.errors) });
}
let mut store = rustls::RootCertStore::empty();
for cert in native.certs {
store.add(cert).map_err(|e| NativeRootsError { message: format!("add native cert: {e}") })?;
}
Ok(Arc::new(store))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn native_roots_returns_same_arc_across_calls() {
let a = native_roots().expect("trust store loads in test env");
let b = native_roots().expect("cached call");
assert!(Arc::ptr_eq(&a, &b), "subsequent calls must hand out the same Arc");
assert!(!a.is_empty(), "system trust store should have at least one anchor");
}
#[test]
fn warm_native_roots_returns_same_result_as_lazy_call() {
let warmed = warm_native_roots().expect("warm");
let lazy = native_roots().expect("lazy");
assert!(Arc::ptr_eq(&warmed, &lazy));
}
}