#![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};
pub use pkix_path::DerError;
#[cfg(feature = "serde")]
mod crl_reason_serde {
use serde::{Deserialize as _, Deserializer, Serializer};
use x509_cert::ext::pkix::crl::CrlReason;
const fn to_code(r: CrlReason) -> u32 {
match r {
CrlReason::Unspecified => 0,
CrlReason::KeyCompromise => 1,
CrlReason::CaCompromise => 2,
CrlReason::AffiliationChanged => 3,
CrlReason::Superseded => 4,
CrlReason::CessationOfOperation => 5,
CrlReason::CertificateHold => 6,
CrlReason::RemoveFromCRL => 8,
CrlReason::PrivilegeWithdrawn => 9,
CrlReason::AaCompromise => 10,
}
}
const fn from_code(c: u32) -> Option<CrlReason> {
match c {
0 => Some(CrlReason::Unspecified),
1 => Some(CrlReason::KeyCompromise),
2 => Some(CrlReason::CaCompromise),
3 => Some(CrlReason::AffiliationChanged),
4 => Some(CrlReason::Superseded),
5 => Some(CrlReason::CessationOfOperation),
6 => Some(CrlReason::CertificateHold),
8 => Some(CrlReason::RemoveFromCRL),
9 => Some(CrlReason::PrivilegeWithdrawn),
10 => Some(CrlReason::AaCompromise),
_ => None,
}
}
pub fn serialize_opt<S: Serializer>(
v: &Option<CrlReason>,
s: S,
) -> Result<S::Ok, S::Error> {
use serde::Serialize as _;
v.map(to_code).serialize(s)
}
pub fn deserialize_opt<'de, D: Deserializer<'de>>(
d: D,
) -> Result<Option<CrlReason>, D::Error> {
let opt = Option::<u32>::deserialize(d)?;
Ok(opt.and_then(from_code))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum OutOfScopeReason {
CrlOnlyAttributeCerts,
CrlOnlyUserCerts,
CrlOnlyCaCerts,
CrlIdpDistributionPointMismatch,
}
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")
}
Self::CrlIdpDistributionPointMismatch => f.write_str(
"CRL IssuingDistributionPoint distributionPoint does not match the certificate's CRLDistributionPoints",
),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum Error {
Revoked {
#[cfg_attr(feature = "serde", serde(with = "pkix_path::serde_der"))]
serial: SerialNumber,
#[cfg_attr(
feature = "serde",
serde(
serialize_with = "crl_reason_serde::serialize_opt",
deserialize_with = "crl_reason_serde::deserialize_opt"
)
)]
reason_code: Option<CrlReason>,
},
CrlExpired,
CrlIssuerMismatch,
CrlSignatureInvalid,
CrlParseError(DerError),
OcspSignatureInvalid,
OcspResponderIdMismatch,
OcspCertIdMismatch,
OcspIssuerCertMismatch,
OcspStatusUnknown,
OcspExpired,
OcspParseError(DerError),
OcspMalformed,
OcspResponderEkuMissing,
OcspResponderEkuMalformed,
OcspResponderCertNotIssuedByCa,
OcspResponderCertExpired,
OcspResponderCertSigInvalid,
IndirectCrlIssuerMissing,
IndirectCrlIssuerUnexpected,
CrlSignMissing,
CrlSignerNotFound,
CrlSignerNotTrusted,
DeltaCrlBaseMismatch,
CrlNumberMismatch,
MalformedCertificate,
OutOfScope(OutOfScopeReason),
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
RevocationFetchFailed {
description: String,
},
}
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::OcspResponderEkuMissing => f.write_str(
"delegated OCSP responder cert lacks id-kp-OCSPSigning Extended Key Usage",
),
Self::OcspResponderEkuMalformed => {
f.write_str("delegated OCSP responder cert ExtendedKeyUsage extension is malformed")
}
Self::OcspResponderCertNotIssuedByCa => {
f.write_str("delegated OCSP responder cert was not issued by the certificate's CA")
}
Self::OcspResponderCertExpired => f.write_str(
"delegated OCSP responder cert validity does not include the response's producedAt",
),
Self::OcspResponderCertSigInvalid => {
f.write_str("CA signature on delegated OCSP responder cert is invalid")
}
Self::IndirectCrlIssuerMissing => f.write_str(
"CRL declares indirectCRL=TRUE but no cRLIssuer certificate was provided",
),
Self::IndirectCrlIssuerUnexpected => {
f.write_str("cRLIssuer certificate was provided but the CRL is not indirect")
}
Self::CrlSignMissing => {
f.write_str("CRL issuer KeyUsage does not include cRLSign (RFC 5280 §6.3.3(f))")
}
Self::CrlSignerNotFound => f.write_str(
"no certificate in the supplied bundle signed the CRL (path-level discovery)",
),
Self::CrlSignerNotTrusted => f.write_str(
"discovered CRL signer does not chain back to a self-signed anchor in the supplied bundle",
),
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}")
}
#[cfg(feature = "std")]
Self::RevocationFetchFailed { description } => {
write!(f, "revocation data fetch failed: {description}")
}
}
}
}
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")]
mod signer_discovery;
#[cfg(feature = "crl")]
#[cfg_attr(docsrs, doc(cfg(feature = "crl")))]
pub use crl::CrlChecker;
#[cfg(feature = "crl")]
#[cfg_attr(docsrs, doc(cfg(feature = "crl")))]
pub use signer_discovery::discover_crl_signer;
#[cfg(feature = "ocsp")]
mod ocsp;
#[cfg(feature = "ocsp")]
#[cfg_attr(docsrs, doc(cfg(feature = "ocsp")))]
pub use ocsp::OcspChecker;
const _: fn() = || {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<Error>();
};