use rustls::RootCertStore;
use rustls::pki_types::{CertificateDer, pem::PemObject};
#[derive(Debug, thiserror::Error)]
pub enum CaLoadError {
#[error("read CA file {path}: {source}")]
Read {
path: String,
#[source]
source: rustls::pki_types::pem::Error,
},
#[error("parse CA certificate in {path}: {source}")]
Parse {
path: String,
#[source]
source: rustls::pki_types::pem::Error,
},
#[error("add CA certificate from {path} to root store: {source}")]
Reject {
path: String,
#[source]
source: rustls::Error,
},
#[error("CA file {path} contained no certificates")]
Empty {
path: String,
},
}
pub fn load_ca_into(roots: &mut RootCertStore, path: &str) -> Result<(), CaLoadError> {
let mut added = 0usize;
for cert in CertificateDer::pem_file_iter(path).map_err(|source| CaLoadError::Read {
path: path.to_owned(),
source,
})? {
let cert = cert.map_err(|source| CaLoadError::Parse {
path: path.to_owned(),
source,
})?;
roots.add(cert).map_err(|source| CaLoadError::Reject {
path: path.to_owned(),
source,
})?;
added += 1;
}
if added == 0 {
return Err(CaLoadError::Empty {
path: path.to_owned(),
});
}
Ok(())
}
pub fn load_ca_pem_into(
roots: &mut RootCertStore,
label: &str,
pem: &[u8],
) -> Result<(), CaLoadError> {
let mut added = 0usize;
for cert in CertificateDer::pem_slice_iter(pem) {
let cert = cert.map_err(|source| CaLoadError::Parse {
path: label.to_owned(),
source,
})?;
roots.add(cert).map_err(|source| CaLoadError::Reject {
path: label.to_owned(),
source,
})?;
added += 1;
}
if added == 0 {
return Err(CaLoadError::Empty {
path: label.to_owned(),
});
}
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::pedantic, clippy::nursery, missing_docs)]
use super::*;
#[test]
fn missing_file_is_a_read_error() {
let mut roots = RootCertStore::empty();
let err = load_ca_into(&mut roots, "/nonexistent/ca.pem").unwrap_err();
assert!(matches!(err, CaLoadError::Read { .. }), "got {err:?}");
assert!(err.to_string().contains("ca.pem"), "got: {err}");
}
#[test]
fn empty_file_is_an_empty_error() {
let dir = std::env::temp_dir();
let path = dir.join(format!("polyc-crypto-tls-test-{}.pem", std::process::id()));
std::fs::write(&path, b"").unwrap();
let mut roots = RootCertStore::empty();
let err = load_ca_into(&mut roots, path.to_str().unwrap()).unwrap_err();
std::fs::remove_file(&path).ok();
assert!(matches!(err, CaLoadError::Empty { .. }), "got {err:?}");
}
#[test]
fn empty_pem_bytes_is_an_empty_error() {
let mut roots = RootCertStore::empty();
let err = load_ca_pem_into(&mut roots, "test CA", b"").unwrap_err();
assert!(matches!(err, CaLoadError::Empty { .. }), "got {err:?}");
assert!(err.to_string().contains("test CA"), "got: {err}");
}
}