Skip to content

Commit

Permalink
wip: extension
Browse files Browse the repository at this point in the history
  • Loading branch information
cpu committed Aug 17, 2023
1 parent 9e5f117 commit 1c27899
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 22 deletions.
106 changes: 86 additions & 20 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ const OID_CRL_REASONS :&[u64] = &[2, 5, 29, 21];
// https://www.rfc-editor.org/rfc/rfc5280#section-5.3.2
const OID_CRL_INVALIDITY_DATE :&[u64] = &[2, 5, 29, 24];

// id-ce-issuingDistributionPoint
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5
const OID_CRL_ISSUING_DISTRIBUTION_POINT :&[u64] = &[2, 5, 29, 28];

#[cfg(feature = "pem")]
const ENCODE_CONFIG: pem::EncodeConfig = match cfg!(target_family = "windows") {
true => pem::EncodeConfig { line_ending: pem::LineEnding::CRLF },
Expand Down Expand Up @@ -680,6 +684,7 @@ impl CertificateSigningRequest {
/// this_update: date_time_ymd(2023, 06, 17),
/// next_update: date_time_ymd(2024, 06, 17),
/// crl_number: SerialNumber::from(1234),
/// issuing_distribution_point: None,
/// revoked_certs: vec![revoked_cert],
/// alg: &PKCS_ECDSA_P256_SHA256,
/// key_identifier_method: KeyIdMethod::Sha256,
Expand Down Expand Up @@ -1247,7 +1252,10 @@ impl CertificateParams {
write_x509_extension(writer.next(), OID_CRL_DISTRIBUTION_POINTS, false, |writer| {
writer.write_sequence(|writer| {
for distribution_point in &self.crl_distribution_points {
write_crl_distribution_point(writer.next(), distribution_point);
// DistributionPoint SEQUENCE
writer.next().write_sequence(|writer| {
write_crl_distribution_point_name(writer.next(), &distribution_point.uris);
});
}
})
});
Expand Down Expand Up @@ -1404,7 +1412,8 @@ impl NameConstraints {
}

/// A certificate revocation list (CRL) distribution point, to be included in a certificate's
/// [distribution points extension](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.13).
/// [distribution points extension](https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.13),
/// or a CRL's [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5).
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct CrlDistributionPoint {
/// One or more URI distribution point names, indicating a place the current CRL can
Expand Down Expand Up @@ -1567,6 +1576,11 @@ pub struct CertificateRevocationListParams {
pub next_update :OffsetDateTime,
/// A monotonically increasing sequence number for a given CRL scope and issuer.
pub crl_number :SerialNumber,
/// An optional CRL extension identifying the CRL distribution point and scope for a
/// particular CRL as described in RFC 5280 Section 5.2.5[^1].
///
/// [^1]: <https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5>
pub issuing_distribution_point :Option<CrlIssuingDistributionPoint>,
/// A list of zero or more parameters describing revoked certificates included in the CRL.
pub revoked_certs :Vec<RevokedCertParams>,
/// Signature algorithm to use when signing the serialized CRL.
Expand Down Expand Up @@ -1664,6 +1678,13 @@ impl CertificateRevocationListParams {
write_x509_extension(writer.next(), OID_CRL_NUMBER, false, |writer| {
writer.write_bigint_bytes(self.crl_number.as_ref(), true);
});

// Write issuing distribution point.
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);
});
}
});
});

Expand All @@ -1672,6 +1693,54 @@ impl CertificateRevocationListParams {
}
}

/// A certificate revocation list (CRL) issuing distribution point, to be included in a CRL's
/// [issuing distribution point extension](https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5).
pub struct CrlIssuingDistributionPoint {
/// The CRL's distribution point, containing a sequence of URIs the CRL can be retrieved from.
pub distribution_point :CrlDistributionPoint,
/// An optional description of the CRL's scope. If omitted, the CRL may contain
/// both user certs and CA certs.
pub scope :Option<CrlScope>,
}

impl CrlIssuingDistributionPoint {
fn write_der(&self, writer :DERWriter) {
// IssuingDistributionPoint SEQUENCE
writer.write_sequence(|writer| {
// distributionPoint [0] DistributionPointName OPTIONAL
write_crl_distribution_point_name(writer.next(), &self.distribution_point.uris);

// -- at most one of onlyContainsUserCerts, onlyContainsCACerts,
// -- and onlyContainsAttributeCerts may be set to TRUE.
if let Some(scope) = self.scope {
match scope {
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
CrlScope::UserCertsOnly => {
writer.next().write_tagged_implicit(Tag::context(1), |writer| {
writer.write_bool(true);
});
}
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
CrlScope::CaCertsOnly => {
writer.next().write_tagged_implicit(Tag::context(2), |writer| {
writer.write_bool(true);
});
}
}
}
});
}
}

/// Describes the scope of a CRL for an issuing distribution point extension.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum CrlScope {
/// The CRL contains only end-entity user certificates.
UserCertsOnly,
/// The CRL contains only CA certificates.
CaCertsOnly,
}

