Skip to content

Commit

Permalink
wip: crl sealed trait + owned representation.
Browse files Browse the repository at this point in the history
TODO:

* Checking unit test coverage
* Breaking up into reviewable chunks
* ????
  • Loading branch information
cpu committed Jun 23, 2023
1 parent 1b344ba commit 4dad32d
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 102 deletions.
236 changes: 143 additions & 93 deletions src/crl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,73 +15,71 @@
use crate::der::Tag;
use crate::x509::{remember_extension, set_extension_once, Extension};
use crate::{der, signed_data, Error, SignatureAlgorithm, Time};

#[cfg(feature = "alloc")]
use std::collections::HashMap;

#[allow(missing_docs)] // TODO(@cpu): remove.
/// Operations over a RFC 5280[^1] profile Certificate Revocation List (CRL) required
/// for revocation checking. Implemented by [`OwnedCertRevocationList`] and
/// [`BorrowedCertRevocationList`].
///
/// [^1]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
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<Option<RevokedCert>, 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]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
#[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,

/// 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<Vec<u8>, OwnedRevokedCert>,

/// Provides a means of identifying the public key corresponding to the private key used to
/// sign this CRL.
pub authority_key_identifier: Option<Vec<u8>>,

/// A monotonically increasing sequence number for a given CRL scope and CRL issuer.
pub crl_number: Option<Vec<u8>>,

issuer: Vec<u8>,

signed_data: signed_data::OwnedSignedData,
}

impl OwnedCertRevocationList {
fn from_der(der: Vec<u8>) -> Result<Self, Error> {
// Parse the CRL, ensuring the top-level content is without error.
let crl = BorrowedCertRevocationList::from_der(&der)?;

// 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();
crl.into_iter()
.collect::<Result<Vec<_>, _>>()?
.iter()
.for_each(|revoked_cert| {
revoked_certs.insert(revoked_cert.serial_number.to_vec(), revoked_cert.to_owned());
});

Ok(OwnedCertRevocationList {
signed_data: crl.signed_data.to_owned(),
issuer: crl.issuer.as_slice_less_safe().to_vec(),
this_update: crl.this_update.clone(),
next_update: crl.next_update.clone(),
authority_key_identifier: crl
.authority_key_identifier
.map(|aki| aki.as_slice_less_safe().to_vec()),
crl_number: crl.crl_number.map(|crl_number| crl_number.to_vec()),
revoked_certs,
})
}
}

#[cfg(feature = "alloc")]
impl CertRevocationList for OwnedCertRevocationList {
fn issuer(&self) -> &[u8] {
&self.issuer
}

fn find_serial(&self, serial: &[u8]) -> Result<Option<RevokedCert>, 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)
Expand All @@ -101,48 +99,39 @@ impl CertRevocationList for OwnedCertRevocationList {
}
}

mod private {
use crate::crl::{BorrowedCertRevocationList, OwnedCertRevocationList};

pub trait Sealed {}

impl Sealed for OwnedCertRevocationList {}

impl Sealed for BorrowedCertRevocationList<'_> {}
}

/// Representation of a RFC 5280[^1] profile Certificate Revocation List (CRL).
/// 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]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
#[allow(dead_code)] // TODO(@cpu): Remove.
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>,

/// 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>,

/// Provides a means of identifying the public key corresponding to the private key used to
/// sign this CRL.
pub(crate) authority_key_identifier: Option<untrusted::Input<'a>>,

/// 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<untrusted::Input<'a>>,

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.
Expand Down Expand Up @@ -261,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<OwnedCertRevocationList, Error> {
// 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::<Result<Vec<_>, _>>()?
.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 {
Expand Down Expand Up @@ -320,17 +338,6 @@ impl<'a> BorrowedCertRevocationList<'a> {
}
})
}

/// Raw DER encoding of the issuer of the CRL.
pub fn issuer(&self) -> &[u8] {
self.issuer.as_slice_less_safe()
}

/// 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())
}
}

impl CertRevocationList for BorrowedCertRevocationList<'_> {
Expand All @@ -339,11 +346,18 @@ impl CertRevocationList for BorrowedCertRevocationList<'_> {
}

