pub use crate::data_model::common::{Asn1TimeEncoding, Asn1TimeUtc, BigUnsigned}; use crate::data_model::oid::{ OID_AUTHORITY_KEY_IDENTIFIER, OID_CRL_NUMBER, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SUBJECT_KEY_IDENTIFIER, }; use x509_parser::extensions::{AuthorityKeyIdentifier, ParsedExtension, X509Extension}; use x509_parser::prelude::FromDer; use x509_parser::prelude::X509Version; use x509_parser::revocation_list::CertificateRevocationList; use x509_parser::certificate::X509Certificate; use x509_parser::x509::SubjectPublicKeyInfo; use x509_parser::x509::AlgorithmIdentifier; #[derive(Clone, Debug, PartialEq, Eq)] pub struct RevokedCert { pub serial_number: BigUnsigned, pub revocation_date: Asn1TimeUtc, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct CrlExtensions { pub authority_key_identifier: Vec, pub crl_number: BigUnsigned, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct RpkixCrl { pub raw_der: Vec, pub version: u32, pub issuer_dn: String, pub signature_algorithm_oid: String, pub this_update: Asn1TimeUtc, pub next_update: Asn1TimeUtc, pub revoked_certs: Vec, pub extensions: CrlExtensions, } #[derive(Debug, thiserror::Error)] pub enum CrlDecodeError { #[error("X.509 CRL parse error: {0} (RFC 5280 §5.1; RFC 6487 §5)")] Parse(String), #[error("trailing bytes after CRL DER: {0} bytes (DER; RFC 5280 §5.1)")] TrailingBytes(usize), #[error("CRL version must be v2, got {0:?} (RFC 5280 §5.1; RFC 6487 §5)")] InvalidVersion(Option), #[error("CRL signatureAlgorithm must be sha256WithRSAEncryption ({OID_SHA256_WITH_RSA_ENCRYPTION}), got {0} (RFC 6487 §5; RFC 7935 §2)")] InvalidSignatureAlgorithm(String), #[error("CRL signature algorithm parameters must be absent or NULL (RFC 5280 §4.1.1.2; RFC 7935 §2)")] InvalidSignatureAlgorithmParameters, #[error("CRL signatureAlgorithm must match TBSCertList.signature (RFC 5280 §5.1)")] SignatureAlgorithmMismatch, #[error("CRL extensions must be exactly two (AKI + CRLNumber), got {0} (RFC 9829 §3.1)")] InvalidExtensionsCount(usize), #[error("unsupported CRL extension OID {0} (RFC 9829 §3.1)")] UnsupportedExtension(String), #[error("duplicate CRL extension OID {0} (RFC 5280 §4.2; RFC 9829 §3.1)")] DuplicateExtension(String), #[error("AuthorityKeyIdentifier must contain keyIdentifier (RFC 5280 §5.2.1; RFC 9829 §3.1)")] AkiMissingKeyIdentifier, #[error("AuthorityKeyIdentifier must not contain authorityCertIssuer or authorityCertSerialNumber (RFC 5280 §5.2.1; RFC 9829 §3.1)")] AkiHasOtherFields, #[error("CRLNumber must be non-critical (RFC 9829 §3.1; RFC 5280 §5.2.3)")] CrlNumberCritical, #[error("CRLNumber out of range (must fit in 0..2^159-1) (RFC 9829 §3.1)")] CrlNumberOutOfRange, #[error("CRL entry extensions must not be present (RFC 6487 §5; RFC 5280 §5.1)")] EntryExtensionsNotAllowed, #[error("CRL nextUpdate must be present (RFC 5280 §5.1.2.5; RFC 6487 §5)")] NextUpdateMissing, #[error("{field} time encoding invalid for year {year}: got {encoding:?} (RFC 5280 §5.1.2.4-§5.1.2.6)")] InvalidTimeEncoding { field: &'static str, year: i32, encoding: Asn1TimeEncoding, }, } impl RpkixCrl { /// Decode a DER-encoded X.509 v2 CRL and enforce the RPKI profile constraints from /// `specs/prepare/data_models/04_crl.md` (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1). pub fn decode_der(der: &[u8]) -> Result { let (rem, crl) = CertificateRevocationList::from_der(der) .map_err(|e| CrlDecodeError::Parse(e.to_string()))?; if !rem.is_empty() { return Err(CrlDecodeError::TrailingBytes(rem.len())); } let version = match crl.version() { Some(X509Version::V2) => 2, Some(v) => return Err(CrlDecodeError::InvalidVersion(Some(v.0))), None => return Err(CrlDecodeError::InvalidVersion(None)), }; let sig_oid = crl.signature_algorithm.algorithm.to_id_string(); let tbs_sig_oid = crl.tbs_cert_list.signature.algorithm.to_id_string(); if sig_oid != tbs_sig_oid { return Err(CrlDecodeError::SignatureAlgorithmMismatch); } if sig_oid != OID_SHA256_WITH_RSA_ENCRYPTION { return Err(CrlDecodeError::InvalidSignatureAlgorithm(sig_oid)); } validate_sig_params(&crl.signature_algorithm)?; validate_sig_params(&crl.tbs_cert_list.signature)?; let extensions = parse_and_validate_extensions(crl.extensions())?; let revoked_certs = crl .iter_revoked_certificates() .map(|rc| { if !rc.extensions().is_empty() { return Err(CrlDecodeError::EntryExtensionsNotAllowed); } let revocation_date = crate::data_model::common::asn1_time_to_model(rc.revocation_date); validate_time_encoding_rfc5280("revocationDate", &revocation_date)?; Ok(RevokedCert { serial_number: BigUnsigned::from_biguint(rc.serial()), revocation_date, }) }) .collect::, _>>()?; let this_update = crate::data_model::common::asn1_time_to_model(crl.last_update()); validate_time_encoding_rfc5280("thisUpdate", &this_update)?; let next_update = crl .next_update() .map(crate::data_model::common::asn1_time_to_model) .ok_or(CrlDecodeError::NextUpdateMissing)?; validate_time_encoding_rfc5280("nextUpdate", &next_update)?; Ok(RpkixCrl { raw_der: der.to_vec(), version, issuer_dn: crl.issuer().to_string(), signature_algorithm_oid: OID_SHA256_WITH_RSA_ENCRYPTION.to_string(), this_update, next_update, revoked_certs, extensions, }) } /// Verify the cryptographic signature on this CRL using the issuer certificate. /// /// Signature verification needs the issuer public key (RFC 5280 §6.3.3 (f)-(g)). /// In RPKI practice, this public key is obtained from the CRL issuer CA certificate /// (and that certificate must already be validated up to the same trust anchor). /// /// This helper also performs common binding checks: /// - CRL `issuer_dn` must equal issuer certificate `subject` /// - if issuer KeyUsage is present, require `cRLSign` /// - if issuer SKI is present, require it matches CRL AKI.keyIdentifier pub fn verify_signature_with_issuer_certificate_der( &self, issuer_cert_der: &[u8], ) -> Result<(), CrlVerifyError> { let (rem, issuer_cert) = X509Certificate::from_der(issuer_cert_der) .map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))?; if !rem.is_empty() { return Err(CrlVerifyError::IssuerCertificateTrailingBytes(rem.len())); } let subject_dn = issuer_cert.subject().to_string(); if subject_dn != self.issuer_dn { return Err(CrlVerifyError::IssuerSubjectMismatch { crl_issuer_dn: self.issuer_dn.clone(), issuer_subject_dn: subject_dn, }); } if let Some(ku) = issuer_cert .key_usage() .map_err(|e| CrlVerifyError::IssuerCertificateParse(e.to_string()))? { if !ku.value.crl_sign() { return Err(CrlVerifyError::IssuerKeyUsageMissingCrlSign); } } if let Some(issuer_ski) = get_subject_key_identifier(&issuer_cert) { if issuer_ski != self.extensions.authority_key_identifier { return Err(CrlVerifyError::AkiSkiMismatch); } } self.verify_signature_with_issuer_spki(issuer_cert.public_key()) } /// Verify the cryptographic signature on this CRL using the issuer SubjectPublicKeyInfo. pub fn verify_signature_with_issuer_spki( &self, issuer_spki: &SubjectPublicKeyInfo<'_>, ) -> Result<(), CrlVerifyError> { let (rem, crl) = CertificateRevocationList::from_der(&self.raw_der) .map_err(|e| CrlVerifyError::CrlParse(e.to_string()))?; if !rem.is_empty() { return Err(CrlVerifyError::CrlTrailingBytes(rem.len())); } crl.verify_signature(issuer_spki) .map_err(|e| CrlVerifyError::InvalidSignature(e.to_string())) } /// Verify the cryptographic signature on this CRL using a DER-encoded SubjectPublicKeyInfo. pub fn verify_signature_with_issuer_spki_der( &self, issuer_spki_der: &[u8], ) -> Result<(), CrlVerifyError> { let (rem, spki) = SubjectPublicKeyInfo::from_der(issuer_spki_der) .map_err(|e| CrlVerifyError::IssuerSpkiParse(e.to_string()))?; if !rem.is_empty() { return Err(CrlVerifyError::IssuerSpkiTrailingBytes(rem.len())); } self.verify_signature_with_issuer_spki(&spki) } } #[derive(Debug, thiserror::Error)] pub enum CrlVerifyError { #[error("issuer certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4)")] IssuerCertificateParse(String), #[error("trailing bytes after issuer certificate DER: {0} bytes (DER; RFC 5280 §4.1)")] IssuerCertificateTrailingBytes(usize), #[error("issuer SubjectPublicKeyInfo parse error: {0} (RFC 5280 §4.1.2.7)")] IssuerSpkiParse(String), #[error("trailing bytes after issuer SubjectPublicKeyInfo DER: {0} bytes (DER; RFC 5280 §4.1.2.7)")] IssuerSpkiTrailingBytes(usize), #[error("CRL parse error: {0} (RFC 5280 §5.1; RFC 6487 §5)")] CrlParse(String), #[error("trailing bytes after CRL DER: {0} bytes (DER; RFC 5280 §5.1)")] CrlTrailingBytes(usize), #[error("CRL issuer DN does not match issuer certificate subject (RFC 5280 §5.1; RFC 5280 §6.3.3(b))")] IssuerSubjectMismatch { crl_issuer_dn: String, issuer_subject_dn: String, }, #[error("issuer certificate keyUsage present but missing cRLSign (RFC 5280 §4.2.1.3; RFC 5280 §6.3.3(f))")] IssuerKeyUsageMissingCrlSign, #[error("CRL AKI.keyIdentifier does not match issuer certificate SKI (RFC 5280 §4.2.1.1; RFC 5280 §4.2.1.2; RFC 5280 §6.3.3(c)/(f))")] AkiSkiMismatch, #[error("CRL signature verification failed: {0} (RFC 5280 §6.3.3(g); RFC 7935 §2)")] InvalidSignature(String), } fn validate_time_encoding_rfc5280( field: &'static str, t: &Asn1TimeUtc, ) -> Result<(), CrlDecodeError> { let year = t.utc.year(); let expected = if year <= 2049 { Asn1TimeEncoding::UtcTime } else { Asn1TimeEncoding::GeneralizedTime }; if t.encoding != expected { return Err(CrlDecodeError::InvalidTimeEncoding { field, year, encoding: t.encoding, }); } Ok(()) } fn validate_sig_params(sig: &AlgorithmIdentifier<'_>) -> Result<(), CrlDecodeError> { if crate::data_model::common::algorithm_params_absent_or_null(sig) { Ok(()) } else { Err(CrlDecodeError::InvalidSignatureAlgorithmParameters) } } fn parse_and_validate_extensions(exts: &[X509Extension<'_>]) -> Result { if exts.len() != 2 { return Err(CrlDecodeError::InvalidExtensionsCount(exts.len())); } let mut authority_key_identifier: Option> = None; let mut crl_number: Option = None; for ext in exts { let oid = ext.oid.to_id_string(); match oid.as_str() { OID_AUTHORITY_KEY_IDENTIFIER => { if authority_key_identifier.is_some() { return Err(CrlDecodeError::DuplicateExtension(oid)); } let aki = parse_aki(ext)?; authority_key_identifier = Some(aki); } OID_CRL_NUMBER => { if crl_number.is_some() { return Err(CrlDecodeError::DuplicateExtension(oid)); } if ext.critical { return Err(CrlDecodeError::CrlNumberCritical); } let n = parse_crl_number(ext)?; if n.bits() > 159 { return Err(CrlDecodeError::CrlNumberOutOfRange); } crl_number = Some(BigUnsigned::from_biguint(&n)); } _ => return Err(CrlDecodeError::UnsupportedExtension(oid)), } } Ok(CrlExtensions { authority_key_identifier: authority_key_identifier.unwrap(), crl_number: crl_number.unwrap(), }) } fn parse_aki(ext: &X509Extension<'_>) -> Result, CrlDecodeError> { let ParsedExtension::AuthorityKeyIdentifier(aki) = ext.parsed_extension() else { return Err(CrlDecodeError::Parse("AKI extension parse failed".into())); }; validate_aki_profile(aki)?; Ok(aki .key_identifier .as_ref() .ok_or(CrlDecodeError::AkiMissingKeyIdentifier)? .0 .to_vec()) } fn validate_aki_profile(aki: &AuthorityKeyIdentifier<'_>) -> Result<(), CrlDecodeError> { if aki.key_identifier.is_none() { return Err(CrlDecodeError::AkiMissingKeyIdentifier); } if aki.authority_cert_issuer.is_some() || aki.authority_cert_serial.is_some() { return Err(CrlDecodeError::AkiHasOtherFields); } Ok(()) } fn parse_crl_number(ext: &X509Extension<'_>) -> Result { match ext.parsed_extension() { ParsedExtension::CRLNumber(n) => Ok(n.clone()), ParsedExtension::ParseError { error } => Err(CrlDecodeError::Parse(error.to_string())), _ => Err(CrlDecodeError::Parse("CRLNumber extension parse failed".into())), } } fn get_subject_key_identifier(cert: &X509Certificate<'_>) -> Option> { cert.extensions() .iter() .find(|ext| ext.oid.to_id_string() == OID_SUBJECT_KEY_IDENTIFIER) .and_then(|ext| match ext.parsed_extension() { ParsedExtension::SubjectKeyIdentifier(ki) => Some(ki.0.to_vec()), _ => None, }) }