use pki_types::{SignatureVerificationAlgorithm, UnixTime};
use crate::error::Error;
use crate::verify_cert::{Budget, PathNode, Role};
use crate::{der, public_values_eq};
use core::fmt::Debug;
mod types;
pub use types::{
BorrowedCertRevocationList, BorrowedRevokedCert, CertRevocationList, RevocationReason,
};
#[cfg(feature = "alloc")]
pub use types::{OwnedCertRevocationList, OwnedRevokedCert};
#[derive(Debug, Copy, Clone)]
pub struct RevocationOptionsBuilder<'a> {
crls: &'a [&'a CertRevocationList<'a>],
depth: RevocationCheckDepth,
status_policy: UnknownStatusPolicy,
expiration_policy: ExpirationPolicy,
}
impl<'a> RevocationOptionsBuilder<'a> {
pub fn new(crls: &'a [&'a CertRevocationList<'a>]) -> Result<Self, CrlsRequired> {
if crls.is_empty() {
return Err(CrlsRequired(()));
}
Ok(Self {
crls,
depth: RevocationCheckDepth::Chain,
status_policy: UnknownStatusPolicy::Deny,
expiration_policy: ExpirationPolicy::Ignore,
})
}
pub fn with_depth(mut self, depth: RevocationCheckDepth) -> Self {
self.depth = depth;
self
}
pub fn with_status_policy(mut self, policy: UnknownStatusPolicy) -> Self {
self.status_policy = policy;
self
}
pub fn with_expiration_policy(mut self, policy: ExpirationPolicy) -> Self {
self.expiration_policy = policy;
self
}
pub fn build(self) -> RevocationOptions<'a> {
RevocationOptions {
crls: self.crls,
depth: self.depth,
status_policy: self.status_policy,
expiration_policy: self.expiration_policy,
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct RevocationOptions<'a> {
pub(crate) crls: &'a [&'a CertRevocationList<'a>],
pub(crate) depth: RevocationCheckDepth,
pub(crate) status_policy: UnknownStatusPolicy,
pub(crate) expiration_policy: ExpirationPolicy,
}
impl RevocationOptions<'_> {
#[allow(clippy::too_many_arguments)]
pub(crate) fn check(
&self,
path: &PathNode<'_>,
issuer_subject: untrusted::Input<'_>,
issuer_spki: untrusted::Input<'_>,
issuer_ku: Option<untrusted::Input<'_>>,
supported_sig_algs: &[&dyn SignatureVerificationAlgorithm],
budget: &mut Budget,
time: UnixTime,
) -> Result<Option<CertNotRevoked>, Error> {
assert!(public_values_eq(path.cert.issuer, issuer_subject));
if let (RevocationCheckDepth::EndEntity, Role::Issuer) = (self.depth, path.role()) {
return Ok(None);
}
let crl = self
.crls
.iter()
.find(|candidate_crl| candidate_crl.authoritative(path));
use UnknownStatusPolicy::*;
let crl = match (crl, self.status_policy) {
(Some(crl), _) => crl,
(None, Allow) => return Ok(None),
(None, _) => return Err(Error::UnknownRevocationStatus),
};
crl.verify_signature(supported_sig_algs, issuer_spki, budget)
.map_err(crl_signature_err)?;
if self.expiration_policy == ExpirationPolicy::Enforce {
crl.check_expiration(time)?;
}
KeyUsageMode::CrlSign.check(issuer_ku)?;
let cert_serial = path.cert.serial.as_slice_less_safe();
match crl.find_serial(cert_serial)? {
None => Ok(Some(CertNotRevoked::assertion())),
Some(_) => Err(Error::CertRevoked),
}
}
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum KeyUsageMode {
CrlSign = 6,
}
impl KeyUsageMode {
fn check(self, input: Option<untrusted::Input<'_>>) -> Result<(), Error> {
let bit_string = match input {
Some(input) => {
der::expect_tag(&mut untrusted::Reader::new(input), der::Tag::BitString)?
}
None => return Ok(()),
};
let flags = der::bit_string_flags(bit_string)?;
#[allow(clippy::as_conversions)] match flags.bit_set(self as usize) {
true => Ok(()),
false => Err(Error::IssuerNotCrlSigner),
}
}
}
fn crl_signature_err(err: Error) -> Error {
match err {
#[allow(deprecated)]
Error::UnsupportedSignatureAlgorithm => Error::UnsupportedCrlSignatureAlgorithm,
Error::UnsupportedSignatureAlgorithmContext(cx) => {
Error::UnsupportedCrlSignatureAlgorithmContext(cx)
}
#[allow(deprecated)]
Error::UnsupportedSignatureAlgorithmForPublicKey => {
Error::UnsupportedCrlSignatureAlgorithmForPublicKey
}
Error::UnsupportedSignatureAlgorithmForPublicKeyContext(cx) => {
Error::UnsupportedCrlSignatureAlgorithmForPublicKeyContext(cx)
}
Error::InvalidSignatureForPublicKey => Error::InvalidCrlSignatureForPublicKey,
_ => err,
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum RevocationCheckDepth {
EndEntity,
Chain,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum UnknownStatusPolicy {
Allow,
Deny,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ExpirationPolicy {
Enforce,
Ignore,
}
pub(crate) struct CertNotRevoked(());
impl CertNotRevoked {
fn assertion() -> Self {
Self(())
}
}
#[derive(Debug, Copy, Clone)]
pub struct CrlsRequired(pub(crate) ());
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[allow(clippy::redundant_clone, clippy::clone_on_copy)]
fn test_revocation_opts_builder() {
let result = RevocationOptionsBuilder::new(&[]);
assert!(matches!(result, Err(CrlsRequired(_))));
#[cfg(feature = "alloc")]
{
let err = result.unwrap_err();
std::println!("{:?}", err.clone());
}
let crl = include_bytes!("../../tests/crls/crl.valid.der");
let crl = BorrowedCertRevocationList::from_der(&crl[..])
.unwrap()
.into();
let crls = [&crl];
let builder = RevocationOptionsBuilder::new(&crls).unwrap();
#[cfg(feature = "alloc")]
{
std::println!("{builder:?}");
_ = builder.clone();
}
let opts = builder.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Allow)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Allow)
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Allow);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_status_policy(UnknownStatusPolicy::Deny)
.with_depth(RevocationCheckDepth::EndEntity)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::EndEntity);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Ignore);
assert_eq!(opts.crls.len(), 1);
let opts = RevocationOptionsBuilder::new(&crls)
.unwrap()
.with_expiration_policy(ExpirationPolicy::Enforce)
.build();
assert_eq!(opts.depth, RevocationCheckDepth::Chain);
assert_eq!(opts.status_policy, UnknownStatusPolicy::Deny);
assert_eq!(opts.expiration_policy, ExpirationPolicy::Enforce);
assert_eq!(opts.crls.len(), 1);
#[cfg(feature = "alloc")]
{
std::println!("{:?}", opts.clone());
}
}
}