use der::asn1::BitString;
use der::Decode as _;
use pkix_path::{DefaultVerifier, TrustAnchor, ValidationPolicy};
use pkix_path_builder::{build_path, CertPool};
use x509_cert::Certificate;
const PKITS_NOW: u64 = 1_580_000_000;
fn pkits_cert(name: &str) -> Certificate {
let path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../pkix-path/tests/pkits/certs")
.join(format!("{name}.crt"));
let der_bytes = std::fs::read(&path)
.unwrap_or_else(|e| panic!("fixture not found at {}: {}", path.display(), e));
Certificate::from_der(&der_bytes).unwrap_or_else(|e| panic!("failed to parse cert {name}: {e}"))
}
fn pkits_trust_anchor() -> TrustAnchor {
TrustAnchor::from(&pkits_cert("TrustAnchorRootCertificate"))
}
#[test]
fn test_build_path_two_cert_chain() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let intermediate = pkits_cert("GoodCACert");
let anchor = pkits_trust_anchor();
let mut pool = CertPool::new();
pool.add(intermediate);
let path = build_path(&ee, &pool, std::slice::from_ref(&anchor))
.expect("build_path should succeed for PKITS §4.1.1 chain");
assert!(
path.len() >= 2,
"path should contain at least EE + intermediate"
);
let policy = ValidationPolicy::new(PKITS_NOW);
let verifier = DefaultVerifier;
pkix_path::validate_path(&path, &[anchor], &policy, &verifier)
.expect("validate_path should succeed on the built chain");
}
#[test]
fn test_build_path_shuffled_order() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let intermediate = pkits_cert("GoodCACert");
let anchor = pkits_trust_anchor();
let mut pool = CertPool::new();
pool.add(intermediate);
let path = build_path(&ee, &pool, std::slice::from_ref(&anchor))
.expect("build_path should succeed regardless of pool insertion order");
assert!(path.len() >= 2);
let policy = ValidationPolicy::new(PKITS_NOW);
let verifier = DefaultVerifier;
pkix_path::validate_path(&path, &[anchor], &policy, &verifier)
.expect("validate_path should succeed");
}
#[test]
fn test_build_path_no_path() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let anchor = pkits_trust_anchor();
let pool = CertPool::new();
let err =
build_path(&ee, &pool, &[anchor]).expect_err("build_path should fail with an empty pool");
assert!(
matches!(err, pkix_path_builder::Error::NoPathFound),
"expected NoPathFound, got {err}"
);
}
#[test]
fn test_build_path_self_signed_non_anchor_in_pool() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let intermediate = pkits_cert("GoodCACert");
let bad_ca = pkits_cert("BadSignedCACert");
let anchor = pkits_trust_anchor();
let mut pool = CertPool::new();
pool.add(intermediate);
pool.add(bad_ca);
let path = build_path(&ee, &pool, std::slice::from_ref(&anchor))
.expect("build_path must find the correct path ignoring the self-signed non-anchor");
let policy = pkix_path::ValidationPolicy::new(PKITS_NOW);
let verifier = pkix_path::DefaultVerifier;
pkix_path::validate_path(&path, &[anchor], &policy, &verifier)
.expect("validate_path should succeed on the built chain");
}
#[test]
fn test_build_path_wrong_pool() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let anchor = pkits_trust_anchor();
let wrong_cert = pkits_cert("BadSignedCACert");
let mut pool = CertPool::new();
pool.add(wrong_cert);
let err = build_path(&ee, &pool, &[anchor])
.expect_err("build_path should fail when pool contains unrelated cert");
assert!(
matches!(err, pkix_path_builder::Error::NoPathFound),
"expected NoPathFound, got {err}"
);
}
#[test]
fn test_build_path_duplicate_cert_in_pool_pruned_by_spki() {
let ee = pkits_cert("ValidCertificatePathTest1EE");
let intermediate = pkits_cert("GoodCACert");
let anchor = pkits_trust_anchor();
let mut pool = CertPool::new();
pool.add(intermediate.clone());
pool.add(intermediate);
let path = build_path(&ee, &pool, std::slice::from_ref(&anchor))
.expect("build_path must find path even with duplicate certs in pool");
let mut policy = ValidationPolicy::new(PKITS_NOW);
policy.enforce_key_usage = false;
pkix_path::validate_path(&path, &[anchor], &policy, &DefaultVerifier)
.expect("path returned with duplicate pool entries must be valid");
let spkis: Vec<_> = path
.iter()
.map(|c| {
use der::Encode as _;
let mut buf = Vec::new();
c.tbs_certificate
.subject_public_key_info
.encode_to_vec(&mut buf)
.unwrap();
buf
})
.collect();
let deduped_len = {
let mut seen = std::collections::HashSet::new();
spkis.iter().filter(|s| seen.insert(*s)).count()
};
assert_eq!(
spkis.len(),
deduped_len,
"path must not contain duplicate SPKI entries"
);
}
#[test]
fn test_build_path_adversarial_pool_budget_exceeded() {
const N: usize = 30;
const _: () = assert!(N <= u8::MAX as usize, "N must fit in u8");
let ee = pkits_cert("ValidCertificatePathTest1EE");
let template_ca = pkits_cert("GoodCACert");
let fake_anchor = TrustAnchor::from(&pkits_cert("BadSignedCACert"));
let mut pool = CertPool::new();
for i in 0..N {
let mut ca = template_ca.clone();
ca.tbs_certificate.issuer = ca.tbs_certificate.subject.clone();
ca.tbs_certificate
.subject_public_key_info
.subject_public_key = BitString::new(
0,
vec![u8::try_from(i).expect("loop bound N fits in u8"); 32],
)
.expect("BitString construction must succeed for valid parameters");
pool.add(ca);
}
let start = std::time::Instant::now();
let err = build_path(&ee, &pool, std::slice::from_ref(&fake_anchor))
.expect_err("adversarial pool should not find a valid path");
let elapsed = start.elapsed();
assert!(
matches!(err, pkix_path_builder::Error::BudgetExceeded),
"expected BudgetExceeded, got {err}"
);
assert!(
elapsed.as_secs() < 2,
"build_path took {elapsed:?}; budget enforcement must prevent exponential blowup"
);
}