use std::path::Path;
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
pub(crate) fn load_pem_certs(
cert_path: &Path,
key_path: &Path,
) -> anyhow::Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
let cert_data = std::fs::read(cert_path)?;
let key_data = std::fs::read(key_path)?;
let certs = rustls_pemfile::certs(&mut cert_data.as_slice()).collect::<Result<Vec<_>, _>>()?;
let key = rustls_pemfile::private_key(&mut key_data.as_slice())?
.ok_or_else(|| anyhow::anyhow!("no private key in {}", key_path.display()))?;
Ok((certs, key))
}
pub(crate) fn check_cert_expiry(cert_path: &Path) -> anyhow::Result<i64> {
let pem = std::fs::read(cert_path)?;
let cert = rustls_pemfile::certs(&mut pem.as_slice())
.next()
.ok_or_else(|| anyhow::anyhow!("no certificate in {}", cert_path.display()))??;
let (_, parsed) = x509_parser::parse_x509_certificate(&cert)
.map_err(|e| anyhow::anyhow!("cannot parse certificate {}: {e}", cert_path.display()))?;
let not_after = parsed.validity().not_after.timestamp();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)?
.as_secs() as i64;
Ok((not_after - now).div_euclid(24 * 60 * 60))
}
#[cfg(test)]
pub(crate) fn mint_cert_expiring_in(dir: &Path, days: i64) -> std::path::PathBuf {
let mut params =
rcgen::CertificateParams::new(vec!["test.example.com".into()]).expect("valid cert params");
params.not_after = time::OffsetDateTime::now_utc() + time::Duration::days(days);
let key = rcgen::KeyPair::generate().expect("keygen");
let cert = params.self_signed(&key).expect("self-signed cert");
let path = dir.join("cert.pem");
std::fs::write(&path, cert.pem()).expect("write cert");
path
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn expiry_comes_from_not_after_not_mtime() {
let tmp = TempDir::new().unwrap();
let path = mint_cert_expiring_in(tmp.path(), 10);
let days = check_cert_expiry(&path).unwrap();
assert!(
(9..=10).contains(&days),
"expected ~10 days from NotAfter, got {days} (mtime-based estimate?)"
);
}
#[test]
fn expired_cert_reports_nonpositive_days() {
let tmp = TempDir::new().unwrap();
let path = mint_cert_expiring_in(tmp.path(), -2);
let days = check_cert_expiry(&path).unwrap();
assert!(days <= 0, "expired cert must report <= 0 days, got {days}");
}
#[test]
fn garbage_cert_data_errors() {
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("cert.pem");
std::fs::write(&path, b"not a certificate").unwrap();
assert!(check_cert_expiry(&path).is_err());
}
}