#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs, rust_2018_idioms)]
use pkix_path::TrustAnchor;
use x509_cert::{ext::pkix::crl::CrlReason, serial_number::SerialNumber, Certificate};
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Revoked {
serial: SerialNumber,
reason_code: Option<CrlReason>,
},
CrlExpired,
CrlIssuerMismatch,
CrlSignatureInvalid,
CrlParseError(der::Error),
OcspSignatureInvalid,
OcspStatusUnknown,
OcspParseError(der::Error),
OcspMalformed,
CrlSignMissing,
DeltaCrlBaseMismatch,
CrlNumberMismatch,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::Revoked {
serial,
reason_code,
} => match reason_code {
Some(code) => write!(
f,
"certificate {serial} is revoked (reason {})",
crl_reason_name(*code)
),
None => write!(f, "certificate {serial} is revoked"),
},
Error::CrlExpired => f.write_str("CRL validity window check failed"),
Error::CrlIssuerMismatch => f.write_str("CRL issuer does not match certificate issuer"),
Error::CrlSignatureInvalid => f.write_str("CRL signature is invalid"),
Error::CrlParseError(e) => write!(f, "CRL parse error: {e}"),
Error::OcspSignatureInvalid => f.write_str("OCSP response signature is invalid"),
Error::OcspStatusUnknown => f.write_str("OCSP responder returned unknown status"),
Error::OcspParseError(e) => write!(f, "OCSP response parse error: {e}"),
Error::OcspMalformed => {
f.write_str("OCSP response is structurally invalid (malformed per RFC 6960)")
}
Error::CrlSignMissing => {
f.write_str("CRL issuer KeyUsage does not include cRLSign (RFC 5280 §6.3.3(f))")
}
Error::DeltaCrlBaseMismatch => {
f.write_str("delta CRL BaseCRLNumber does not match the base CRL's CRLNumber")
}
Error::CrlNumberMismatch => f.write_str("CRL number is lower than expected"),
}
}
}
fn crl_reason_name(r: CrlReason) -> &'static str {
match r {
CrlReason::Unspecified => "unspecified",
CrlReason::KeyCompromise => "keyCompromise",
CrlReason::CaCompromise => "cACompromise",
CrlReason::AffiliationChanged => "affiliationChanged",
CrlReason::Superseded => "superseded",
CrlReason::CessationOfOperation => "cessationOfOperation",
CrlReason::CertificateHold => "certificateHold",
CrlReason::RemoveFromCRL => "removeFromCRL",
CrlReason::PrivilegeWithdrawn => "privilegeWithdrawn",
CrlReason::AaCompromise => "aACompromise",
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::CrlParseError(e) => Some(e),
Error::OcspParseError(e) => Some(e),
_ => None,
}
}
}
pub type Result<T> = core::result::Result<T, Error>;
pub trait RevocationChecker {
#[must_use = "revocation check result must not be silently discarded"]
fn check_revocation(&self, cert: &Certificate, issuer: &Certificate) -> crate::Result<()>;
#[must_use = "revocation check result must not be silently discarded"]
fn check_revocation_against_anchor(
&self,
_cert: &Certificate,
_anchor: &TrustAnchor,
) -> crate::Result<()> {
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct NoRevocation;
impl RevocationChecker for NoRevocation {
#[inline]
fn check_revocation(&self, _cert: &Certificate, _issuer: &Certificate) -> crate::Result<()> {
Ok(())
}
}
#[cfg(feature = "crl")]
mod crl;
#[cfg(feature = "crl")]
pub use crl::CrlChecker;
#[cfg(feature = "ocsp")]
mod ocsp;
#[cfg(feature = "ocsp")]
pub use ocsp::OcspChecker;