From 582a53518c9dfea55f39a8778296b08c4d5b1950 Mon Sep 17 00:00:00 2001 From: Daniel McCarney Date: Fri, 23 Jun 2023 14:26:58 -0400 Subject: [PATCH] wip: crl sealed trait + owned representation. TODO: * Fixing generate.py * Checking unit test coverage * Breaking up into reviewable chunks * ???? --- src/crl.rs | 262 +++++++++++++++++++++++++++----- src/end_entity.rs | 17 +-- src/lib.rs | 3 +- src/signed_data.rs | 64 +++++++- src/verify_cert.rs | 46 ++---- tests/client_auth.rs | 2 +- tests/client_auth_revocation.rs | 167 ++++++-------------- tests/crl_tests.rs | 27 +++- 8 files changed, 370 insertions(+), 218 deletions(-) diff --git a/src/crl.rs b/src/crl.rs index 6a0492f8..66609772 100644 --- a/src/crl.rs +++ b/src/crl.rs @@ -14,39 +14,124 @@ use crate::der::Tag; use crate::x509::{remember_extension, set_extension_once, Extension}; -use crate::{der, signed_data, Error, Time}; +use crate::{der, signed_data, Error, SignatureAlgorithm, Time}; -/// Representation of a RFC 5280[^1] profile Certificate Revocation List (CRL). +#[cfg(feature = "alloc")] +use std::collections::HashMap; + +/// Operations over a RFC 5280[^1] profile Certificate Revocation List (CRL) required +/// for revocation checking. Implemented by [`OwnedCertRevocationList`] and +/// [`BorrowedCertRevocationList`]. /// /// [^1]: -pub struct BorrowedCertRevocationList<'a> { - /// A `SignedData` structure that can be passed to `verify_signed_data`. - pub(crate) signed_data: signed_data::SignedData<'a>, - - /// Identifies the entity that has signed and issued this - /// CRL. - pub(crate) issuer: untrusted::Input<'a>, +pub trait CertRevocationList: private::Sealed { + /// Return the DER encoded issuer of the CRL. + fn issuer(&self) -> &[u8]; + + /// Try to find a revoked certificate in the CRL by DER encoded serial number. This + /// may yield an error if the CRL has malformed revoked certificates. + fn find_serial(&self, serial: &[u8]) -> Result, Error>; + + /// Verify the CRL signature using the issuer's subject public key information (SPKI) + /// and a list of supported signature algorithms. + fn verify_signature( + &self, + supported_sig_algs: &[&SignatureAlgorithm], + issuer_spki: &[u8], + ) -> Result<(), Error>; +} +/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL). +/// +/// This type is only available when using the `alloc` feature. +/// +/// [^1]: +#[cfg(feature = "alloc")] +pub struct OwnedCertRevocationList { /// Indicates the issue date of this CRL. pub this_update: Time, /// Indicates the date by which the next CRL will be issued. pub next_update: Time, - /// List of certificates revoked by the issuer in this CRL. - pub(crate) revoked_certs: untrusted::Input<'a>, + /// A map of the revoked certificates contained in then CRL, keyed by the DER encoding + /// of the revoked cert's serial number. + pub revoked_certs: HashMap, OwnedRevokedCert>, /// Provides a means of identifying the public key corresponding to the private key used to /// sign this CRL. - pub(crate) authority_key_identifier: Option>, + pub authority_key_identifier: Option>, + + /// A monotonically increasing sequence number for a given CRL scope and CRL issuer. + pub crl_number: Option>, + + issuer: Vec, + + signed_data: signed_data::OwnedSignedData, +} + +#[cfg(feature = "alloc")] +impl CertRevocationList for OwnedCertRevocationList { + fn issuer(&self) -> &[u8] { + &self.issuer + } + + fn find_serial(&self, serial: &[u8]) -> Result, Error> { + // note: this is infallible for the owned representation because we process all + // revoked certificates at the time of construction to build the `revoked_certs` map, + // returning any encountered errors at that time. + Ok(self + .revoked_certs + .get(serial) + .map(|owned_revoked_cert| owned_revoked_cert.borrow())) + } + + fn verify_signature( + &self, + supported_sig_algs: &[&SignatureAlgorithm], + issuer_spki: &[u8], + ) -> Result<(), Error> { + signed_data::verify_signed_data( + supported_sig_algs, + untrusted::Input::from(issuer_spki), + &self.signed_data.borrow(), + ) + } +} + +/// Borrowed representation of a RFC 5280[^1] profile Certificate Revocation List (CRL). +/// +/// This implementation allocates no memory, but as a result may be less efficient. E.g. +/// looking up a revoked certificate by serial number is an O(N) operation. +/// +/// [^1]: +pub struct BorrowedCertRevocationList<'a> { + /// Indicates the issue date of this CRL. + pub this_update: Time, + + /// Indicates the date by which the next CRL will be issued. + pub next_update: Time, /// A monotonically increasing sequence number for a given CRL scope and CRL issuer. pub crl_number: Option<&'a [u8]>, + + revoked_certs: untrusted::Input<'a>, + + authority_key_identifier: Option>, + + signed_data: signed_data::SignedData<'a>, + + issuer: untrusted::Input<'a>, } impl<'a> BorrowedCertRevocationList<'a> { /// Try to parse the given bytes as a RFC 5280[^1] profile Certificate Revocation List (CRL). /// + /// Note that this will not parse or process the revoked certificate entries of the CRL, meaning + /// that any errors related to revoked certificates will not be surfaced until + /// [`BorrowedCertRevocationList.into_iter`] or [`BorrowedCertRevocationList.find_serial`] are + /// used. + /// /// Webpki does not support: /// * CRL versions other than version 2. /// * CRLs missing the next update field. @@ -165,15 +250,44 @@ impl<'a> BorrowedCertRevocationList<'a> { Ok(crl) })?; - // Iterate through the revoked certificate entries to ensure they are valid so we can - // yield an error up-front instead of on first iteration by the caller. - for cert_result in crl.into_iter() { - cert_result?; - } - Ok(crl) } + /// Convert the CRL to an [`OwnedCertRevocationList`]. This may error if any of the revoked + /// certificates in the CRL are malformed or contain unsupported features. + /// + /// This function is only available when the "alloc" feature is enabled. + #[cfg(feature = "alloc")] + pub fn to_owned(&self) -> Result { + // Parse and collect the CRL's revoked cert entries, ensuring there are no errors. With + // the full set in-hand, create a lookup map by serial number for fast revocation checking. + let mut revoked_certs = HashMap::new(); + self.into_iter() + .collect::, _>>()? + .iter() + .for_each(|revoked_cert| { + revoked_certs.insert(revoked_cert.serial_number.to_vec(), revoked_cert.to_owned()); + }); + + Ok(OwnedCertRevocationList { + signed_data: self.signed_data.to_owned(), + issuer: self.issuer.as_slice_less_safe().to_vec(), + this_update: self.this_update, + next_update: self.next_update, + authority_key_identifier: self + .authority_key_identifier + .map(|aki| aki.as_slice_less_safe().to_vec()), + crl_number: self.crl_number.map(|crl_number| crl_number.to_vec()), + revoked_certs, + }) + } + + /// DER encoding of the authority key identifier (AKI) of the CRL. + pub fn authority_key_identifier(&self) -> Option<&[u8]> { + self.authority_key_identifier + .map(|input| input.as_slice_less_safe()) + } + fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> { remember_extension(extension, |id| { match id { @@ -224,29 +338,38 @@ impl<'a> BorrowedCertRevocationList<'a> { } }) } +} - /// Try to find a [`RevokedCert`] in the CRL that has a serial number matching `serial`. This - /// method will ignore any [`RevokedCert`] entries that do not parse successfully. To handle - /// parse errors use [`BorrowedCertRevocationList`]'s [`IntoIterator`] trait. - pub fn find_serial(&self, serial: &[u8]) -> Option> { - // TODO(XXX): This linear scan is sub-optimal from a performance perspective, but avoids - // any allocation. It would be nice to offer a speedier alternative for - // when the alloc feature is enabled: - // https://github.com/rustls/webpki/issues/80 - self.into_iter() - .filter_map(|parse_res| parse_res.ok()) - .find(|revoked_cert| revoked_cert.serial_number.eq(serial)) +impl CertRevocationList for BorrowedCertRevocationList<'_> { + fn issuer(&self) -> &[u8] { + self.issuer.as_slice_less_safe() } - /// Raw DER encoding of the issuer of the CRL. - pub fn issuer(&self) -> &[u8] { - self.issuer.as_slice_less_safe() + fn find_serial(&self, serial: &[u8]) -> Result, Error> { + for revoked_cert_result in self.into_iter() { + match revoked_cert_result { + Err(e) => return Err(e), + Ok(revoked_cert) => { + if revoked_cert.serial_number.eq(serial) { + return Ok(Some(revoked_cert)); + } + } + } + } + + Ok(None) } - /// DER encoding of the authority key identifier (AKI) of the CRL. - pub fn authority_key_identifier(&self) -> Option<&[u8]> { - self.authority_key_identifier - .map(|input| input.as_slice_less_safe()) + fn verify_signature( + &self, + supported_sig_algs: &[&SignatureAlgorithm], + issuer_spki: &[u8], + ) -> Result<(), Error> { + signed_data::verify_signed_data( + supported_sig_algs, + untrusted::Input::from(issuer_spki), + &self.signed_data, + ) } } @@ -273,6 +396,45 @@ impl<'a> Iterator for RevokedCerts<'a> { } } +/// Owned representation of a RFC 5280[^1] profile Certificate Revocation List (CRL) revoked +/// certificate entry. +/// +/// Only available when the "alloc" feature is enabled. +/// +/// [^1]: +#[cfg(feature = "alloc")] +pub struct OwnedRevokedCert { + /// Serial number of the revoked certificate. + pub serial_number: Vec, + + /// The date at which the CA processed the revocation. + pub revocation_date: Time, + + /// Identifies the reason for the certificate revocation. When absent, the revocation reason + /// is assumed to be RevocationReason::Unspecified. For consistency with other extensions + /// and to ensure only one revocation reason extension may be present we maintain this field + /// as optional instead of defaulting to unspecified. + pub reason_code: Option, + + /// Provides the date on which it is known or suspected that the private key was compromised or + /// that the certificate otherwise became invalid. This date may be earlier than the revocation + /// date which is the date at which the CA processed the revocation. + pub invalidity_date: Option