use der::Encode;
use der_stable::Decode as _;
use std::collections::{HashMap, HashSet};
use std::time::{SystemTime, UNIX_EPOCH};
use x509_cert::Certificate;
use crate::error::{CertChainError, SmimeError};
use crate::key::RevocationChecker;
const MAX_DEPTH: usize = 10;
pub(crate) fn validate_chain(
signer_cert: &Certificate,
bag: &[Certificate],
trust_anchors: &[Certificate],
now: SystemTime,
revocation: &dyn RevocationChecker,
) -> Result<(), SmimeError> {
if trust_anchors.is_empty() {
return Err(SmimeError::CertChain(CertChainError::NoTrustAnchors));
}
let chain = build_chain(signer_cert, bag, trust_anchors)?;
let chain_stable: Vec<x509_cert_stable::Certificate> = chain
.iter()
.map(|c| bridge_cert(c))
.collect::<Result<_, _>>()?;
let anchors_stable: Vec<pkix_chain::TrustAnchor> = trust_anchors
.iter()
.map(|a| bridge_cert(a).map(pkix_chain::TrustAnchor::from_cert))
.collect::<Result<_, _>>()?;
let now_unix = now.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
let policy = pkix_chain::ValidationPolicy {
current_time_unix: now_unix,
max_path_len: MAX_DEPTH as u8,
..Default::default()
};
pkix_chain::verify_chain_default(
&chain_stable,
&anchors_stable,
&policy,
&pkix_chain::NoRevocation,
)
.map_err(|e| map_pkix_error(e, &chain))?;
for cert in &chain {
revocation.check(cert)?;
}
Ok(())
}
fn build_chain<'a>(
signer_cert: &'a Certificate,
bag: &'a [Certificate],
trust_anchors: &[Certificate],
) -> Result<Vec<&'a Certificate>, SmimeError> {
let mut chain: Vec<&Certificate> = vec![signer_cert];
let mut visited: HashSet<Vec<u8>> = HashSet::new();
let signer_subject_der = signer_cert
.tbs_certificate()
.subject()
.to_der()
.map_err(|e| {
SmimeError::CertChain(CertChainError::Other(format!(
"signer cert subject DER encode: {e}"
)))
})?;
visited.insert(signer_subject_der);
let anchor_subjects: HashSet<Vec<u8>> = trust_anchors
.iter()
.map(|a| {
a.tbs_certificate().subject().to_der().map_err(|e| {
SmimeError::CertChain(CertChainError::Other(format!(
"trust anchor subject DER encode failed: {e}"
)))
})
})
.collect::<Result<HashSet<Vec<u8>>, SmimeError>>()?;
let bag_by_subject: HashMap<Vec<u8>, &Certificate> = bag
.iter()
.filter_map(|c| c.tbs_certificate().subject().to_der().ok().map(|s| (s, c)))
.collect();
let mut current = signer_cert;
for _ in 0..MAX_DEPTH {
let issuer_der = current.tbs_certificate().issuer().to_der().map_err(|e| {
SmimeError::CertChain(CertChainError::Other(format!("issuer DER encode: {e}")))
})?;
if anchor_subjects.contains(&issuer_der) {
return Ok(chain);
}
let parent = bag_by_subject.get(&issuer_der).copied();
match parent {
None => {
return Err(SmimeError::CertChain(CertChainError::NoMatchingIssuer {
issuer: current.tbs_certificate().issuer().to_string(),
}));
}
Some(p) => {
let subj_der = p.tbs_certificate().subject().to_der().map_err(|e| {
SmimeError::CertChain(CertChainError::Other(format!("subject DER encode: {e}")))
})?;
if !visited.insert(subj_der) {
return Err(SmimeError::CertChain(CertChainError::Cycle {
subject: p.tbs_certificate().subject().to_string(),
}));
}
chain.push(p);
current = p;
}
}
}
Err(SmimeError::CertChain(CertChainError::TooDeep))
}
fn bridge_cert(cert: &Certificate) -> Result<x509_cert_stable::Certificate, SmimeError> {
let der = cert.to_der().map_err(|e| {
SmimeError::CertChain(CertChainError::Other(format!("cert DER encode: {e}")))
})?;
x509_cert_stable::Certificate::from_der(&der)
.map_err(|e| SmimeError::CertChain(CertChainError::Other(format!("cert DER bridge: {e}"))))
}
fn map_pkix_error(e: pkix_chain::Error, chain: &[&Certificate]) -> SmimeError {
use pkix_chain::pkix_path::Error as PE;
use pkix_chain::Error as CE;
let subject_at = |i: usize| -> String {
chain
.get(i)
.map(|c| c.tbs_certificate().subject().to_string())
.unwrap_or_default()
};
let chain_err = match e {
CE::Path(PE::NoTrustedPath) | CE::Path(PE::ChainBroken { .. }) => {
let issuer = chain
.last()
.map(|c| c.tbs_certificate().issuer().to_string())
.unwrap_or_default();
CertChainError::NoMatchingIssuer { issuer }
}
CE::Path(PE::PathTooLong) => CertChainError::TooDeep,
CE::Path(PE::SignatureInvalid { index }) => CertChainError::SignatureVerification {
subject: subject_at(index),
},
CE::Path(PE::NotCA { index }) | CE::Path(PE::KeyUsageMissing { index }) => {
CertChainError::NotACa {
subject: subject_at(index),
}
}
CE::Path(PE::ValidityPeriod { index }) => {
if let Some(cert) = chain.get(index) {
CertChainError::CertificateExpired {
subject: cert.tbs_certificate().subject().to_string(),
not_after: cert.tbs_certificate().validity().not_after.to_string(),
}
} else {
CertChainError::Other(format!("{e}"))
}
}
other => CertChainError::Other(format!("{other}")),
};
SmimeError::CertChain(chain_err)
}