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");
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");
const OID_BASIC_CONSTRAINTS: der::asn1::ObjectIdentifier =
der::asn1::ObjectIdentifier::new_unwrap("2.5.29.19");
#[derive(Clone, Debug)]
pub struct CrlChecker<V> {
crl_der: Vec<u8>,
delta_crl_der: Option<Vec<u8>>,
now_unix: u64,
verifier: V,
}
impl<V: SignatureVerifier> CrlChecker<V> {
#[must_use]
pub fn new(crl_der: impl Into<Vec<u8>>, now_unix: u64, verifier: V) -> Self {
Self {
crl_der: crl_der.into(),
delta_crl_der: None,
now_unix,
verifier,
}
}
pub fn with_delta(
base_der: impl Into<Vec<u8>>,
delta_der: impl Into<Vec<u8>>,
now_unix: u64,
verifier: V,
) -> crate::Result<Self> {
let base_der = base_der.into();
let delta_der_bytes = delta_der.into();
let base_crl = CertificateList::from_der(&base_der).map_err(Error::CrlParseError)?;
let delta_crl =
CertificateList::from_der(&delta_der_bytes).map_err(Error::CrlParseError)?;
if has_delta_crl_indicator(&base_crl) {
return Err(Error::DeltaCrlBaseMismatch);
}
let delta_base_num = base_crl_number(&delta_crl);
if delta_base_num.is_none() {
return Err(Error::DeltaCrlBaseMismatch);
}
if !names_match(
&base_crl.tbs_cert_list.issuer,
&delta_crl.tbs_cert_list.issuer,
) {
return Err(Error::DeltaCrlBaseMismatch);
}
if let (Some(base_num), Some(db_num)) = (crl_number(&base_crl), delta_base_num) {
if db_num > base_num {
return Err(Error::CrlNumberMismatch);
}
}
Ok(Self {
crl_der: base_der,
delta_crl_der: Some(delta_der_bytes),
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);
}
if !names_match(&issuer.tbs_certificate.subject, &crl.tbs_cert_list.issuer) {
return Err(Error::CrlIssuerMismatch);
}
if !issuer_has_crl_sign(issuer) {
return Err(Error::CrlSignMissing);
}
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),
}
if let Some(idp) = parse_issuing_dp(&crl) {
if idp.only_contains_attribute_certs {
return Ok(());
}
let cert_is_ca = cert_is_ca_cert(cert);
if idp.only_contains_user_certs && cert_is_ca {
return Ok(());
}
if idp.only_contains_ca_certs && !cert_is_ca {
return Ok(());
}
}
let delta_entries: Vec<RevokedCert> = if let Some(ref delta_der) = self.delta_crl_der {
let delta_crl = CertificateList::from_der(delta_der).map_err(Error::CrlParseError)?;
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);
}
if !names_match(
&issuer.tbs_certificate.subject,
&delta_crl.tbs_cert_list.issuer,
) {
return Err(Error::CrlIssuerMismatch);
}
let delta_tbs_bytes = delta_crl
.tbs_cert_list
.to_der()
.map_err(Error::CrlParseError)?;
self.verifier
.verify_signature(
delta_crl.signature_algorithm.owned_to_ref(),
issuer
.tbs_certificate
.subject_public_key_info
.owned_to_ref(),
&delta_tbs_bytes,
delta_crl.signature.raw_bytes(),
)
.map_err(|_| Error::CrlSignatureInvalid)?;
let delta_this_update = delta_crl
.tbs_cert_list
.this_update
.to_unix_duration()
.as_secs();
if self.now_unix < delta_this_update {
return Err(Error::CrlExpired);
}
match &delta_crl.tbs_cert_list.next_update {
Some(nu) => {
if self.now_unix > nu.to_unix_duration().as_secs() {
return Err(Error::CrlExpired);
}
}
None => return Err(Error::CrlExpired),
}
delta_crl
.tbs_cert_list
.revoked_certificates
.unwrap_or_default()
} else {
Vec::new()
};
let cert_serial = &cert.tbs_certificate.serial_number;
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 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<u64> {
crl.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_CRL_NUMBER)
.and_then(|e| {
der::asn1::Uint::from_der(e.extn_value.as_bytes())
.ok()
.and_then(|n| uint_to_u64(&n))
})
}
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<u64> {
crl.tbs_cert_list
.crl_extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_DELTA_CRL_INDICATOR)
.and_then(|e| {
der::asn1::Uint::from_der(e.extn_value.as_bytes())
.ok()
.and_then(|n| uint_to_u64(&n))
})
}
fn issuer_has_crl_sign(cert: &Certificate) -> bool {
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 true; };
KeyUsage::from_der(ku_ext.extn_value.as_bytes())
.map(|ku| ku.crl_sign())
.unwrap_or(false) }
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,
) -> 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)
.and_then(|e| IssuingDistributionPoint::from_der(e.extn_value.as_bytes()).ok())
}
fn cert_is_ca_cert(cert: &Certificate) -> bool {
use x509_cert::ext::pkix::BasicConstraints;
cert.tbs_certificate
.extensions
.as_deref()
.unwrap_or(&[])
.iter()
.find(|e| e.extn_id == OID_BASIC_CONSTRAINTS)
.and_then(|e| BasicConstraints::from_der(e.extn_value.as_bytes()).ok())
.map(|bc| bc.ca)
.unwrap_or(false)
}