Skip to content

Commit

Permalink
extensions: parse CRL issuing distribution point.
Browse files Browse the repository at this point in the history
Prior to this commit the x509-parser extension parsing code recognized
*certificate* CRL distribution points extensions, but not the
corresponding *CRL* issuing distribution point extension.

This commit adds the missing support, leveraging the existing code for
parsing distribution point name fields and revocation reason fields.
  • Loading branch information
cpu committed Feb 6, 2024
1 parent 98d4bd8 commit c846c6c
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 10 deletions.
Binary file added assets/crl-idp/indirect.der
Binary file not shown.
Binary file added assets/crl-idp/minimal.der
Binary file not shown.
Binary file added assets/crl-idp/only_attribute_certs.der
Binary file not shown.
Binary file added assets/crl-idp/only_ca_certs.der
Binary file not shown.
Binary file added assets/crl-idp/only_some_reasons.der
Binary file not shown.
Binary file added assets/crl-idp/only_user_certs.der
Binary file not shown.
79 changes: 76 additions & 3 deletions src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ pub enum ParsedExtension<'a> {
NSCertType(NSCertType),
/// Netscape certificate comment
NsCertComment(&'a str),
/// Section 5.2.5 of rfc 5280
IssuingDistributionPoint(IssuingDistributionPoint<'a>),
/// Section 5.3.1 of rfc 5280
CRLNumber(BigUint),
/// Section 5.3.1 of rfc 5280
Expand Down Expand Up @@ -593,10 +595,21 @@ impl fmt::Display for ReasonFlags {
}
}

#[derive(Clone, Debug, PartialEq)]
pub struct IssuingDistributionPoint<'a> {
pub distribution_point: Option<DistributionPointName<'a>>,
pub only_contains_user_certs: bool,
pub only_contains_ca_certs: bool,
pub only_some_reasons: Option<ReasonFlags>,
pub indirect_crl: bool,
pub only_contains_attribute_certs: bool,
}

pub(crate) mod parser {
use crate::extensions::*;
use crate::time::ASN1Time;
use asn1_rs::{GeneralizedTime, ParseResult};
use der_parser::ber::BerObject;
use der_parser::error::BerError;
use der_parser::{oid::Oid, *};
use lazy_static::lazy_static;
Expand Down Expand Up @@ -678,6 +691,11 @@ pub(crate) mod parser {
add!(m, OID_X509_EXT_CRL_NUMBER, parse_crl_number);
add!(m, OID_X509_EXT_REASON_CODE, parse_reason_code);
add!(m, OID_X509_EXT_INVALIDITY_DATE, parse_invalidity_date);
add!(
m,
OID_X509_EXT_ISSUER_DISTRIBUTION_POINT,
parse_issuingdistributionpoint_ext
);
m
};
}
Expand Down Expand Up @@ -849,6 +867,13 @@ pub(crate) mod parser {
}
}

fn parse_implicit_tagged_reasons(tag: u32) -> impl Fn(&[u8]) -> BerResult<ReasonFlags> {
move |i: &[u8]| {
let (rem, obj) = parse_der_tagged_implicit(tag, parse_der_content(Tag::BitString))(i)?;
parse_reasons(rem, obj)
}
}

