use std::sync::{Arc, OnceLock};
use arc_swap::ArcSwap;
#[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 {}
type Cached = Arc<Result<Arc<rustls::RootCertStore>, NativeRootsError>>;
static NATIVE_ROOTS: OnceLock<ArcSwap<Result<Arc<rustls::RootCertStore>, NativeRootsError>>> =
OnceLock::new();
fn snapshot() -> &'static ArcSwap<Result<Arc<rustls::RootCertStore>, NativeRootsError>> {
NATIVE_ROOTS.get_or_init(|| ArcSwap::from(Arc::new(load_native_roots())))
}
pub fn native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
let cached: Cached = snapshot().load_full();
cached.as_ref().as_ref().map(Arc::clone).map_err(Clone::clone)
}
pub fn warm_native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
native_roots()
}
pub fn refresh_native_roots() -> Result<Arc<rustls::RootCertStore>, NativeRootsError> {
let outcome = load_native_roots();
match &outcome {
Ok(store) => {
snapshot().store(Arc::new(Ok(Arc::clone(store))));
Ok(Arc::clone(store))
}
Err(e) => {
tracing::warn!(
error = %e,
"native trust store refresh failed; keeping previous snapshot",
);
Err(e.clone())
}
}
}
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));
}
#[test]
fn refresh_native_roots_swaps_to_a_fresh_arc() {
let before = native_roots().expect("first load");
let refreshed = refresh_native_roots().expect("refresh");
assert!(!Arc::ptr_eq(&before, &refreshed), "refresh swaps Arc identity");
let after = native_roots().expect("post-refresh");
assert!(Arc::ptr_eq(&refreshed, &after), "subsequent reads see refreshed snapshot");
}
}