use crate::{Error, RevocationChecker};
use der::{Decode as _, Encode as _};
use pkix_path::{names_match, SignatureVerifier};
use spki::der::referenced::OwnedToRef as _;
use x509_cert::{
crl::{CertificateList, RevokedCert},
ext::pkix::crl::CrlReason,
Certificate,
};
const OID_CRL_REASONS: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.21");
pub struct CrlChecker<V> {
crl_der: Vec<u8>,
now_unix: u64,
verifier: V,
}
impl<V: SignatureVerifier> CrlChecker<V> {
pub fn new(crl_der: impl Into<Vec<u8>>, now_unix: u64, verifier: V) -> Self {
Self {
crl_der: crl_der.into(),
now_unix,
verifier,
}
}
}
impl<V: SignatureVerifier> RevocationChecker for CrlChecker<V> {
fn check_revocation(&self, cert: &Certificate, issuer: &Certificate) -> crate::Result<()> {
let crl = CertificateList::from_der(&self.crl_der).map_err(Error::CrlParseError)?;
if !names_match(&crl.tbs_cert_list.issuer, &cert.tbs_certificate.issuer) {
return Err(Error::CrlIssuerMismatch);
}
let tbs_bytes = crl.tbs_cert_list.to_der().map_err(Error::CrlParseError)?;
self.verifier
.verify_signature(
crl.signature_algorithm.owned_to_ref(),
issuer
.tbs_certificate
.subject_public_key_info
.owned_to_ref(),
&tbs_bytes,
crl.signature.raw_bytes(),
)
.map_err(|_| Error::CrlSignatureInvalid)?;
let this_update = crl.tbs_cert_list.this_update.to_unix_duration().as_secs();
if self.now_unix < this_update {
return Err(Error::CrlExpired);
}
match &crl.tbs_cert_list.next_update {
Some(next_update) => {
if self.now_unix > next_update.to_unix_duration().as_secs() {
return Err(Error::CrlExpired);
}
}
None => return Err(Error::CrlExpired),
}
let cert_serial = &cert.tbs_certificate.serial_number;
if let Some(revoked) = &crl.tbs_cert_list.revoked_certificates {
if let Some(entry) = revoked.iter().find(|e| &e.serial_number == cert_serial) {
return Err(Error::Revoked {
serial: cert_serial.clone(),
reason_code: extract_reason_code(entry),
});
}
}
Ok(())
}
}
fn extract_reason_code(entry: &RevokedCert) -> Option<CrlReason> {
let exts = entry.crl_entry_extensions.as_ref()?;
for ext in exts.iter() {
if ext.extn_id == OID_CRL_REASONS {
return CrlReason::from_der(ext.extn_value.as_bytes()).ok();
}
}
None
}