use crate::{Error, RevocationChecker};
use der::{Decode as _, Encode as _};
use pkix_path::{names_match, SignatureVerifier, TrustAnchor};
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");
const OID_CRL_NUMBER: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.20");
const OID_DELTA_CRL_INDICATOR: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.27");
const OID_ISSUING_DISTRIBUTION_POINT: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.28");
const OID_KEY_USAGE_CRL: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.15");
#[derive(Clone, Debug)]
pub struct CrlChecker<V> {
crl: CertificateList,
delta_crl: Option<CertificateList>,
now_unix: u64,
verifier: V,
}
impl<V: SignatureVerifier> CrlChecker<V> {
pub fn new(crl_der: impl AsRef<[u8]>, now_unix: u64, verifier: V) -> crate::Result<Self> {
let crl = CertificateList::from_der(crl_der.as_ref())
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
Ok(Self {
crl,
delta_crl: None,
now_unix,
verifier,
})
}
pub fn with_delta(
base_der: impl AsRef<[u8]>,
delta_der: impl AsRef<[u8]>,
now_unix: u64,
verifier: V,
) -> crate::Result<Self> {
let base_crl = CertificateList::from_der(base_der.as_ref())
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
let delta_crl = CertificateList::from_der(delta_der.as_ref())
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
if has_delta_crl_indicator(&base_crl) {
return Err(Error::DeltaCrlBaseMismatch);
}
if !has_delta_crl_indicator(&delta_crl) {
return Err(Error::DeltaCrlBaseMismatch);
}
let delta_base_num = base_crl_number(&delta_crl)
.ok_or(Error::DeltaCrlBaseMismatch)? .map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
if !names_match(
&base_crl.tbs_cert_list.issuer,
&delta_crl.tbs_cert_list.issuer,
) {
return Err(Error::DeltaCrlBaseMismatch);
}
if let Some(base_num_result) = crl_number(&base_crl) {
let base_num = base_num_result.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
if delta_base_num > base_num {
return Err(Error::CrlNumberMismatch);
}
}
Ok(Self {
crl: base_crl,
delta_crl: Some(delta_crl),
now_unix,
verifier,
})
}
}
impl<V: SignatureVerifier> RevocationChecker for CrlChecker<V> {
fn check_revocation(&self, cert: &Certificate, issuer: &Certificate) -> crate::Result<()> {
let crl = &self.crl;
if !names_match(&crl.tbs_cert_list.issuer, &cert.tbs_certificate.issuer) {
return Err(Error::CrlIssuerMismatch);
}
if !names_match(&issuer.tbs_certificate.subject, &crl.tbs_cert_list.issuer) {
return Err(Error::CrlIssuerMismatch);
}
check_crl_sign(issuer)?;
let this_update = crl.tbs_cert_list.this_update.to_unix_duration().as_secs();
if self.now_unix < this_update {
return Err(Error::CrlExpired);
}
let next_update = crl
.tbs_cert_list
.next_update
.as_ref()
.ok_or(Error::CrlExpired)?;
if self.now_unix > next_update.to_unix_duration().as_secs() {
return Err(Error::CrlExpired);
}
let tbs_bytes = crl
.tbs_cert_list
.to_der()
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
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)?;
if let Some(idp) = parse_issuing_dp(crl)? {
if idp.only_contains_attribute_certs {
return Err(Error::OutOfScope(
crate::OutOfScopeReason::CrlOnlyAttributeCerts,
));
}
let cert_is_ca = cert_is_ca_cert(cert)?;
if idp.only_contains_user_certs && cert_is_ca {
return Err(Error::OutOfScope(crate::OutOfScopeReason::CrlOnlyUserCerts));
}
if idp.only_contains_ca_certs && !cert_is_ca {
return Err(Error::OutOfScope(crate::OutOfScopeReason::CrlOnlyCaCerts));
}
}
let delta_entries: Vec<RevokedCert> = if let Some(delta_crl) = &self.delta_crl {
if !names_match(&delta_crl.tbs_cert_list.issuer, &crl.tbs_cert_list.issuer) {
return Err(Error::CrlIssuerMismatch);
}
if !names_match(
&delta_crl.tbs_cert_list.issuer,
&cert.tbs_certificate.issuer,
) {
return Err(Error::CrlIssuerMismatch);
}
verify_delta_crl_and_collect(
delta_crl,
&self.verifier,
issuer
.tbs_certificate
.subject_public_key_info
.owned_to_ref(),
&issuer.tbs_certificate.subject,
self.now_unix,
)?
} else {
Vec::new()
};
let cert_serial = &cert.tbs_certificate.serial_number;
check_revocation_status(cert_serial, &delta_entries, crl)
}
fn check_revocation_against_anchor(
&self,
cert: &Certificate,
anchor: &TrustAnchor,
) -> crate::Result<()> {
let crl = &self.crl;
if !names_match(&crl.tbs_cert_list.issuer, &anchor.subject) {
return Err(Error::CrlIssuerMismatch);
}
if !names_match(&cert.tbs_certificate.issuer, &anchor.subject) {
return Err(Error::CrlIssuerMismatch);
}
let this_update = crl.tbs_cert_list.this_update.to_unix_duration().as_secs();
if self.now_unix < this_update {
return Err(Error::CrlExpired);
}
let next_update = crl
.tbs_cert_list
.next_update
.as_ref()
.ok_or(Error::CrlExpired)?;
if self.now_unix > next_update.to_unix_duration().as_secs() {
return Err(Error::CrlExpired);
}
let tbs_bytes = crl
.tbs_cert_list
.to_der()
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
self.verifier
.verify_signature(
crl.signature_algorithm.owned_to_ref(),
anchor.subject_public_key_info.owned_to_ref(),
&tbs_bytes,
crl.signature.raw_bytes(),
)
.map_err(|_| Error::CrlSignatureInvalid)?;
if let Some(idp) = parse_issuing_dp(crl)? {
if idp.only_contains_attribute_certs {
return Err(Error::OutOfScope(
crate::OutOfScopeReason::CrlOnlyAttributeCerts,
));
}
let cert_is_ca = cert_is_ca_cert(cert)?;
if idp.only_contains_user_certs && cert_is_ca {
return Err(Error::OutOfScope(crate::OutOfScopeReason::CrlOnlyUserCerts));
}
if idp.only_contains_ca_certs && !cert_is_ca {
return Err(Error::OutOfScope(crate::OutOfScopeReason::CrlOnlyCaCerts));
}
}
let delta_entries: Vec<RevokedCert> = if let Some(delta_crl) = &self.delta_crl {
if !names_match(&delta_crl.tbs_cert_list.issuer, &crl.tbs_cert_list.issuer) {
return Err(Error::CrlIssuerMismatch);
}
if !names_match(&delta_crl.tbs_cert_list.issuer, &anchor.subject) {
return Err(Error::CrlIssuerMismatch);
}
verify_delta_crl_and_collect(
delta_crl,
&self.verifier,
anchor.subject_public_key_info.owned_to_ref(),
&anchor.subject,
self.now_unix,
)?
} else {
Vec::new()
};
let cert_serial = &cert.tbs_certificate.serial_number;
check_revocation_status(cert_serial, &delta_entries, crl)
}
}
fn check_revocation_status(
cert_serial: &x509_cert::serial_number::SerialNumber,
delta_entries: &[RevokedCert],
crl: &CertificateList,
) -> crate::Result<()> {
if let Some(delta_entry) = delta_entries
.iter()
.find(|e| &e.serial_number == cert_serial)
{
let reason = extract_reason_code(delta_entry);
if reason == Some(CrlReason::RemoveFromCRL) {
return Ok(());
}
return Err(Error::Revoked {
serial: cert_serial.clone(),
reason_code: reason,
});
}
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 verify_delta_crl_and_collect<V: SignatureVerifier>(
delta_crl: &CertificateList,
verifier: &V,
issuer_spki: spki::SubjectPublicKeyInfoRef<'_>,
expected_issuer_name: &x509_cert::name::Name,
now_unix: u64,
) -> crate::Result<Vec<RevokedCert>> {
if !names_match(&delta_crl.tbs_cert_list.issuer, expected_issuer_name) {
return Err(Error::CrlIssuerMismatch);
}
let delta_this_update = delta_crl
.tbs_cert_list
.this_update
.to_unix_duration()
.as_secs();
if now_unix < delta_this_update {
return Err(Error::CrlExpired);
}
let delta_next_update = delta_crl
.tbs_cert_list
.next_update
.as_ref()
.ok_or(Error::CrlExpired)?;
if now_unix > delta_next_update.to_unix_duration().as_secs() {
return Err(Error::CrlExpired);
}
let delta_tbs_bytes = delta_crl
.tbs_cert_list
.to_der()
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
verifier
.verify_signature(
delta_crl.signature_algorithm.owned_to_ref(),
issuer_spki,
&delta_tbs_bytes,
delta_crl.signature.raw_bytes(),
)
.map_err(|_| Error::CrlSignatureInvalid)?;
Ok(delta_crl
.tbs_cert_list
.revoked_certificates
.clone()
.unwrap_or_default())
}
fn uint_to_u64(n: &der::asn1::Uint) -> Option<u64> {
let b = n.as_bytes();
if b.len() > 8 {
return None; }
let mut arr = [0u8; 8];
arr[8 - b.len()..].copy_from_slice(b);
Some(u64::from_be_bytes(arr))
}
fn crl_number(crl: &CertificateList) -> Option<Result<u64, der::Error>> {
let ext = crl
.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_CRL_NUMBER)?;
let result = der::asn1::Uint::from_der(ext.extn_value.as_bytes())
.and_then(|n| uint_to_u64(&n).ok_or_else(|| der::Error::from(der::ErrorKind::Overflow)));
Some(result)
}
fn has_delta_crl_indicator(crl: &CertificateList) -> bool {
crl.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.any(|e| e.extn_id == OID_DELTA_CRL_INDICATOR)
}
fn base_crl_number(crl: &CertificateList) -> Option<Result<u64, der::Error>> {
let ext = crl
.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_DELTA_CRL_INDICATOR)?;
let result = der::asn1::Uint::from_der(ext.extn_value.as_bytes())
.and_then(|n| uint_to_u64(&n).ok_or_else(|| der::Error::from(der::ErrorKind::Overflow)));
Some(result)
}
fn check_crl_sign(cert: &Certificate) -> crate::Result<()> {
use x509_cert::ext::pkix::KeyUsage;
let Some(ku_ext) = cert
.tbs_certificate
.extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_KEY_USAGE_CRL)
else {
return Ok(()); };
let ku = KeyUsage::from_der(ku_ext.extn_value.as_bytes())
.map_err(|e| Error::CrlParseError(crate::DerError(e)))?;
if ku.crl_sign() {
Ok(())
} else {
Err(Error::CrlSignMissing)
}
}
fn extract_reason_code(entry: &RevokedCert) -> Option<CrlReason> {
let exts = entry.crl_entry_extensions.as_ref()?;
exts.iter()
.find(|ext| ext.extn_id == OID_CRL_REASONS)
.and_then(|ext| CrlReason::from_der(ext.extn_value.as_bytes()).ok())
}
fn parse_issuing_dp(
crl: &CertificateList,
) -> crate::Result<Option<x509_cert::ext::pkix::crl::IssuingDistributionPoint>> {
use x509_cert::ext::pkix::crl::IssuingDistributionPoint;
crl.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_ISSUING_DISTRIBUTION_POINT)
.map(|e| {
IssuingDistributionPoint::from_der(e.extn_value.as_bytes())
.map_err(|err| Error::CrlParseError(crate::DerError(err)))
})
.transpose()
}
fn cert_is_ca_cert(cert: &Certificate) -> crate::Result<bool> {
pkix_path::cert_is_ca(cert).map_err(|_| Error::MalformedCertificate)
}