diff --git a/.gitignore b/.gitignore index 3166f7c..3569ce7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .*.swp target Cargo.lock +/.idea diff --git a/assets/crl-idp/indirect.der b/assets/crl-idp/indirect.der new file mode 100644 index 0000000..0fc7d5b Binary files /dev/null and b/assets/crl-idp/indirect.der differ diff --git a/assets/crl-idp/minimal.der b/assets/crl-idp/minimal.der new file mode 100644 index 0000000..ba56c9a Binary files /dev/null and b/assets/crl-idp/minimal.der differ diff --git a/assets/crl-idp/only_attribute_certs.der b/assets/crl-idp/only_attribute_certs.der new file mode 100644 index 0000000..3587b7f Binary files /dev/null and b/assets/crl-idp/only_attribute_certs.der differ diff --git a/assets/crl-idp/only_ca_certs.der b/assets/crl-idp/only_ca_certs.der new file mode 100644 index 0000000..a981245 Binary files /dev/null and b/assets/crl-idp/only_ca_certs.der differ diff --git a/assets/crl-idp/only_some_reasons.der b/assets/crl-idp/only_some_reasons.der new file mode 100644 index 0000000..bd18910 Binary files /dev/null and b/assets/crl-idp/only_some_reasons.der differ diff --git a/assets/crl-idp/only_user_certs.der b/assets/crl-idp/only_user_certs.der new file mode 100644 index 0000000..6072449 Binary files /dev/null and b/assets/crl-idp/only_user_certs.der differ diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs index 868dc77..f191e17 100644 --- a/src/extensions/mod.rs +++ b/src/extensions/mod.rs @@ -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 @@ -593,10 +595,21 @@ impl fmt::Display for ReasonFlags { } } +#[derive(Clone, Debug, PartialEq)] +pub struct IssuingDistributionPoint<'a> { + pub distribution_point: Option>, + pub only_contains_user_certs: bool, + pub only_contains_ca_certs: bool, + pub only_some_reasons: Option, + 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; @@ -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 }; } @@ -849,6 +867,13 @@ pub(crate) mod parser { } } + fn parse_implicit_tagged_reasons(tag: u32) -> impl Fn(&[u8]) -> BerResult { + 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), @@ -859,8 +884,7 @@ pub(crate) mod parser { // certificateHold (6), // privilegeWithdrawn (7), // aACompromise (8) } - fn parse_tagged1_reasons(i: &[u8]) -> BerResult { - 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 @@ -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)?; @@ -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 // diff --git a/tests/readcrl.rs b/tests/readcrl.rs index 45b63bf..3bc88d2 100644 --- a/tests/readcrl.rs +++ b/tests/readcrl.rs @@ -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); +}