fn find_serial(&self, serial: &[u8]) -> Result<Option<RevokedCert>, Error> {
// TODO(xxx): Handle error case.
Ok(self
.into_iter()
.filter_map(|parse_res| parse_res.ok())
.find(|revoked_cert| revoked_cert.serial_number.eq(serial)))
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)
}

fn verify_signature(
Expand Down Expand Up @@ -382,21 +396,41 @@ 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]: <https://www.rfc-editor.org/rfc/rfc5280#section-5>
#[cfg(feature = "alloc")]
pub struct OwnedRevokedCert {
/// Serial number of the revoked certificate.
pub serial_number: Vec<u8>,

/// 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<RevocationReason>,

/// 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<Time>,
}

#[cfg(feature = "alloc")]
impl OwnedRevokedCert {
fn borrow(&self) -> RevokedCert {
/// Convert the owned representation of this revoked cert to a borrowed version.
pub fn borrow(&self) -> RevokedCert {
RevokedCert {
serial_number: &self.serial_number,
revocation_date: self.revocation_date.clone(),
reason_code: self.reason_code.clone(),
invalidity_date: self.invalidity_date.clone(),
revocation_date: self.revocation_date,
reason_code: self.reason_code,
invalidity_date: self.invalidity_date,
}
}
}
Expand Down Expand Up @@ -425,6 +459,19 @@ pub struct RevokedCert<'a> {
}

impl<'a> RevokedCert<'a> {
/// Construct an owned represetation of the revoked certificate.
///
/// Only available when the "alloc" feature is enabled.
#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> OwnedRevokedCert {
OwnedRevokedCert {
serial_number: self.serial_number.to_vec(),
revocation_date: self.revocation_date,
reason_code: self.reason_code,
invalidity_date: self.invalidity_date,
}
}

fn from_der(der: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
der::nested(der, Tag::Sequence, Error::BadDer, |der| {
// RFC 5280 §4.1.2.2:
Expand Down Expand Up @@ -485,16 +532,6 @@ impl<'a> RevokedCert<'a> {
})
}

#[cfg(feature = "alloc")]
pub fn to_owned(&self) -> OwnedRevokedCert {
OwnedRevokedCert {
serial_number: self.serial_number.to_vec(),
revocation_date: self.revocation_date.clone(),
reason_code: self.reason_code.clone(),
invalidity_date: self.invalidity_date.clone(),
}
}

fn remember_extension(&mut self, extension: &Extension<'a>) -> Result<(), Error> {
remember_extension(extension, |id| {
match id {
Expand Down Expand Up @@ -528,7 +565,7 @@ impl<'a> RevokedCert<'a> {
/// See RFC 5280 §5.3.1[^1]
///
/// [^1] <https://www.rfc-editor.org/rfc/rfc5280#section-5.3.1>
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[allow(missing_docs)] // Not much to add above the code name.
pub enum RevocationReason {
/// Unspecified should not be used, and is instead assumed by the absence of a RevocationReason
Expand Down Expand Up @@ -581,6 +618,19 @@ impl TryFrom<u8> for RevocationReason {
}
}

mod private {
use crate::crl::BorrowedCertRevocationList;
#[cfg(feature = "alloc")]
use crate::crl::OwnedCertRevocationList;

pub trait Sealed {}

impl Sealed for BorrowedCertRevocationList<'_> {}

#[cfg(feature = "alloc")]
impl Sealed for OwnedCertRevocationList {}
}

#[cfg(test)]
mod tests {
use alloc::vec::Vec;
Expand Down
6 changes: 2 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,8 @@

#![doc(html_root_url = "https://briansmith.org/rustdoc/")]
#![cfg_attr(not(feature = "std"), no_std)]
//#![warn(unreachable_pub)] // TODO(@cpu): Restore.
//#![deny(warnings, missing_docs, clippy::as_conversions)] // TODO(@cpu): Restore
#![deny(warnings, clippy::as_conversions)]
#![allow(unused)] // TODO(@cpu): Restore.
#![warn(unreachable_pub)]
#![deny(warnings, missing_docs, clippy::as_conversions)]
#![allow(
clippy::len_without_is_empty,
clippy::new_without_default,
Expand Down
Loading

0 comments on commit 4dad32d

Please sign in to comment.