/// Parameters used for describing a revoked certificate included in a [`CertificateRevocationList`].
pub struct RevokedCertParams {
/// Serial number identifying the revoked certificate.
Expand Down Expand Up @@ -1828,24 +1897,21 @@ fn write_general_subtrees(writer :DERWriter, tag :u64, general_subtrees :&[Gener
});
}

fn write_crl_distribution_point(writer :DERWriter, dp :&CrlDistributionPoint) {
// DistributionPoint SEQUENCE
writer.write_sequence(|writer| {
// distributionPoint DistributionPointName
writer.next().write_tagged_implicit(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
// fullName GeneralNames
writer.next().write_tagged_implicit(Tag::context(0), | writer| {
// GeneralNames
writer.write_sequence(|writer| {
for uri in dp.uris.iter() {
// uniformResourceIdentifier [6] IA5String,
writer.next().write_tagged_implicit(Tag::context(6), |writer| {
writer.write_ia5_string(uri)
});
}
})
});
fn write_crl_distribution_point_name<'a>(writer :DERWriter, uris :impl IntoIterator<Item = &'a String>) {
// distributionPoint DistributionPointName
writer.write_tagged_implicit(Tag::context(0), |writer| {
writer.write_sequence(|writer| {
// fullName GeneralNames
writer.next().write_tagged_implicit(Tag::context(0), | writer| {
// GeneralNames
writer.write_sequence(|writer| {
for uri in uris.into_iter() {
// uniformResourceIdentifier [6] IA5String,
writer.next().write_tagged_implicit(Tag::context(6), |writer| {
writer.write_ia5_string(uri)
});
}
})
});
});
});
Expand Down
1 change: 1 addition & 0 deletions tests/botan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ fn test_botan_crl_parse() {
this_update: now,
next_update: now + Duration::weeks(1),
crl_number: rcgen::SerialNumber::from(1234),
issuing_distribution_point: None,
revoked_certs: vec![RevokedCertParams{
serial_number: ee.get_params().serial_number.clone().unwrap(),
revocation_time: now,
Expand Down
9 changes: 8 additions & 1 deletion tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,17 @@ mod test_x509_parser_crl {
crl.get_params().this_update.unix_timestamp());
assert_eq!(x509_crl.next_update().unwrap().to_datetime().unix_timestamp(),
crl.get_params().next_update.unix_timestamp());
// TODO(XXX): Waiting on https://github.com/rusticata/x509-parser/pull/144
// TODO(XXX): Waiting on x509-parser 0.15.1 to be released.
// let crl_number = BigUint::from_bytes_be(crl.get_params().crl_number.as_ref());
// assert_eq!(x509_crl.crl_number().unwrap(), &crl_number);

// The issuing distribution point extension should be present and marked critical.
let issuing_dp_ext = x509_crl.extensions().iter()
.find(|ext| ext.oid == x509_parser::oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT)
.expect("failed to find issuing distribution point extension");
assert!(issuing_dp_ext.critical);
// TODO(XXX): x509-parser does not yet parse the CRL issuing DP extension for further examination.

// We should find the expected revoked certificate serial with the correct reason code.
let x509_revoked_cert = x509_crl.iter_revoked_certificates().next()
.expect("failed to find revoked cert in CRL");
Expand Down
6 changes: 5 additions & 1 deletion tests/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use time::{Duration, OffsetDateTime};
use rcgen::{BasicConstraints, Certificate, CertificateParams, CertificateRevocationList, CrlDistributionPoint};
use rcgen::{BasicConstraints, Certificate, CertificateParams, CertificateRevocationList, CrlDistributionPoint, CrlIssuingDistributionPoint, CrlScope};
use rcgen::{CertificateRevocationListParams, DnType, IsCa, KeyIdMethod};
use rcgen::{KeyUsagePurpose, PKCS_ECDSA_P256_SHA256, RevocationReason, RevokedCertParams, SerialNumber};

Expand Down Expand Up @@ -91,6 +91,10 @@ pub fn test_crl() -> (CertificateRevocationList, Certificate) {
this_update: now,
next_update: next_week,
crl_number: SerialNumber::from(1234),
issuing_distribution_point: Some(CrlIssuingDistributionPoint{
distribution_point: CrlDistributionPoint { uris: vec!["http://example.com/crl".to_string()] },
scope: Some(CrlScope::UserCertsOnly),
}),
revoked_certs: vec![revoked_cert],
alg: &PKCS_ECDSA_P256_SHA256,
key_identifier_method: KeyIdMethod::Sha256,
Expand Down
1 change: 1 addition & 0 deletions tests/webpki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ fn test_webpki_crl_revoke() {
this_update: now,
next_update: now + Duration::weeks(1),
crl_number: rcgen::SerialNumber::from(1234),
issuing_distribution_point: None,
revoked_certs: vec![RevokedCertParams{
serial_number: ee.get_params().serial_number.clone().unwrap(),
revocation_time: now,
Expand Down

0 comments on commit 1c27899

Please sign in to comment.