// ReasonFlags ::= BIT STRING {
// unused (0),
// keyCompromise (1),
Expand All @@ -859,8 +884,7 @@ pub(crate) mod parser {
// certificateHold (6),
// privilegeWithdrawn (7),
// aACompromise (8) }
fn parse_tagged1_reasons(i: &[u8]) -> BerResult<ReasonFlags> {
let (rem, obj) = parse_der_tagged_implicit(1, parse_der_content(Tag::BitString))(i)?;
fn parse_reasons<'a>(rem: &'a [u8], obj: BerObject<'a>) -> BerResult<'a, ReasonFlags> {
if let DerObjectContent::BitString(_, b) = obj.content {
let flags = b
.data
Expand Down Expand Up @@ -889,7 +913,7 @@ pub(crate) mod parser {
opt(complete(parse_der_tagged_explicit_g(0, |b, _| {
parse_distributionpointname(b)
})))(content)?;
let (rem, reasons) = opt(complete(parse_tagged1_reasons))(rem)?;
let (rem, reasons) = opt(complete(parse_implicit_tagged_reasons(1)))(rem)?;
let (rem, crl_issuer) = opt(complete(parse_der_tagged_implicit_g(2, |i, _, _| {
parse_crlissuer_content(i)
})))(rem)?;
Expand All @@ -916,6 +940,55 @@ pub(crate) mod parser {
)(i)
}

// IssuingDistributionPoint ::= SEQUENCE {
// distributionPoint [0] DistributionPointName OPTIONAL,
// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
// onlySomeReasons [3] ReasonFlags OPTIONAL,
// indirectCRL [4] BOOLEAN DEFAULT FALSE,
// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
pub(super) fn parse_issuingdistributionpoint(
i: &[u8],
) -> IResult<&[u8], IssuingDistributionPoint, BerError> {
parse_der_sequence_defined_g(|content, _| {
let parse_tagged_bool = |tag: u32, rem| -> IResult<&[u8], bool, BerError> {
let (rem, value) = opt(complete(|_| {
parse_der_implicit(rem, tag, parse_der_content(Tag::Boolean))
.map(|(res, ob)| (res, ob.as_bool().unwrap_or(false)))
}))(rem)?;
Ok((rem, value.unwrap_or_default()))
};

let (rem, distribution_point) =
opt(complete(parse_der_tagged_explicit_g(0, |b, _| {
parse_distributionpointname(b)
})))(content)?;

let (rem, only_contains_user_certs) = parse_tagged_bool(1, rem)?;
let (rem, only_contains_ca_certs) = parse_tagged_bool(2, rem)?;
let (rem, only_some_reasons) = opt(complete(parse_implicit_tagged_reasons(3)))(rem)?;
let (rem, indirect_crl) = parse_tagged_bool(4, rem)?;
let (rem, only_contains_attribute_certs) = parse_tagged_bool(5, rem)?;

let crl_idp = IssuingDistributionPoint {
distribution_point,
only_contains_user_certs,
only_contains_ca_certs,
only_some_reasons,
indirect_crl,
only_contains_attribute_certs,
};
Ok((rem, crl_idp))
})(i)
}

fn parse_issuingdistributionpoint_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> {
map(
parse_issuingdistributionpoint,
ParsedExtension::IssuingDistributionPoint,
)(i)
}

// AuthorityInfoAccessSyntax ::=
// SEQUENCE SIZE (1..MAX) OF AccessDescription
//
Expand Down
104 changes: 97 additions & 7 deletions tests/readcrl.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,108 @@
// Currently, this file is only used to test 'verify' features, so we guard it to this feature
// To be removed if other test functions with different features are added
#![cfg(feature = "verify")]

use x509_parser::prelude::*;

const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der");
const CRL_DATA: &[u8] = include_bytes!("../assets/minimal.crl");

#[cfg(feature = "verify")]
#[test]
fn read_crl_verify() {
const CA_DATA: &[u8] = include_bytes!("../assets/ca_minimalcrl.der");
const CRL_DATA: &[u8] = include_bytes!("../assets/minimal.crl");

let (_, x509_ca) = X509Certificate::from_der(CA_DATA).expect("could not parse certificate");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let res = crl.verify_signature(&x509_ca.tbs_certificate.subject_pki);
eprintln!("Verification: {:?}", res);
assert!(res.is_ok());
}

fn crl_idp<'a>(crl: &'a CertificateRevocationList) -> &'a IssuingDistributionPoint<'a> {
let crl_idp = crl
.tbs_cert_list
.find_extension(&oid_registry::OID_X509_EXT_ISSUER_DISTRIBUTION_POINT)
.expect("missing IDP extension");
match crl_idp.parsed_extension() {
ParsedExtension::IssuingDistributionPoint(crl_idp) => crl_idp,
_ => panic!("wrong extension type"),
}
}

#[test]
fn read_minimal_crl_idp() {
const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/minimal.der");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let crl_idp = crl_idp(&crl);

let dp = crl_idp
.distribution_point
.as_ref()
.expect("missing distribution point");
let full_name = match dp {
DistributionPointName::FullName(full_name) => full_name,
DistributionPointName::NameRelativeToCRLIssuer(_) => {
panic!("wrong distribution point name type")
}
};
assert_eq!(full_name.len(), 1);
let uri = match full_name.first().unwrap() {
GeneralName::URI(uri) => *uri,
_ => panic!("wrong general name type"),
};
assert_eq!(uri, "http://crl.trustcor.ca/sub/dv-ssl-rsa-s-0.crl");

assert!(!crl_idp.only_contains_user_certs);
assert!(!crl_idp.only_contains_ca_certs);
assert!(crl_idp.only_some_reasons.is_none());
assert!(!crl_idp.only_contains_attribute_certs);
}

#[test]
fn test_only_user_crl_idp() {
const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_user_certs.der");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let crl_idp = crl_idp(&crl);

assert!(crl_idp.only_contains_user_certs);
assert!(!crl_idp.only_contains_ca_certs);
assert!(crl_idp.only_some_reasons.is_none());
assert!(!crl_idp.only_contains_attribute_certs);
}

#[test]
fn test_only_ca_crl_idp() {
const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_ca_certs.der");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let crl_idp = crl_idp(&crl);

assert!(!crl_idp.only_contains_user_certs);
assert!(crl_idp.only_contains_ca_certs);
assert!(crl_idp.only_some_reasons.is_none());
assert!(!crl_idp.only_contains_attribute_certs);
}

#[test]
fn test_only_some_reasons_crl_idp() {
const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_some_reasons.der");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let crl_idp = crl_idp(&crl);

assert!(!crl_idp.only_contains_user_certs);
assert!(!crl_idp.only_contains_ca_certs);
assert!(!crl_idp.only_contains_attribute_certs);

let reasons = crl_idp
.only_some_reasons
.as_ref()
.expect("missing only_some_reasons");
assert!(reasons.key_compromise());
assert!(reasons.affilation_changed());
}

#[test]
fn test_only_attribute_cers_crl_idp() {
const CRL_DATA: &[u8] = include_bytes!("../assets/crl-idp/only_attribute_certs.der");
let (_, crl) = parse_x509_crl(CRL_DATA).expect("could not parse revocation list");
let crl_idp = crl_idp(&crl);

assert!(!crl_idp.only_contains_user_certs);
assert!(!crl_idp.only_contains_ca_certs);
assert!(crl_idp.only_some_reasons.is_none());
assert!(crl_idp.only_contains_attribute_certs);
}

0 comments on commit c846c6c

Please sign in to comment.