use core::fmt::Debug;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::crypto::x509::error::CertificateValidationError;
use crate::crypto::x509::policy::CertificateValidation;
use crate::crypto::x509::Certificate;
#[cfg(feature = "std")]
use crate::crypto::x509::utils::validate_certificate_expiry;
#[cfg(feature = "std")]
use crate::der::Encode;
#[cfg(feature = "std")]
mod std_imports {
pub use std::collections::{HashMap, HashSet};
pub use std::sync::Arc;
pub use crate::cms::signed_data::SignerIdentifier;
pub use crate::crypto::hash::Digest;
pub use crate::crypto::hash::Sha3_256;
pub use crate::crypto::policy::VerificationPolicy;
pub use crate::crypto::x509::ext::pkix::{BasicConstraints, KeyUsage, KeyUsages};
pub use crate::crypto::x509::name::Name;
pub use crate::crypto::x509::utils::{certificate_extension, ensure_signature_algorithm_consistency};
pub use crate::der::oid::AssociatedOid;
}
#[cfg(feature = "std")]
use std_imports::*;
pub type Fingerprint = [u8; 32];
pub trait RevocationChecker: Debug + Send + Sync {
fn check(&self, issuer: &Certificate, cert: &Certificate) -> Result<(), CertificateValidationError>;
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NoRevocation;
impl RevocationChecker for NoRevocation {
fn check(&self, _issuer: &Certificate, _cert: &Certificate) -> Result<(), CertificateValidationError> {
Ok(())
}
}
#[cfg(feature = "std")]
#[derive(Debug, Default)]
pub struct StaticRevocationList {
fingerprints: HashSet<Fingerprint>,
serials: HashMap<Vec<u8>, HashSet<Vec<u8>>>,
}
#[cfg(feature = "std")]
impl StaticRevocationList {
pub fn with_fingerprint(mut self, fingerprint: Fingerprint) -> Self {
self.fingerprints.insert(fingerprint);
self
}
pub fn with_certificate(self, cert: &Certificate) -> Result<Self, CertificateValidationError> {
let fingerprint = CertificateTrustStore::to_fingerprint(cert)?;
Ok(self.with_fingerprint(fingerprint))
}
pub fn with_serial(mut self, issuer: &Name, serial: impl AsRef<[u8]>) -> Result<Self, CertificateValidationError> {
self.serials
.entry(issuer.to_der()?)
.or_default()
.insert(serial.as_ref().to_vec());
Ok(self)
}
}
#[cfg(feature = "std")]
impl RevocationChecker for StaticRevocationList {
fn check(&self, _issuer: &Certificate, cert: &Certificate) -> Result<(), CertificateValidationError> {
let fingerprint = CertificateTrustStore::to_fingerprint(cert)?;
if self.fingerprints.contains(&fingerprint) {
return Err(CertificateValidationError::CertificateRevoked);
}
if self.serials.is_empty() {
return Ok(());
}
let issuer_der = cert.tbs_certificate.issuer.to_der()?;
let revoked = self
.serials
.get(issuer_der.as_slice())
.is_some_and(|serials| serials.contains(cert.tbs_certificate.serial_number.as_bytes()));
if revoked {
return Err(CertificateValidationError::CertificateRevoked);
}
Ok(())
}
}
#[cfg(feature = "std")]
pub trait CertificateTrust: CertificateValidation + Debug + Send + Sync {
fn is_trusted(&self, cert: &Certificate) -> bool;
fn verify_chain(&self, chain: &[Certificate]) -> Result<(), CertificateValidationError>;
fn find_by_signer_info(&self, signer_info: &crate::SignerInfo) -> Option<&Certificate>;
fn to_policy_ref(&self) -> &dyn VerificationPolicy;
}
#[cfg(not(feature = "std"))]
pub trait CertificateTrust: CertificateValidation + Debug + Send + Sync {
fn is_trusted(&self, cert: &Certificate) -> bool;
fn verify_chain(&self, chain: &[Certificate]) -> Result<(), CertificateValidationError>;
}
pub trait TrustBuilder: Sized {
type Store: CertificateTrust;
fn with_chain(self, chain: Vec<Certificate>) -> Result<Self, CertificateValidationError>;
fn with_certificate(self, cert: Certificate) -> Result<Self, CertificateValidationError>;
fn build(self) -> Self::Store;
}
#[cfg(feature = "std")]
fn ensure_critical_extensions_processed(cert: &Certificate) -> Result<(), CertificateValidationError> {
let Some(extensions) = cert.tbs_certificate.extensions.as_ref() else {
return Ok(());
};
for extension in extensions {
let processed = extension.extn_id == BasicConstraints::OID || extension.extn_id == KeyUsage::OID;
if extension.critical && !processed {
return Err(CertificateValidationError::UnprocessedCriticalExtension(extension.extn_id));
}
}
Ok(())
}
#[cfg(feature = "std")]
fn ensure_terminal_is_end_entity(path: &[&Certificate]) -> Result<(), CertificateValidationError> {
let [_, .., terminal] = path else {
return Ok(());
};
match certificate_extension::<BasicConstraints>(terminal)? {
Some(basic_constraints) if basic_constraints.ca => Err(CertificateValidationError::EndEntityIsCa),
_ => Ok(()),
}
}
#[cfg(feature = "std")]
fn ensure_issuer_is_ca(issuer: &Certificate) -> Result<(), CertificateValidationError> {
match certificate_extension::<BasicConstraints>(issuer)? {
Some(basic_constraints) if basic_constraints.ca => {}
_ => return Err(CertificateValidationError::IssuerNotCa),
}
if let Some(key_usage) = certificate_extension::<KeyUsage>(issuer)? {
if !key_usage.0.contains(KeyUsages::KeyCertSign) {
return Err(CertificateValidationError::MissingKeyCertSign);
}
}
Ok(())
}
#[cfg(feature = "std")]
fn ensure_path_len(chain: &[&Certificate]) -> Result<(), CertificateValidationError> {
for (index, cert) in chain.iter().enumerate() {
let Some(basic_constraints) = certificate_extension::<BasicConstraints>(cert)? else {
continue;
};
let Some(max_intermediates) = basic_constraints.path_len_constraint else {
continue;
};
let intermediates_below = chain.len().saturating_sub(index + 2);
if intermediates_below as u64 > u64::from(max_intermediates) {
return Err(CertificateValidationError::PathLenExceeded);
}
}
Ok(())
}
pub type Skid = [u8; 20];
#[cfg(feature = "std")]
pub struct CertificateTrustStore {
fingerprints: HashSet<Fingerprint>,
certificates: HashMap<Fingerprint, Certificate>,
skid_index: HashMap<Skid, Fingerprint>,
policy: Arc<dyn VerificationPolicy>,
revocation: Arc<dyn RevocationChecker>,
}
#[cfg(feature = "std")]
impl CertificateTrustStore {
pub fn to_fingerprint(cert: &Certificate) -> Result<Fingerprint, CertificateValidationError> {
let der_bytes = cert.to_der()?;
let hash = Sha3_256::digest(&der_bytes);
let mut fp = [0u8; 32];
fp.copy_from_slice(hash.as_ref());
Ok(fp)
}
pub fn to_certificate_ref(&self, fingerprint: &Fingerprint) -> Option<&Certificate> {
self.certificates.get(fingerprint)
}
pub fn len(&self) -> usize {
self.fingerprints.len()
}
pub fn is_empty(&self) -> bool {
self.fingerprints.is_empty()
}
fn validate_path(&self, path: &[&Certificate]) -> Result<(), CertificateValidationError> {
path.iter().try_for_each(|cert| validate_certificate_expiry(cert))?;
path.iter().try_for_each(|cert| ensure_critical_extensions_processed(cert))?;
ensure_terminal_is_end_entity(path)?;
path.iter().try_for_each(|cert| ensure_signature_algorithm_consistency(cert))?;
path.windows(2).try_for_each(|pair| {
let (issuer, cert) = (pair[0], pair[1]);
if cert.tbs_certificate.issuer != issuer.tbs_certificate.subject {
return Err(CertificateValidationError::InvalidChain);
}
ensure_issuer_is_ca(issuer)?;
let algorithm_oid = cert.signature_algorithm.oid;
let public_key_der = issuer.tbs_certificate.subject_public_key_info.to_der()?;
let message = cert.tbs_certificate.to_der()?;
let signature_bytes = cert.signature.raw_bytes();
self.policy
.verify_signature(&algorithm_oid, &public_key_der, &message, signature_bytes)
})?;
if let Some(anchor) = path.first() {
self.revocation.check(anchor, anchor)?;
}
path.windows(2).try_for_each(|pair| self.revocation.check(pair[0], pair[1]))?;
ensure_path_len(path)
}
}
#[cfg(feature = "std")]
impl Debug for CertificateTrustStore {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CertificateTrustStore")
.field("fingerprints", &self.fingerprints.len())
.field("certificates", &self.certificates.len())
.finish_non_exhaustive()
}
}
#[cfg(feature = "std")]
impl CertificateValidation for CertificateTrustStore {
fn evaluate(&self, cert: &Certificate) -> Result<(), CertificateValidationError> {
let mut path: Vec<&Certificate> = Vec::new();
let mut visited: HashSet<Fingerprint> = HashSet::new();
visited.insert(Self::to_fingerprint(cert)?);
path.push(cert);
let mut current = cert;
loop {
let Some(issuer) = self
.certificates
.values()
.find(|c| c.tbs_certificate.subject == current.tbs_certificate.issuer)
else {
break;
};
if !visited.insert(Self::to_fingerprint(issuer)?) {
break;
}
path.push(issuer);
current = issuer;
}
if !self.is_trusted(current) {
return Err(CertificateValidationError::CertificateNotTrusted);
}
path.reverse();
self.validate_path(&path)
}
}
#[cfg(feature = "std")]
impl CertificateTrust for CertificateTrustStore {
fn is_trusted(&self, cert: &Certificate) -> bool {
match Self::to_fingerprint(cert) {
Ok(fp) => self.fingerprints.contains(&fp),
Err(_) => false,
}
}
fn verify_chain(&self, chain: &[Certificate]) -> Result<(), CertificateValidationError> {
let root = chain.first().ok_or(CertificateValidationError::EmptyChain)?;
if !self.is_trusted(root) {
return Err(CertificateValidationError::CertificateNotTrusted);
}
let path: Vec<&Certificate> = chain.iter().collect();
self.validate_path(&path)
}
fn find_by_signer_info(&self, signer_info: &crate::SignerInfo) -> Option<&Certificate> {
match &signer_info.sid {
SignerIdentifier::IssuerAndSerialNumber(ias) => {
self.certificates.values().find(|cert| {
cert.tbs_certificate.issuer == ias.issuer && cert.tbs_certificate.serial_number == ias.serial_number
})
}
SignerIdentifier::SubjectKeyIdentifier(skid) => {
let skid_bytes = skid.0.as_bytes();
(skid_bytes.len() == 20)
.then(|| {
let mut key = [0u8; 20];
key.copy_from_slice(skid_bytes);
key
})
.and_then(|key| self.skid_index.get(&key))
.and_then(|fp| self.certificates.get(fp))
}
}
}
fn to_policy_ref(&self) -> &dyn VerificationPolicy {
&*self.policy
}
}
#[cfg(feature = "std")]
pub struct CertificateTrustBuilder<D: Digest> {
fingerprints: HashSet<Fingerprint>,
certificates: HashMap<Fingerprint, Certificate>,
skid_index: HashMap<Skid, Fingerprint>,
policy: Arc<dyn VerificationPolicy>,
revocation: Arc<dyn RevocationChecker>,
_digest: core::marker::PhantomData<D>,
}
#[cfg(feature = "std")]
impl<D: Digest, P: VerificationPolicy + 'static> From<P> for CertificateTrustBuilder<D> {
fn from(policy: P) -> Self {
Self {
fingerprints: HashSet::new(),
certificates: HashMap::new(),
skid_index: HashMap::new(),
policy: Arc::new(policy),
revocation: Arc::new(NoRevocation),
_digest: core::marker::PhantomData,
}
}
}
#[cfg(feature = "std")]
impl<D: Digest> CertificateTrustBuilder<D> {
pub fn with_revocation_checker(mut self, checker: impl RevocationChecker + 'static) -> Self {
self.revocation = Arc::new(checker);
self
}
fn add_certificate(&mut self, cert: Certificate) -> Result<(), CertificateValidationError> {
let fp = CertificateTrustStore::to_fingerprint(&cert)?;
let spki_der = cert.tbs_certificate.subject_public_key_info.to_der()?;
let hash = D::digest(&spki_der);
let mut skid = [0u8; 20];
skid.copy_from_slice(crate::crypto::x509::utils::skid_window(hash.as_ref())?);
if let Some(existing_fp) = self.skid_index.get(&skid) {
if *existing_fp != fp {
return Err(CertificateValidationError::SkidCollision);
}
}
self.fingerprints.insert(fp);
self.skid_index.insert(skid, fp);
self.certificates.insert(fp, cert);
Ok(())
}
}
#[cfg(feature = "std")]
impl<D: Digest> TrustBuilder for CertificateTrustBuilder<D> {
type Store = CertificateTrustStore;
fn with_chain(mut self, chain: Vec<Certificate>) -> Result<Self, CertificateValidationError> {
if chain.is_empty() {
return Err(CertificateValidationError::EmptyChain);
}
chain.iter().try_for_each(validate_certificate_expiry)?;
chain.windows(2).try_for_each(|pair| {
let (issuer, cert) = (&pair[0], &pair[1]);
(cert.tbs_certificate.issuer == issuer.tbs_certificate.subject)
.then_some(())
.ok_or(CertificateValidationError::InvalidChain)
})?;
chain.into_iter().try_for_each(|cert| self.add_certificate(cert))?;
Ok(self)
}
fn with_certificate(mut self, cert: Certificate) -> Result<Self, CertificateValidationError> {
validate_certificate_expiry(&cert)?;
self.add_certificate(cert)?;
Ok(self)
}
fn build(self) -> Self::Store {
CertificateTrustStore {
fingerprints: self.fingerprints,
certificates: self.certificates,
skid_index: self.skid_index,
policy: self.policy,
revocation: self.revocation,
}
}
}
#[cfg(feature = "std")]
impl<D: Digest> Debug for CertificateTrustBuilder<D> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CertificateTrustBuilder")
.field("fingerprints", &self.fingerprints.len())
.field("certificates", &self.certificates.len())
.field("skid_index", &self.skid_index.len())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::policy::Secp256k1Policy;
use crate::crypto::sign::ecdsa::SigningKey;
use crate::crypto::sign::Signatory;
use crate::testing::create_test_signing_key;
use crate::testing::utils::{
ca_extensions, create_test_certificate, create_test_certificate_chain, TestCertificateChain,
};
type TestResult = Result<(), Box<dyn std::error::Error>>;
type TestBuilder = CertificateTrustBuilder<Sha3_256>;
#[derive(Debug, Clone, Copy)]
enum StoreCerts {
None,
Root,
RootAndIntermediate,
}
#[derive(Debug, Clone, Copy)]
enum EvalTarget {
Root,
Intermediate,
Leaf,
}
fn build_store(
chain: &TestCertificateChain,
certs: StoreCerts,
) -> Result<CertificateTrustStore, CertificateValidationError> {
let builder: TestBuilder = Secp256k1Policy.into();
let builder = match certs {
StoreCerts::None => builder,
StoreCerts::Root => {
let certificate = chain.root.clone();
builder.with_certificate(certificate)?
}
StoreCerts::RootAndIntermediate => {
let root = chain.root.clone();
let intermediate = chain.intermediate.clone();
builder.with_certificate(root)?.with_certificate(intermediate)?
}
};
Ok(builder.build())
}
fn target_cert(chain: &TestCertificateChain, target: EvalTarget) -> &Certificate {
match target {
EvalTarget::Root => &chain.root,
EvalTarget::Intermediate => &chain.intermediate,
EvalTarget::Leaf => &chain.leaf,
}
}
#[test]
fn fingerprint_is_32_bytes() -> TestResult {
let cert = create_test_certificate(&create_test_signing_key());
assert_eq!(CertificateTrustStore::to_fingerprint(&cert)?.len(), 32);
Ok(())
}
#[test]
fn is_trusted_matches_fingerprint() -> TestResult {
let cert = create_test_certificate(&create_test_signing_key());
let certificate = cert.clone();
let store = TestBuilder::from(Secp256k1Policy).with_certificate(certificate)?.build();
assert!(store.is_trusted(&cert));
assert!(!store.is_trusted(&create_test_certificate(&SigningKey::from_bytes(&[2u8; 32].into())?)));
Ok(())
}
#[test]
fn builder_validates_chain_structure() -> TestResult {
let chain = create_test_certificate_chain()?;
let chain = vec![chain.root, chain.intermediate, chain.leaf];
assert!(TestBuilder::from(Secp256k1Policy).with_chain(chain).is_ok());
Ok(())
}
const EVALUATE_CASES: &[(StoreCerts, EvalTarget, bool)] = &[
(StoreCerts::Root, EvalTarget::Root, true),
(StoreCerts::Root, EvalTarget::Intermediate, false),
(StoreCerts::RootAndIntermediate, EvalTarget::Leaf, true),
(StoreCerts::Root, EvalTarget::Leaf, false),
(StoreCerts::None, EvalTarget::Leaf, false),
];
#[test]
fn evaluate_chain_walking() -> TestResult {
let chain = create_test_certificate_chain()?;
for (store_certs, eval_target, should_succeed) in EVALUATE_CASES {
let store = build_store(&chain, *store_certs)?;
let cert = target_cert(&chain, *eval_target);
let result = store.evaluate(cert);
assert_eq!(
result.is_ok(),
*should_succeed,
"store={store_certs:?} target={eval_target:?}: expected {should_succeed}, got {result:?}"
);
}
Ok(())
}
#[test]
fn evaluate_rejects_cross_chain_cert() -> TestResult {
let store = TestBuilder::from(Secp256k1Policy)
.with_certificate(create_test_certificate(&create_test_signing_key()))?
.build();
let other_chain = create_test_certificate_chain()?;
assert!(store.evaluate(&other_chain.leaf).is_err());
Ok(())
}
#[test]
fn verify_chain_cases() -> TestResult {
let chain = create_test_certificate_chain()?;
let cases: &[(StoreCerts, &[&Certificate], bool)] = &[
(StoreCerts::Root, &[], false),
(StoreCerts::None, &[&chain.root], false),
(StoreCerts::Root, &[&chain.root], true),
(StoreCerts::Root, &[&chain.root, &chain.intermediate, &chain.leaf], true),
];
for (store_certs, chain_slice, should_succeed) in cases {
let store = build_store(&chain, *store_certs)?;
let chain_vec: Vec<_> = chain_slice.iter().map(|c| (*c).clone()).collect();
let result: Result<(), CertificateValidationError> = store.verify_chain(&chain_vec);
assert_eq!(
result.is_ok(),
*should_succeed,
"verify_chain: store={store_certs:?} chain_len={}: expected {should_succeed}, got {result:?}",
chain_slice.len()
);
}
Ok(())
}
const ISSUER_CONSTRAINT_CASES: &[(bool, bool, Option<u8>, CertificateValidationError)] = &[
(false, true, None, CertificateValidationError::IssuerNotCa),
(true, false, None, CertificateValidationError::MissingKeyCertSign),
(true, true, Some(0), CertificateValidationError::PathLenExceeded),
];
#[test]
fn verify_chain_enforces_issuer_constraints() -> TestResult {
for (ca, key_cert_sign, path_len, expected) in ISSUER_CONSTRAINT_CASES {
let chain = create_test_certificate_chain()?;
let mut root = chain.root.clone();
root.tbs_certificate.extensions = Some(ca_extensions(*ca, *key_cert_sign, *path_len));
let certificate = root.clone();
let store = TestBuilder::from(Secp256k1Policy).with_certificate(certificate)?.build();
let result = store.verify_chain(&[root, chain.intermediate, chain.leaf]);
assert!(matches!(result, Err(ref e) if core::mem::discriminant(e) == core::mem::discriminant(expected)));
}
Ok(())
}
#[test]
fn evaluate_enforces_path_len_constraint() -> TestResult {
let chain = create_test_certificate_chain()?;
let mut root = chain.root.clone();
root.tbs_certificate.extensions = Some(ca_extensions(true, true, Some(0)));
let store = TestBuilder::from(Secp256k1Policy)
.with_certificate(root)?
.with_certificate(chain.intermediate)?
.build();
assert!(matches!(
store.evaluate(&chain.leaf),
Err(CertificateValidationError::PathLenExceeded)
));
Ok(())
}
fn opaque_extension(oid: &str, critical: bool) -> crate::x509::ext::Extension {
crate::x509::ext::Extension {
extn_id: crate::der::oid::ObjectIdentifier::new_unwrap(oid),
critical,
extn_value: crate::der::asn1::OctetString::new(Vec::new()).unwrap(),
}
}
#[test]
fn rejects_unknown_critical_extension() {
let mut cert = create_test_certificate(&create_test_signing_key());
cert.tbs_certificate.extensions = Some(vec![opaque_extension("2.5.29.30", true)]);
assert!(matches!(
ensure_critical_extensions_processed(&cert),
Err(CertificateValidationError::UnprocessedCriticalExtension(_))
));
}
#[test]
fn accepts_unknown_noncritical_extension() {
let mut cert = create_test_certificate(&create_test_signing_key());
cert.tbs_certificate.extensions = Some(vec![opaque_extension("2.5.29.30", false)]);
assert!(ensure_critical_extensions_processed(&cert).is_ok());
}
#[test]
fn accepts_processed_critical_extensions() {
let mut cert = create_test_certificate(&create_test_signing_key());
cert.tbs_certificate.extensions = Some(ca_extensions(true, true, None));
assert!(ensure_critical_extensions_processed(&cert).is_ok());
}
#[test]
fn verify_chain_rejects_unknown_critical_extension() -> TestResult {
let chain = create_test_certificate_chain()?;
let mut leaf = chain.leaf.clone();
leaf.tbs_certificate.extensions = Some(vec![opaque_extension("2.5.29.30", true)]);
let store = build_store(&chain, StoreCerts::Root)?;
let result = store.verify_chain(&[chain.root, chain.intermediate, leaf]);
assert!(matches!(
result,
Err(CertificateValidationError::UnprocessedCriticalExtension(_))
));
Ok(())
}
#[test]
fn terminal_with_ca_bit_rejected() -> TestResult {
let chain = create_test_certificate_chain()?;
let path = [&chain.root, &chain.intermediate];
assert!(matches!(
ensure_terminal_is_end_entity(&path),
Err(CertificateValidationError::EndEntityIsCa)
));
Ok(())
}
#[test]
fn terminal_without_ca_bit_accepted() -> TestResult {
let chain = create_test_certificate_chain()?;
let path = [&chain.root, &chain.intermediate, &chain.leaf];
assert!(ensure_terminal_is_end_entity(&path).is_ok());
Ok(())
}
#[test]
fn single_certificate_path_exempt_from_ca_bit_check() -> TestResult {
let chain = create_test_certificate_chain()?;
let path = [&chain.root];
assert!(ensure_terminal_is_end_entity(&path).is_ok());
Ok(())
}
fn build_store_with_revocation(
chain: &TestCertificateChain,
revocation: StaticRevocationList,
) -> Result<CertificateTrustStore, CertificateValidationError> {
let root = chain.root.clone();
Ok(TestBuilder::from(Secp256k1Policy)
.with_revocation_checker(revocation)
.with_certificate(root)?
.build())
}
#[test]
fn static_revocation_list_passes_unlisted_certificate() -> TestResult {
let chain = create_test_certificate_chain()?;
let revocation = StaticRevocationList::default().with_certificate(&chain.intermediate)?;
assert!(revocation.check(&chain.intermediate, &chain.leaf).is_ok());
Ok(())
}
#[test]
fn verify_chain_rejects_leaf_revoked_by_fingerprint() -> TestResult {
let chain = create_test_certificate_chain()?;
let revocation = StaticRevocationList::default().with_certificate(&chain.leaf)?;
let store = build_store_with_revocation(&chain, revocation)?;
let result = store.verify_chain(&[chain.root, chain.intermediate, chain.leaf]);
assert!(matches!(result, Err(CertificateValidationError::CertificateRevoked)));
Ok(())
}
#[test]
fn verify_chain_rejects_leaf_revoked_by_serial() -> TestResult {
let chain = create_test_certificate_chain()?;
let issuer = chain.leaf.tbs_certificate.issuer.clone();
let serial = chain.leaf.tbs_certificate.serial_number.as_bytes().to_vec();
let revocation = StaticRevocationList::default().with_serial(&issuer, serial)?;
let store = build_store_with_revocation(&chain, revocation)?;
let result = store.verify_chain(&[chain.root, chain.intermediate, chain.leaf]);
assert!(matches!(result, Err(CertificateValidationError::CertificateRevoked)));
Ok(())
}
#[test]
fn serial_revocation_is_scoped_to_issuer() -> TestResult {
let chain = create_test_certificate_chain()?;
let other_issuer = chain.leaf.tbs_certificate.subject.clone();
let serial = chain.leaf.tbs_certificate.serial_number.as_bytes().to_vec();
let revocation = StaticRevocationList::default().with_serial(&other_issuer, serial)?;
let store = build_store_with_revocation(&chain, revocation)?;
let result = store.verify_chain(&[chain.root, chain.intermediate, chain.leaf]);
assert!(result.is_ok());
Ok(())
}
#[test]
fn verify_chain_rejects_revoked_anchor() -> TestResult {
let chain = create_test_certificate_chain()?;
let revocation = StaticRevocationList::default().with_certificate(&chain.root)?;
let store = build_store_with_revocation(&chain, revocation)?;
let result = store.verify_chain(&[chain.root]);
assert!(matches!(result, Err(CertificateValidationError::CertificateRevoked)));
Ok(())
}
#[test]
fn rejects_algorithm_identifier_mismatch() -> TestResult {
let chain = create_test_certificate_chain()?;
let mut leaf = chain.leaf.clone();
leaf.signature_algorithm.oid = crate::oids::SIGNER_ECDSA_WITH_SHA256;
let walk_store = build_store(&chain, StoreCerts::RootAndIntermediate)?;
assert!(matches!(
walk_store.evaluate(&leaf),
Err(CertificateValidationError::AlgorithmMismatch)
));
let chain_store = build_store(&chain, StoreCerts::Root)?;
let result = chain_store.verify_chain(&[chain.root, chain.intermediate, leaf]);
assert!(matches!(result, Err(CertificateValidationError::AlgorithmMismatch)));
Ok(())
}
#[test]
fn find_by_signer_info_skid() -> TestResult {
let key = create_test_signing_key();
let cert = create_test_certificate(&key);
let certificate = cert.clone();
let store = TestBuilder::from(Secp256k1Policy).with_certificate(certificate)?.build();
let signer_info = key.to_signer_info(b"test")?;
let Some(found) = store.find_by_signer_info(&signer_info) else {
return Err(crate::testing::error::TestingError::InvariantViolated.into());
};
assert_eq!(
CertificateTrustStore::to_fingerprint(found)?,
CertificateTrustStore::to_fingerprint(&cert)?
);
Ok(())
}
#[test]
fn find_by_signer_info_not_found() -> TestResult {
let store = TestBuilder::from(Secp256k1Policy)
.with_certificate(create_test_certificate(&create_test_signing_key()))?
.build();
let other_key = SigningKey::from_bytes(&[99u8; 32].into())?;
let signer_info = other_key.to_signer_info(b"test")?;
assert!(store.find_by_signer_info(&signer_info).is_none());
Ok(())
}
}