#[cfg(feature = "pem")]
use pem::Pem;
use pki_types::CertificateRevocationListDer;
use time::OffsetDateTime;
use yasna::DERWriter;
use yasna::Tag;
use crate::key_pair::sign_der;
#[cfg(feature = "pem")]
use crate::ENCODE_CONFIG;
use crate::{
oid, write_distinguished_name, write_dt_utc_or_generalized,
write_x509_authority_key_identifier, write_x509_extension, Error, Issuer, KeyIdMethod,
KeyUsagePurpose, SerialNumber, SigningKey,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateRevocationList {
der: CertificateRevocationListDer<'static>,
}
impl CertificateRevocationList {
#[cfg(feature = "pem")]
pub fn pem(&self) -> Result<String, Error> {
let p = Pem::new("X509 CRL", &*self.der);
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
pub fn der(&self) -> &CertificateRevocationListDer<'static> {
&self.der
}
}
impl From<CertificateRevocationList> for CertificateRevocationListDer<'static> {
fn from(crl: CertificateRevocationList) -> Self {
crl.der
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CrlDistributionPoint {
pub uris: Vec<String>,
}
impl CrlDistributionPoint {
pub(crate) fn write_der(&self, writer: DERWriter) {
writer.write_sequence(|writer| {
write_distribution_point_name_uris(writer.next(), &self.uris);
});
}
}
fn write_distribution_point_name_uris<'a>(
writer: DERWriter,
uris: impl IntoIterator<Item = &'a String>,
) {
writer.write_tagged_implicit(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
writer
.next()
.write_tagged_implicit(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
for uri in uris.into_iter() {
writer
.next()
.write_tagged_implicit(Tag::context(6), |writer| {
writer.write_ia5_string(uri)
});
}
})
});
});
});
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[allow(missing_docs)] pub enum RevocationReason {
Unspecified = 0,
KeyCompromise = 1,
CaCompromise = 2,
AffiliationChanged = 3,
Superseded = 4,
CessationOfOperation = 5,
CertificateHold = 6,
RemoveFromCrl = 8,
PrivilegeWithdrawn = 9,
AaCompromise = 10,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CertificateRevocationListParams {
pub this_update: OffsetDateTime,
pub next_update: OffsetDateTime,
pub crl_number: SerialNumber,
pub issuing_distribution_point: Option<CrlIssuingDistributionPoint>,
pub revoked_certs: Vec<RevokedCertParams>,
pub key_identifier_method: KeyIdMethod,
}
impl CertificateRevocationListParams {
pub fn signed_by(
&self,
issuer: &Issuer<'_, impl SigningKey>,
) -> Result<CertificateRevocationList, Error> {
if self.next_update.le(&self.this_update) {
return Err(Error::InvalidCrlNextUpdate);
}
if !issuer.key_usages.is_empty() && !issuer.key_usages.contains(&KeyUsagePurpose::CrlSign) {
return Err(Error::IssuerNotCrlSigner);
}
Ok(CertificateRevocationList {
der: self.serialize_der(issuer)?.into(),
})
}
fn serialize_der(&self, issuer: &Issuer<'_, impl SigningKey>) -> Result<Vec<u8>, Error> {
sign_der(&issuer.signing_key, |writer| {
writer.next().write_u8(1);
issuer
.signing_key
.algorithm()
.write_alg_ident(writer.next());
write_distinguished_name(writer.next(), issuer.distinguished_name.as_ref());
write_dt_utc_or_generalized(writer.next(), self.this_update);
write_dt_utc_or_generalized(writer.next(), self.next_update);
if !self.revoked_certs.is_empty() {
writer.next().write_sequence(|writer| {
for revoked_cert in &self.revoked_certs {
revoked_cert.write_der(writer.next());
}
});
}
writer.next().write_tagged(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
write_x509_authority_key_identifier(
writer.next(),
self.key_identifier_method
.derive(issuer.signing_key.subject_public_key_info()),
);
write_x509_extension(writer.next(), oid::CRL_NUMBER, false, |writer| {
writer.write_bigint_bytes(self.crl_number.as_ref(), true);
});
if let Some(issuing_distribution_point) = &self.issuing_distribution_point {
write_x509_extension(
writer.next(),
oid::CRL_ISSUING_DISTRIBUTION_POINT,
true,
|writer| {
issuing_distribution_point.write_der(writer);
},
);
}
});
});
Ok(())
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CrlIssuingDistributionPoint {
pub distribution_point: CrlDistributionPoint,
pub scope: Option<CrlScope>,
}
impl CrlIssuingDistributionPoint {
fn write_der(&self, writer: DERWriter) {
writer.write_sequence(|writer| {
write_distribution_point_name_uris(writer.next(), &self.distribution_point.uris);
if let Some(scope) = self.scope {
let tag = match scope {
CrlScope::UserCertsOnly => Tag::context(1),
CrlScope::CaCertsOnly => Tag::context(2),
};
writer.next().write_tagged_implicit(tag, |writer| {
writer.write_bool(true);
});
}
});
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum CrlScope {
UserCertsOnly,
CaCertsOnly,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RevokedCertParams {
pub serial_number: SerialNumber,
pub revocation_time: OffsetDateTime,
pub reason_code: Option<RevocationReason>,
pub invalidity_date: Option<OffsetDateTime>,
}
impl RevokedCertParams {
fn write_der(&self, writer: DERWriter) {
writer.write_sequence(|writer| {
writer
.next()
.write_bigint_bytes(self.serial_number.as_ref(), true);
write_dt_utc_or_generalized(writer.next(), self.revocation_time);
let has_reason_code =
matches!(self.reason_code, Some(reason) if reason != RevocationReason::Unspecified);
let has_invalidity_date = self.invalidity_date.is_some();
if has_reason_code || has_invalidity_date {
writer.next().write_sequence(|writer| {
if let Some(reason_code) = self.reason_code {
write_x509_extension(writer.next(), oid::CRL_REASONS, false, |writer| {
writer.write_enum(reason_code as i64);
});
}
if let Some(invalidity_date) = self.invalidity_date {
write_x509_extension(
writer.next(),
oid::CRL_INVALIDITY_DATE,
false,
|writer| {
write_dt_utc_or_generalized(writer, invalidity_date);
},
)
}
});
}
})
}
}