#[cfg(feature = "pem")]
use pem::Pem;
use time::OffsetDateTime;
use yasna::DERWriter;
use yasna::Tag;
use crate::oid::*;
#[cfg(feature = "pem")]
use crate::ENCODE_CONFIG;
use crate::{
write_distinguished_name, write_dt_utc_or_generalized, write_x509_authority_key_identifier,
write_x509_extension,
};
use crate::{
Certificate, KeyIdMethod, KeyUsagePurpose, RcgenError, SerialNumber, SignatureAlgorithm,
};
pub struct CertificateRevocationList {
params: CertificateRevocationListParams,
}
impl CertificateRevocationList {
pub fn from_params(params: CertificateRevocationListParams) -> Result<Self, RcgenError> {
if params.next_update.le(¶ms.this_update) {
return Err(RcgenError::InvalidCrlNextUpdate);
}
Ok(Self { params })
}
pub fn get_params(&self) -> &CertificateRevocationListParams {
&self.params
}
pub fn serialize_der_with_signer(&self, ca: &Certificate) -> Result<Vec<u8>, RcgenError> {
if !ca.params.key_usages.is_empty()
&& !ca.params.key_usages.contains(&KeyUsagePurpose::CrlSign)
{
return Err(RcgenError::IssuerNotCrlSigner);
}
self.params.serialize_der_with_signer(ca)
}
#[cfg(feature = "pem")]
pub fn serialize_pem_with_signer(&self, ca: &Certificate) -> Result<String, RcgenError> {
let contents = self.serialize_der_with_signer(ca)?;
let p = Pem::new("X509 CRL", contents);
Ok(pem::encode_config(&p, ENCODE_CONFIG))
}
}
#[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,
}
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 alg: &'static SignatureAlgorithm,
pub key_identifier_method: KeyIdMethod,
}
impl CertificateRevocationListParams {
fn serialize_der_with_signer(&self, ca: &Certificate) -> Result<Vec<u8>, RcgenError> {
yasna::try_construct_der(|writer| {
writer.write_sequence(|writer| {
let tbs_cert_list_serialized = yasna::try_construct_der(|writer| {
self.write_crl(writer, ca)?;
Ok::<(), RcgenError>(())
})?;
writer.next().write_der(&tbs_cert_list_serialized);
ca.params.alg.write_alg_ident(writer.next());
ca.key_pair.sign(&tbs_cert_list_serialized, writer.next())?;
Ok(())
})
})
}
fn write_crl(&self, writer: DERWriter, ca: &Certificate) -> Result<(), RcgenError> {
writer.write_sequence(|writer| {
writer.next().write_u8(1);
ca.params.alg.write_alg_ident(writer.next());
write_distinguished_name(writer.next(), &ca.params.distinguished_name);
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(), ca);
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(())
})
}
}
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,
}
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| {
self.reason_code.map(|reason_code| {
write_x509_extension(writer.next(), OID_CRL_REASONS, false, |writer| {
writer.write_enum(reason_code as i64);
});
});
self.invalidity_date.map(|invalidity_date| {
write_x509_extension(
writer.next(),
OID_CRL_INVALIDITY_DATE,
false,
|writer| {
write_dt_utc_or_generalized(writer, invalidity_date);
},
)
});
});
}
})
}
}