use x509_cert::Certificate;
use x509_verify::VerifyingKey;
use super::super::cert;
use super::super::tsl::TrustStore;
const MAX_CHAIN_DEPTH: usize = 20;
pub(super) fn build_chain(leaf: &Certificate, pool: &[Certificate]) -> Vec<Certificate> {
let mut chain = vec![leaf.clone()];
let mut current = leaf.clone();
for _ in 0..MAX_CHAIN_DEPTH {
if cert::is_self_signed(¤t) {
break;
}
let Some(aki) = cert::authority_key_id(¤t) else {
break;
};
let Some(issuer) = issuer_by_ski(pool, &aki, ¤t) else {
break;
};
chain.push(issuer.clone());
current = issuer;
}
chain
}
fn issuer_by_ski(
pool: &[Certificate],
key_id: &[u8],
current: &Certificate,
) -> Option<Certificate> {
pool.iter()
.find(|candidate| {
cert::subject_key_identifier(candidate).as_deref() == Some(key_id)
&& *candidate != current
})
.cloned()
}
pub(super) fn find_matching_anchor(
chain: &[Certificate],
trust_store: &TrustStore,
) -> Option<String> {
if chain.is_empty() {
return None;
}
for cert in chain {
let Some(cert_ski) = cert::subject_key_identifier(cert) else {
continue;
};
for anchor in &trust_store.ca_anchors {
if let Ok(anchor_cert) = cert::parse_der(&anchor.cert_der) {
if cert::subject_key_identifier(&anchor_cert).as_deref() == Some(&cert_ski) {
return Some(anchor.service_name.clone());
}
}
}
}
for cert in chain {
let issuer_dn = cert::issuer_dn(cert);
for anchor in &trust_store.ca_anchors {
if !anchor.subject_name.is_empty() && issuer_dn.contains(&anchor.subject_name) {
return Some(anchor.service_name.clone());
}
}
}
None
}
pub(super) fn verify_chain_crypto(
chain: &[Certificate],
anchors: &[Certificate],
) -> Result<(), String> {
let Some(top) = chain.last() else {
return Err("empty chain".to_owned());
};
for cert in chain {
if !cert::is_currently_valid(cert) {
return Err(format!(
"certificate outside its validity period: {}",
cert::subject_dn(cert)
));
}
}
for (index, issuer) in chain.iter().enumerate().skip(1) {
if !cert::is_ca_cert(issuer) {
return Err(format!(
"chain certificate is not a valid CA: {}",
cert::subject_dn(issuer)
));
}
if let Some(max_intermediates) = cert::ca_path_len(issuer) {
let intermediates_below = index - 1;
if intermediates_below > usize::from(max_intermediates) {
return Err(format!(
"pathLenConstraint violated at {}: {intermediates_below} intermediate(s) below a limit of {max_intermediates}",
cert::subject_dn(issuer)
));
}
}
}
for pair in chain.windows(2) {
verify_signed_by(&pair[1], &pair[0])?;
}
for anchor in anchors {
if certs_equal(anchor, top) || verify_signed_by(anchor, top).is_ok() {
return Ok(());
}
}
Err("chain does not terminate at a trusted anchor".to_owned())
}
fn verify_signed_by(issuer: &Certificate, subject: &Certificate) -> Result<(), String> {
let key = VerifyingKey::try_from(issuer).map_err(|e| format!("unusable issuer key: {e}"))?;
key.verify(subject)
.map_err(|e| format!("signature verification failed: {e}"))
}
fn certs_equal(a: &Certificate, b: &Certificate) -> bool {
use der::Encode;
matches!((a.to_der(), b.to_der()), (Ok(x), Ok(y)) if x == y)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pki::tsl::TrustAnchor;
use std::time::Instant;
const ROOT_DER: &[u8] = include_bytes!("../testdata/root.der");
const INTER_DER: &[u8] = include_bytes!("../testdata/intermediate.der");
const LEAF_DER: &[u8] = include_bytes!("../testdata/leaf.der");
const LEAF_DIRECT_DER: &[u8] = include_bytes!("../testdata/leaf_direct.der");
const LEAF_AIA_DER: &[u8] = include_bytes!("../testdata/leaf_aia.der");
const ROOT2_DER: &[u8] = include_bytes!("../testdata/root2.der");
const NO_AKI_DER: &[u8] = include_bytes!("../testdata/no_aki.der");
fn cert(der: &[u8]) -> Certificate {
cert::parse_der(der).unwrap()
}
fn trust_store_with(anchor_der: &[u8], subject_name: &str, service_name: &str) -> TrustStore {
let anchor = TrustAnchor {
subject_name: subject_name.to_owned(),
service_name: service_name.to_owned(),
service_type: "CA/QC".to_owned(),
status: "granted".to_owned(),
cert_der: anchor_der.to_vec(),
};
TrustStore {
anchors: vec![anchor.clone()],
ca_anchors: vec![anchor],
scheme_operator: "Test Operator".to_owned(),
tsl_url: "https://example.com".to_owned(),
fetched_at: Instant::now(),
}
}
#[test]
fn builds_full_chain_from_pool() {
let pool = [cert(LEAF_DER), cert(INTER_DER), cert(ROOT_DER)];
assert_eq!(build_chain(&cert(LEAF_DER), &pool).len(), 3);
}
#[test]
fn self_signed_only_is_depth_one() {
assert_eq!(build_chain(&cert(ROOT_DER), &[cert(ROOT_DER)]).len(), 1);
}
#[test]
fn no_aki_stops_at_depth_one() {
assert_eq!(build_chain(&cert(NO_AKI_DER), &[cert(NO_AKI_DER)]).len(), 1);
}
#[test]
fn a_missing_issuer_stops_the_chain_rather_than_being_fetched() {
let pool = [cert(LEAF_AIA_DER), cert(ROOT_DER)];
assert_eq!(build_chain(&cert(LEAF_AIA_DER), &pool).len(), 1);
}
#[test]
fn matches_anchor_by_ski() {
let store = trust_store_with(ROOT_DER, "CN=Test Root CA", "TestRootCA");
assert_eq!(
find_matching_anchor(&[cert(ROOT_DER)], &store).as_deref(),
Some("TestRootCA")
);
}
#[test]
fn matches_anchor_by_issuer_dn_substring() {
let store = trust_store_with(ROOT_DER, "CN=Test Intermediate", "InterAnchor");
assert_eq!(
find_matching_anchor(&[cert(LEAF_DER)], &store).as_deref(),
Some("InterAnchor")
);
}
#[test]
fn no_matching_anchor_returns_none() {
let store = trust_store_with(ROOT_DER, "CN=Nonexistent CA", "X");
assert_eq!(find_matching_anchor(&[cert(LEAF_DER)], &store), None);
}
#[test]
fn empty_chain_matches_nothing() {
let store = trust_store_with(ROOT_DER, "CN=Test Root CA", "TestRootCA");
assert_eq!(find_matching_anchor(&[], &store), None);
}
#[test]
fn crypto_verifies_direct_leaf() {
assert!(verify_chain_crypto(&[cert(LEAF_DIRECT_DER)], &[cert(ROOT_DER)]).is_ok());
}
#[test]
fn crypto_verifies_three_level_chain() {
let chain = [cert(LEAF_DER), cert(INTER_DER)];
assert!(verify_chain_crypto(&chain, &[cert(ROOT_DER)]).is_ok());
}
#[test]
fn crypto_rejects_wrong_anchor() {
assert!(verify_chain_crypto(&[cert(LEAF_DIRECT_DER)], &[cert(ROOT2_DER)]).is_err());
}
#[test]
fn ca_role_is_enforced() {
assert!(cert::is_ca_cert(&cert(ROOT_DER)));
assert!(cert::is_ca_cert(&cert(INTER_DER)));
assert!(!cert::is_ca_cert(&cert(LEAF_DER)));
let err = verify_chain_crypto(&[cert(LEAF_DER), cert(LEAF_DER)], &[cert(ROOT_DER)]);
assert!(err.is_err());
}
}