use std::io;
use purecrypto::tls::RootCertStore;
use crate::error::{Error, Result};
pub(crate) const SYSTEM_CA_PATHS: &[&str] = &[
"/etc/ssl/certs/ca-certificates.crt", "/etc/pki/tls/certs/ca-bundle.crt", "/etc/ssl/cert.pem", "/etc/ssl/ca-bundle.pem", "/etc/ca-certificates/extracted/tls-ca-bundle.pem", ];
pub(crate) fn load_system_roots() -> Result<RootCertStore> {
for path in SYSTEM_CA_PATHS {
let pem = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) if e.kind() == io::ErrorKind::NotFound => continue,
Err(e) => return Err(Error::Io(e)),
};
return parse_into_store(&pem, path);
}
Err(Error::BadResponse(
"no system CA bundle found; tried common Unix paths".into(),
))
}
#[allow(dead_code)]
pub(crate) fn load_from_file(path: &str) -> Result<RootCertStore> {
let pem = std::fs::read_to_string(path).map_err(Error::Io)?;
parse_into_store(&pem, path)
}
fn parse_into_store(pem: &str, path: &str) -> Result<RootCertStore> {
let mut roots = RootCertStore::new();
let mut loaded = 0usize;
for block in pem_blocks(pem) {
if roots.add_pem(&block).is_ok() {
loaded += 1;
}
}
if loaded == 0 {
return Err(Error::BadResponse(format!(
"no usable CA certificates parsed from {path}"
)));
}
Ok(roots)
}
pub(crate) fn pem_blocks(pem: &str) -> Vec<String> {
const BEGIN: &str = "-----BEGIN CERTIFICATE-----";
const END: &str = "-----END CERTIFICATE-----";
let mut out = Vec::new();
let mut rest = pem;
while let Some(start) = rest.find(BEGIN) {
let after_begin = &rest[start..];
let Some(end_rel) = after_begin.find(END) else {
break;
};
let end_abs = start + end_rel + END.len();
out.push(rest[start..end_abs].to_string());
rest = &rest[end_abs..];
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pem_blocks_splits() {
let pem = "junk\n\
-----BEGIN CERTIFICATE-----\nAAA\n-----END CERTIFICATE-----\n\
noise\n\
-----BEGIN CERTIFICATE-----\nBBB\n-----END CERTIFICATE-----\n";
let blocks = pem_blocks(pem);
assert_eq!(blocks.len(), 2);
assert!(blocks[0].contains("AAA"));
assert!(blocks[1].contains("BBB"));
}
}