#![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)]
pub struct DerError(der::Error);
impl core::fmt::Display for DerError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}
#[cfg(feature = "std")]
impl std::error::Error for DerError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum OutOfScopeReason {
CrlOnlyAttributeCerts,
CrlOnlyUserCerts,
CrlOnlyCaCerts,
}
impl core::fmt::Display for OutOfScopeReason {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::CrlOnlyAttributeCerts => f.write_str(
"CRL onlyContainsAttributeCerts=TRUE; subject is a public-key certificate",
),
Self::CrlOnlyUserCerts => {
f.write_str("CRL onlyContainsUserCerts=TRUE; subject is a CA certificate")
}
Self::CrlOnlyCaCerts => {
f.write_str("CRL onlyContainsCACerts=TRUE; subject is an end-entity certificate")
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Revoked {
serial: SerialNumber,
reason_code: Option<CrlReason>,
},
CrlExpired,
CrlIssuerMismatch,
CrlSignatureInvalid,
CrlParseError(DerError),
OcspSignatureInvalid,
OcspResponderIdMismatch,
OcspCertIdMismatch,
OcspIssuerCertMismatch,
OcspStatusUnknown,
OcspExpired,
OcspParseError(DerError),
OcspMalformed,
CrlSignMissing,
DeltaCrlBaseMismatch,
CrlNumberMismatch,
MalformedCertificate,
OutOfScope(OutOfScopeReason),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::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"),
},
Self::CrlExpired => f.write_str("CRL validity window check failed"),
Self::CrlIssuerMismatch => f.write_str("CRL issuer does not match certificate issuer"),
Self::CrlSignatureInvalid => f.write_str("CRL signature is invalid"),
Self::CrlParseError(e) => write!(f, "CRL parse error: {e}"),
Self::OcspSignatureInvalid => f.write_str("OCSP response signature is invalid"),
Self::OcspResponderIdMismatch => {
f.write_str("OCSP ResponderId does not match the expected issuer identity")
}
Self::OcspCertIdMismatch => {
f.write_str("OCSP CertID issuer hashes do not match the expected issuer")
}
Self::OcspIssuerCertMismatch => f.write_str(
"issuer certificate subject DN does not match the certificate's issuer DN",
),
Self::OcspStatusUnknown => f.write_str("OCSP responder returned unknown status"),
Self::OcspExpired => f.write_str("OCSP response is stale or has no nextUpdate"),
Self::OcspParseError(e) => write!(f, "OCSP response parse error: {e}"),
Self::OcspMalformed => {
f.write_str("OCSP response is structurally invalid (malformed per RFC 6960)")
}
Self::CrlSignMissing => {
f.write_str("CRL issuer KeyUsage does not include cRLSign (RFC 5280 §6.3.3(f))")
}
Self::DeltaCrlBaseMismatch => {
f.write_str("delta CRL BaseCRLNumber does not match the base CRL's CRLNumber")
}
Self::CrlNumberMismatch => f.write_str("CRL number is lower than expected"),
Self::MalformedCertificate => f.write_str(
"certificate BasicConstraints extension is present but cannot be decoded",
),
Self::OutOfScope(reason) => {
write!(f, "revocation source out of scope: {reason}")
}
}
}
}
const 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 {
Self::CrlParseError(e) | Self::OcspParseError(e) => Some(e),
_ => None,
}
}
}
pub type Result<T> = core::result::Result<T, Error>;
pub trait RevocationChecker {
fn check_revocation(&self, cert: &Certificate, issuer: &Certificate) -> crate::Result<()>;
fn check_revocation_against_anchor(
&self,
_cert: &Certificate,
_anchor: &TrustAnchor,
) -> crate::Result<()> {
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
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")]
#[cfg_attr(docsrs, doc(cfg(feature = "crl")))]
pub use crl::CrlChecker;
#[cfg(feature = "ocsp")]
mod ocsp;
#[cfg(feature = "ocsp")]
#[cfg_attr(docsrs, doc(cfg(feature = "ocsp")))]
pub use ocsp::OcspChecker;