//! X.509 Extensions objects and types use crate::error::{X509Error, X509Result}; use crate::time::{der_to_utctime, ASN1Time}; use crate::traits::FromDer; use crate::x509::{ReasonCode, RelativeDistinguishedName, X509Name}; use der_parser::ber::parse_ber_bool; use der_parser::der::*; use der_parser::error::{BerError, BerResult}; use der_parser::num_bigint::BigUint; use der_parser::oid::Oid; use nom::combinator::{all_consuming, complete, map, map_res, opt}; use nom::multi::{many0, many1}; use nom::{Err, IResult, Parser}; use oid_registry::*; use std::collections::HashMap; use std::fmt; /// X.509 version 3 extension /// /// X.509 extensions allow adding attributes to objects like certificates or revocation lists. /// /// Each extension in a certificate is designated as either critical or non-critical. A /// certificate using system MUST reject the certificate if it encounters a critical extension it /// does not recognize; however, a non-critical extension MAY be ignored if it is not recognized. /// /// Each extension includes an OID and an ASN.1 structure. When an extension appears in a /// certificate, the OID appears as the field extnID and the corresponding ASN.1 encoded structure /// is the value of the octet string extnValue. A certificate MUST NOT include more than one /// instance of a particular extension. /// /// When parsing an extension, the global extension structure (described above) is parsed, /// and the object is returned if it succeeds. /// During this step, it also attempts to parse the content of the extension, if known. /// The returned object has a /// [`X509Extension::parsed_extension()`] method. The returned /// enum is either a known extension, or the special value `ParsedExtension::UnsupportedExtension`. /// /// # Example /// /// ```rust /// use x509_parser::extensions::{X509Extension, ParsedExtension}; /// use x509_parser::traits::FromDer; /// /// static DER: &[u8] = &[ /// 0x30, 0x1D, 0x06, 0x03, 0x55, 0x1D, 0x0E, 0x04, 0x16, 0x04, 0x14, 0xA3, 0x05, 0x2F, 0x18, /// 0x60, 0x50, 0xC2, 0x89, 0x0A, 0xDD, 0x2B, 0x21, 0x4F, 0xFF, 0x8E, 0x4E, 0xA8, 0x30, 0x31, /// 0x36 ]; /// /// # fn main() { /// let res = X509Extension::from_der(DER); /// match res { /// Ok((_rem, ext)) => { /// println!("Extension OID: {}", ext.oid); /// println!(" Critical: {}", ext.critical); /// let parsed_ext = ext.parsed_extension(); /// assert!(!parsed_ext.unsupported()); /// assert!(parsed_ext.error().is_none()); /// if let ParsedExtension::SubjectKeyIdentifier(key_id) = parsed_ext { /// assert!(key_id.0.len() > 0); /// } else { /// panic!("Extension has wrong type"); /// } /// }, /// _ => panic!("x509 extension parsing failed: {:?}", res), /// } /// # } /// ``` #[derive(Clone, Debug, PartialEq)] pub struct X509Extension<'a> { /// OID describing the extension content pub oid: Oid<'a>, /// Boolean value describing the 'critical' attribute of the extension /// /// An extension includes the boolean critical, with a default value of FALSE. pub critical: bool, /// Raw content of the extension pub value: &'a [u8], pub(crate) parsed_extension: ParsedExtension<'a>, } impl<'a> X509Extension<'a> { /// Creates a new extension with the provided values. #[inline] pub const fn new( oid: Oid<'a>, critical: bool, value: &'a [u8], parsed_extension: ParsedExtension<'a>, ) -> X509Extension<'a> { X509Extension { oid, critical, value, parsed_extension, } } /// Return the extension type or `UnsupportedExtension` if the extension is not implemented. #[inline] pub fn parsed_extension(&self) -> &ParsedExtension<'a> { &self.parsed_extension } } ///
/// Extension  ::=  SEQUENCE  {
///     extnID      OBJECT IDENTIFIER,
///     critical    BOOLEAN DEFAULT FALSE,
///     extnValue   OCTET STRING  }
/// 
impl<'a> FromDer<'a> for X509Extension<'a> { fn from_der(i: &'a [u8]) -> X509Result { X509ExtensionParser::new().parse(i) } } /// `X509Extension` parser builder #[derive(Clone, Copy, Debug, PartialEq)] pub struct X509ExtensionParser { deep_parse_extensions: bool, } impl X509ExtensionParser { #[inline] pub const fn new() -> Self { X509ExtensionParser { deep_parse_extensions: true, } } #[inline] pub const fn with_deep_parse_extensions(self, deep_parse_extensions: bool) -> Self { X509ExtensionParser { deep_parse_extensions, } } } impl<'a> Parser<&'a [u8], X509Extension<'a>, X509Error> for X509ExtensionParser { fn parse(&mut self, input: &'a [u8]) -> IResult<&'a [u8], X509Extension<'a>, X509Error> { parse_der_sequence_defined_g(|i, _| { let (i, oid) = map_res(parse_der_oid, |x| x.as_oid_val())(i)?; let (i, critical) = der_read_critical(i)?; let (i, value) = map_res(parse_der_octetstring, |x| x.as_slice())(i)?; let (i, parsed_extension) = if self.deep_parse_extensions { parser::parse_extension(i, value, &oid)? } else { (&[] as &[_], ParsedExtension::Unparsed) }; let ext = X509Extension { oid, critical, value, parsed_extension, }; Ok((i, ext)) })(input) .map_err(|_| X509Error::InvalidExtensions.into()) } } #[derive(Clone, Debug, PartialEq)] pub enum ParsedExtension<'a> { /// Crate parser does not support this extension (yet) UnsupportedExtension { oid: Oid<'a>, }, ParseError { error: Err, }, /// Section 4.2.1.1 of rfc 5280 AuthorityKeyIdentifier(AuthorityKeyIdentifier<'a>), /// Section 4.2.1.2 of rfc 5280 SubjectKeyIdentifier(KeyIdentifier<'a>), /// Section 4.2.1.3 of rfc 5280 KeyUsage(KeyUsage), /// Section 4.2.1.4 of rfc 5280 CertificatePolicies(CertificatePolicies<'a>), /// Section 4.2.1.5 of rfc 5280 PolicyMappings(PolicyMappings<'a>), /// Section 4.2.1.6 of rfc 5280 SubjectAlternativeName(SubjectAlternativeName<'a>), /// Section 4.2.1.9 of rfc 5280 BasicConstraints(BasicConstraints), /// Section 4.2.1.10 of rfc 5280 NameConstraints(NameConstraints<'a>), /// Section 4.2.1.11 of rfc 5280 PolicyConstraints(PolicyConstraints), /// Section 4.2.1.12 of rfc 5280 ExtendedKeyUsage(ExtendedKeyUsage<'a>), /// Section 4.2.1.13 of rfc 5280 CRLDistributionPoints(CRLDistributionPoints<'a>), /// Section 4.2.1.14 of rfc 5280 InhibitAnyPolicy(InhibitAnyPolicy), /// Section 4.2.2.1 of rfc 5280 AuthorityInfoAccess(AuthorityInfoAccess<'a>), /// Netscape certificate type (subject is SSL client, an SSL server, or a CA) NSCertType(NSCertType), /// Section 5.3.1 of rfc 5280 CRLNumber(BigUint), /// Section 5.3.1 of rfc 5280 ReasonCode(ReasonCode), /// Section 5.3.3 of rfc 5280 InvalidityDate(ASN1Time), /// Unparsed extension (was not requested in parsing options) Unparsed, } impl<'a> ParsedExtension<'a> { /// Return `true` if the extension is unsupported pub fn unsupported(&self) -> bool { matches!(self, &ParsedExtension::UnsupportedExtension { .. }) } /// Return a reference on the parsing error if the extension parsing failed pub fn error(&self) -> Option<&Err> { match self { ParsedExtension::ParseError { error } => Some(error), _ => None, } } } #[derive(Clone, Debug, PartialEq)] pub struct AuthorityKeyIdentifier<'a> { pub key_identifier: Option>, pub authority_cert_issuer: Option>>, pub authority_cert_serial: Option<&'a [u8]>, } impl<'a> FromDer<'a> for AuthorityKeyIdentifier<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_authoritykeyidentifier(i).map_err(Err::convert) } } pub type CertificatePolicies<'a> = Vec>; impl<'a> FromDer<'a> for CertificatePolicies<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_certificatepolicies(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct PolicyInformation<'a> { pub policy_id: Oid<'a>, pub policy_qualifiers: Option>>, } #[derive(Clone, Debug, PartialEq)] pub struct PolicyQualifierInfo<'a> { pub policy_qualifier_id: Oid<'a>, pub qualifier: &'a [u8], } /// Identifies whether the subject of the certificate is a CA, and the max validation depth. #[derive(Clone, Debug, PartialEq)] pub struct BasicConstraints { pub ca: bool, pub path_len_constraint: Option, } impl<'a> FromDer<'a> for BasicConstraints { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_basicconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct KeyIdentifier<'a>(pub &'a [u8]); impl<'a> FromDer<'a> for KeyIdentifier<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_keyidentifier(i).map_err(Err::convert) } } #[derive(Clone, Copy, Debug, PartialEq)] pub struct KeyUsage { pub flags: u16, } impl KeyUsage { pub fn digital_signature(&self) -> bool { self.flags & 1 == 1 } pub fn non_repudiation(&self) -> bool { (self.flags >> 1) & 1u16 == 1 } pub fn key_encipherment(&self) -> bool { (self.flags >> 2) & 1u16 == 1 } pub fn data_encipherment(&self) -> bool { (self.flags >> 3) & 1u16 == 1 } pub fn key_agreement(&self) -> bool { (self.flags >> 4) & 1u16 == 1 } pub fn key_cert_sign(&self) -> bool { (self.flags >> 5) & 1u16 == 1 } pub fn crl_sign(&self) -> bool { (self.flags >> 6) & 1u16 == 1 } pub fn encipher_only(&self) -> bool { (self.flags >> 7) & 1u16 == 1 } pub fn decipher_only(&self) -> bool { (self.flags >> 8) & 1u16 == 1 } } // This list must have the same order as KeyUsage flags declaration (4.2.1.3) const KEY_USAGE_FLAGS: &[&str] = &[ "Digital Signature", "Non Repudiation", "Key Encipherment", "Data Encipherment", "Key Agreement", "Key Cert Sign", "CRL Sign", "Encipher Only", "Decipher Only", ]; impl fmt::Display for KeyUsage { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = KEY_USAGE_FLAGS .iter() .enumerate() .fold(String::new(), |acc, (idx, s)| { if self.flags >> idx & 1 != 0 { acc + s + ", " } else { acc } }); s.pop(); s.pop(); f.write_str(&s) } } impl<'a> FromDer<'a> for KeyUsage { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_keyusage(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct ExtendedKeyUsage<'a> { pub any: bool, pub server_auth: bool, pub client_auth: bool, pub code_signing: bool, pub email_protection: bool, pub time_stamping: bool, pub ocsp_signing: bool, pub other: Vec>, } impl<'a> FromDer<'a> for ExtendedKeyUsage<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_extendedkeyusage(i).map_err(Err::convert) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct NSCertType(u8); // The value is a bit-string, where the individual bit positions are defined as: // // bit-0 SSL client - this cert is certified for SSL client authentication use // bit-1 SSL server - this cert is certified for SSL server authentication use // bit-2 S/MIME - this cert is certified for use by clients (New in PR3) // bit-3 Object Signing - this cert is certified for signing objects such as Java applets and plugins(New in PR3) // bit-4 Reserved - this bit is reserved for future use // bit-5 SSL CA - this cert is certified for issuing certs for SSL use // bit-6 S/MIME CA - this cert is certified for issuing certs for S/MIME use (New in PR3) // bit-7 Object Signing CA - this cert is certified for issuing certs for Object Signing (New in PR3) impl NSCertType { pub fn ssl_client(&self) -> bool { self.0 & 0x1 == 1 } pub fn ssl_server(&self) -> bool { (self.0 >> 1) & 1 == 1 } pub fn smime(&self) -> bool { (self.0 >> 2) & 1 == 1 } pub fn object_signing(&self) -> bool { (self.0 >> 3) & 1 == 1 } pub fn ssl_ca(&self) -> bool { (self.0 >> 5) & 1 == 1 } pub fn smime_ca(&self) -> bool { (self.0 >> 6) & 1 == 1 } pub fn object_signing_ca(&self) -> bool { (self.0 >> 7) & 1 == 1 } } const NS_CERT_TYPE_FLAGS: &[&str] = &[ "SSL CLient", "SSL Server", "S/MIME", "Object Signing", "Reserved", "SSL CA", "S/MIME CA", "Object Signing CA", ]; impl fmt::Display for NSCertType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = String::new(); let mut acc = self.0; for flag_text in NS_CERT_TYPE_FLAGS { if acc & 1 != 0 { s = s + flag_text + ", "; } acc >>= 1; } s.pop(); s.pop(); f.write_str(&s) } } impl<'a> FromDer<'a> for NSCertType { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_nscerttype(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct AuthorityInfoAccess<'a> { pub accessdescs: Vec>, } impl<'a> AuthorityInfoAccess<'a> { /// Returns a `HashMap` mapping `Oid` to the list of references to `GeneralNames` /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn as_hashmap(&self) -> HashMap, Vec<&GeneralName<'a>>> { // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in &self.accessdescs { let AccessDescription { access_method: oid, access_location: gn, } = desc; if let Some(general_names) = m.get_mut(oid) { general_names.push(gn); } else { m.insert(oid.clone(), vec![gn]); } } m } /// Returns a `HashMap` mapping `Oid` to the list of `GeneralNames` (consuming the input) /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn into_hashmap(self) -> HashMap, Vec>> { let mut aia_list = self.accessdescs; // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in aia_list.drain(..) { let AccessDescription { access_method: oid, access_location: gn, } = desc; if let Some(general_names) = m.get_mut(&oid) { general_names.push(gn); } else { m.insert(oid, vec![gn]); } } m } } impl<'a> FromDer<'a> for AuthorityInfoAccess<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_authorityinfoaccess(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct AccessDescription<'a> { pub access_method: Oid<'a>, pub access_location: GeneralName<'a>, } impl<'a> AccessDescription<'a> { pub const fn new(access_method: Oid<'a>, access_location: GeneralName<'a>) -> Self { AccessDescription { access_method, access_location, } } } #[derive(Clone, Debug, PartialEq)] pub struct InhibitAnyPolicy { pub skip_certs: u32, } impl<'a> FromDer<'a> for InhibitAnyPolicy { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { map(parse_der_u32, |skip_certs| InhibitAnyPolicy { skip_certs })(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct PolicyMappings<'a> { pub mappings: Vec>, } impl<'a> FromDer<'a> for PolicyMappings<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_policymappings(i).map_err(Err::convert) } } impl<'a> PolicyMappings<'a> { /// Returns a `HashMap` mapping `Oid` to the list of references to `Oid` /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn as_hashmap(&self) -> HashMap, Vec<&Oid<'a>>> { // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for desc in &self.mappings { let PolicyMapping { issuer_domain_policy: left, subject_domain_policy: right, } = desc; if let Some(l) = m.get_mut(left) { l.push(right); } else { m.insert(left.clone(), vec![right]); } } m } /// Returns a `HashMap` mapping `Oid` to the list of `Oid` (consuming the input) /// /// If several names match the same `Oid`, they are merged in the same entry. pub fn into_hashmap(self) -> HashMap, Vec>> { let mut l = self.mappings; // create the hashmap and merge entries with same OID let mut m: HashMap> = HashMap::new(); for mapping in l.drain(..) { let PolicyMapping { issuer_domain_policy: left, subject_domain_policy: right, } = mapping; if let Some(general_names) = m.get_mut(&left) { general_names.push(right); } else { m.insert(left, vec![right]); } } m } } #[derive(Clone, Debug, PartialEq)] pub struct PolicyMapping<'a> { pub issuer_domain_policy: Oid<'a>, pub subject_domain_policy: Oid<'a>, } impl<'a> PolicyMapping<'a> { pub const fn new(issuer_domain_policy: Oid<'a>, subject_domain_policy: Oid<'a>) -> Self { PolicyMapping { issuer_domain_policy, subject_domain_policy, } } } #[derive(Clone, Debug, PartialEq)] pub struct PolicyConstraints { pub require_explicit_policy: Option, pub inhibit_policy_mapping: Option, } impl<'a> FromDer<'a> for PolicyConstraints { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_policyconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct SubjectAlternativeName<'a> { pub general_names: Vec>, } impl<'a> FromDer<'a> for SubjectAlternativeName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(GeneralName::from_der)))(input)?; Ok((i, SubjectAlternativeName { general_names })) })(i) } } #[derive(Clone, Debug, PartialEq)] /// Represents a GeneralName as defined in RFC5280. There /// is no support X.400 addresses and EDIPartyName. /// /// String formats are not validated. pub enum GeneralName<'a> { OtherName(Oid<'a>, &'a [u8]), /// More or less an e-mail, the format is not checked. RFC822Name(&'a str), /// A hostname, format is not checked. DNSName(&'a str), /// X400Address, X400Address(UnparsedObject<'a>), /// RFC5280 defines several string types, we always try to parse as utf-8 /// which is more or less a superset of the string types. DirectoryName(X509Name<'a>), /// EDIPartyName EDIPartyName(UnparsedObject<'a>), /// An uniform resource identifier. The format is not checked. URI(&'a str), /// An ip address, provided as encoded. IPAddress(&'a [u8]), RegisteredID(Oid<'a>), } impl<'a> FromDer<'a> for GeneralName<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_generalname(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct UnparsedObject<'a> { pub header: DerObjectHeader<'a>, pub data: &'a [u8], } #[derive(Clone, Debug, PartialEq)] pub struct NameConstraints<'a> { pub permitted_subtrees: Option>>, pub excluded_subtrees: Option>>, } impl<'a> FromDer<'a> for NameConstraints<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_nameconstraints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] /// Represents the structure used in the name constraints extensions. /// The fields minimum and maximum are not supported (openssl also has no support). pub struct GeneralSubtree<'a> { pub base: GeneralName<'a>, // minimum: u32, // maximum: Option, } pub type CRLDistributionPoints<'a> = Vec>; impl<'a> FromDer<'a> for CRLDistributionPoints<'a> { fn from_der(i: &'a [u8]) -> X509Result<'a, Self> { parser::parse_crldistributionpoints(i).map_err(Err::convert) } } #[derive(Clone, Debug, PartialEq)] pub struct CRLDistributionPoint<'a> { pub distribution_point: Option>, pub reasons: Option, pub crl_issuer: Option>>, } #[derive(Clone, Debug, PartialEq)] pub enum DistributionPointName<'a> { FullName(Vec>), NameRelativeToCRLIssuer(RelativeDistinguishedName<'a>), } #[derive(Clone, Debug, PartialEq)] pub struct ReasonFlags { pub flags: u16, } impl ReasonFlags { pub fn key_compromise(&self) -> bool { (self.flags >> 1) & 1 == 1 } pub fn ca_compromise(&self) -> bool { (self.flags >> 2) & 1 == 1 } pub fn affilation_changed(&self) -> bool { (self.flags >> 3) & 1 == 1 } pub fn superseded(&self) -> bool { (self.flags >> 4) & 1 == 1 } pub fn cessation_of_operation(&self) -> bool { (self.flags >> 5) & 1 == 1 } pub fn certificate_hold(&self) -> bool { (self.flags >> 6) & 1 == 1 } pub fn privelege_withdrawn(&self) -> bool { (self.flags >> 7) & 1 == 1 } pub fn aa_compromise(&self) -> bool { (self.flags >> 8) & 1 == 1 } } const REASON_FLAGS: &[&str] = &[ "Unused", "Key Compromise", "CA Compromise", "Affiliation Changed", "Superseded", "Cessation Of Operation", "Certificate Hold", "Privilege Withdrawn", "AA Compromise", ]; impl fmt::Display for ReasonFlags { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut s = String::new(); let mut acc = self.flags; for flag_text in REASON_FLAGS { if acc & 1 != 0 { s = s + flag_text + ", "; } acc >>= 1; } s.pop(); s.pop(); f.write_str(&s) } } pub(crate) mod parser { use crate::extensions::*; use crate::traits::FromDer; use der_parser::error::BerError; use der_parser::{oid::Oid, *}; use lazy_static::lazy_static; use nom::bytes::streaming::take; use nom::combinator::{map, verify}; use nom::{Err, IResult}; type ExtParser = fn(&[u8]) -> IResult<&[u8], ParsedExtension, BerError>; lazy_static! { static ref EXTENSION_PARSERS: HashMap, ExtParser> = { macro_rules! add { ($m:ident, $oid:ident, $p:ident) => { $m.insert($oid, $p as ExtParser); }; } let mut m = HashMap::new(); add!( m, OID_X509_EXT_SUBJECT_KEY_IDENTIFIER, parse_keyidentifier_ext ); add!(m, OID_X509_EXT_KEY_USAGE, parse_keyusage_ext); add!( m, OID_X509_EXT_SUBJECT_ALT_NAME, parse_subjectalternativename_ext ); add!( m, OID_X509_EXT_BASIC_CONSTRAINTS, parse_basicconstraints_ext ); add!(m, OID_X509_EXT_NAME_CONSTRAINTS, parse_nameconstraints_ext); add!( m, OID_X509_EXT_CERTIFICATE_POLICIES, parse_certificatepolicies_ext ); add!(m, OID_X509_EXT_POLICY_MAPPINGS, parse_policymappings_ext); add!( m, OID_X509_EXT_POLICY_CONSTRAINTS, parse_policyconstraints_ext ); add!( m, OID_X509_EXT_EXTENDED_KEY_USAGE, parse_extendedkeyusage_ext ); add!( m, OID_X509_EXT_CRL_DISTRIBUTION_POINTS, parse_crldistributionpoints_ext ); add!( m, OID_X509_EXT_INHIBITANT_ANY_POLICY, parse_inhibitanypolicy_ext ); add!( m, OID_PKIX_AUTHORITY_INFO_ACCESS, parse_authorityinfoaccess_ext ); add!( m, OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER, parse_authoritykeyidentifier_ext ); add!(m, OID_X509_EXT_CERT_TYPE, parse_nscerttype_ext); 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); m }; } // look into the parser map if the extension is known, and parse it // otherwise, leave it as UnsupportedExtension fn parse_extension0<'a>( orig_i: &'a [u8], i: &'a [u8], oid: &Oid, ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { if let Some(parser) = EXTENSION_PARSERS.get(oid) { match parser(i) { Ok((_, ext)) => Ok((orig_i, ext)), Err(error) => Ok((orig_i, ParsedExtension::ParseError { error })), } } else { Ok(( orig_i, ParsedExtension::UnsupportedExtension { oid: oid.to_owned(), }, )) } } pub(crate) fn parse_extension<'a>( orig_i: &'a [u8], i: &'a [u8], oid: &Oid, ) -> IResult<&'a [u8], ParsedExtension<'a>, BerError> { parse_extension0(orig_i, i, oid) } /// Parse a "Basic Constraints" extension /// ///
    ///   id-ce-basicConstraints OBJECT IDENTIFIER ::=  { id-ce 19 }
    ///   BasicConstraints ::= SEQUENCE {
    ///        cA                      BOOLEAN DEFAULT FALSE,
    ///        pathLenConstraint       INTEGER (0..MAX) OPTIONAL }
    /// 
/// /// Note the maximum length of the `pathLenConstraint` field is limited to the size of a 32-bits /// unsigned integer, and parsing will fail if value if larger. pub(super) fn parse_basicconstraints(i: &[u8]) -> IResult<&[u8], BasicConstraints, BerError> { let (rem, obj) = parse_der_sequence(i)?; if let Ok(seq) = obj.as_sequence() { let (ca, path_len_constraint) = match seq.len() { 0 => (false, None), 1 => { if let Ok(b) = seq[0].as_bool() { (b, None) } else if let Ok(u) = seq[0].as_u32() { (false, Some(u)) } else { return Err(nom::Err::Error(BerError::InvalidTag)); } } 2 => { let ca = seq[0] .as_bool() .or(Err(nom::Err::Error(BerError::InvalidLength)))?; let pl = seq[1] .as_u32() .or(Err(nom::Err::Error(BerError::InvalidLength)))?; (ca, Some(pl)) } _ => return Err(nom::Err::Error(BerError::InvalidLength)), }; Ok(( rem, BasicConstraints { ca, path_len_constraint, }, )) } else { Err(nom::Err::Error(BerError::InvalidLength)) } } fn parse_basicconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_basicconstraints, ParsedExtension::BasicConstraints)(i) } pub(super) fn parse_nameconstraints<'a>( i: &'a [u8], ) -> IResult<&'a [u8], NameConstraints, BerError> { fn parse_subtree<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralSubtree, BerError> { parse_der_sequence_defined_g(|input, _| { map(parse_generalname, |base| GeneralSubtree { base })(input) })(i) } fn parse_subtrees(i: &[u8]) -> IResult<&[u8], Vec, BerError> { all_consuming(many1(complete(parse_subtree)))(i) } let (ret, named_constraints) = parse_der_sequence_defined_g(|input, _| { let (rem, permitted_subtrees) = opt(complete(parse_der_tagged_explicit_g(0, |input, _| { parse_subtrees(input) })))(input)?; let (rem, excluded_subtrees) = opt(complete(parse_der_tagged_explicit_g(1, |input, _| { parse_subtrees(input) })))(rem)?; let named_constraints = NameConstraints { permitted_subtrees, excluded_subtrees, }; Ok((rem, named_constraints)) })(i)?; Ok((ret, named_constraints)) } fn parse_nameconstraints_ext<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { map(parse_nameconstraints, ParsedExtension::NameConstraints)(i) } pub(super) fn parse_generalname<'a>(i: &'a [u8]) -> IResult<&'a [u8], GeneralName, BerError> { let (rest, hdr) = verify(der_read_element_header, |hdr| hdr.is_contextspecific())(i)?; let len = hdr.len.primitive()?; if len > rest.len() { return Err(nom::Err::Failure(BerError::ObjectTooShort)); } fn ia5str<'a>(i: &'a [u8], hdr: DerObjectHeader) -> Result<&'a str, Err> { // Relax constraints from RFC here: we are expecting an IA5String, but many certificates // are using unicode characters der_read_element_content_as(i, DerTag::Utf8String, hdr.len, hdr.is_constructed(), 0)? .1 .as_slice() .and_then(|s| std::str::from_utf8(s).map_err(|_| BerError::BerValueError)) .map_err(nom::Err::Failure) } let name = match hdr.tag.0 { 0 => { // otherName SEQUENCE { OID, [0] explicit any defined by oid } let (any, oid) = parse_der_oid(rest)?; let oid = oid.as_oid_val().map_err(nom::Err::Failure)?; GeneralName::OtherName(oid, any) } 1 => GeneralName::RFC822Name(ia5str(rest, hdr)?), 2 => GeneralName::DNSName(ia5str(rest, hdr)?), 3 => { // XXX Not yet implemented let (_, data) = take(len)(rest)?; let obj = UnparsedObject { header: hdr, data }; GeneralName::X400Address(obj) } 4 => { // directoryName, name let (_, name) = all_consuming(X509Name::from_der)(&rest[..len]) .or(Err(BerError::Unsupported)) // XXX remove me ?; GeneralName::DirectoryName(name) } 5 => { // XXX Not yet implemented let (_, data) = take(len)(rest)?; let obj = UnparsedObject { header: hdr, data }; GeneralName::EDIPartyName(obj) } 6 => GeneralName::URI(ia5str(rest, hdr)?), 7 => { // IPAddress, OctetString let ip = der_read_element_content_as( rest, DerTag::OctetString, hdr.len, hdr.is_constructed(), 0, )? .1 .as_slice() .map_err(nom::Err::Failure)?; GeneralName::IPAddress(ip) } 8 => { let oid = der_read_element_content_as( rest, DerTag::Oid, hdr.len, hdr.is_constructed(), 0, )? .1 .as_oid_val() .map_err(nom::Err::Failure)?; GeneralName::RegisteredID(oid) } _ => return Err(Err::Failure(BerError::UnknownTag)), }; Ok((&rest[len..], name)) } pub(super) fn parse_subjectalternativename_ext<'a>( i: &'a [u8], ) -> IResult<&'a [u8], ParsedExtension, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, general_names) = all_consuming(many0(complete(parse_generalname)))(input)?; Ok(( i, ParsedExtension::SubjectAlternativeName(SubjectAlternativeName { general_names }), )) })(i) } pub(super) fn parse_policyconstraints(i: &[u8]) -> IResult<&[u8], PolicyConstraints, BerError> { parse_der_sequence_defined_g(|input, _| { let (i, require_explicit_policy) = opt(complete(map_res( parse_der_tagged_implicit(0, parse_der_content(DerTag::Integer)), |x| x.as_u32(), )))(input)?; let (i, inhibit_policy_mapping) = all_consuming(opt(complete(map_res( parse_der_tagged_implicit(1, parse_der_content(DerTag::Integer)), |x| x.as_u32(), ))))(i)?; let policy_constraint = PolicyConstraints { require_explicit_policy, inhibit_policy_mapping, }; Ok((i, policy_constraint)) })(i) } fn parse_policyconstraints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_policyconstraints, ParsedExtension::PolicyConstraints)(i) } // PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE { // issuerDomainPolicy CertPolicyId, // subjectDomainPolicy CertPolicyId } pub(super) fn parse_policymappings(i: &[u8]) -> IResult<&[u8], PolicyMappings, BerError> { fn parse_oid_pair(i: &[u8]) -> IResult<&[u8], Vec>, BerError> { // read 2 OID as a SEQUENCE OF OID - length will be checked later parse_der_sequence_of_v(parse_der_oid)(i) } let (ret, pairs) = parse_der_sequence_of_v(parse_oid_pair)(i)?; let mut mappings = Vec::new(); // let mut mappings: HashMap> = HashMap::new(); for pair in pairs.iter() { if pair.len() != 2 { return Err(Err::Failure(BerError::BerValueError)); } let left = pair[0].as_oid_val().map_err(nom::Err::Failure)?; let right = pair[1].as_oid_val().map_err(nom::Err::Failure)?; // XXX this should go to Validate // if left.bytes() == oid!(raw 2.5.29.32.0) || right.bytes() == oid!(raw 2.5.29.32.0) { // // mapping to or from anyPolicy is not allowed // return Err(Err::Failure(BerError::InvalidTag)); // } mappings.push(PolicyMapping::new(left, right)); } Ok((ret, PolicyMappings { mappings })) } fn parse_policymappings_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_policymappings, ParsedExtension::PolicyMappings)(i) } fn parse_inhibitanypolicy_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (ret, skip_certs) = parse_der_u32(i)?; Ok(( ret, ParsedExtension::InhibitAnyPolicy(InhibitAnyPolicy { skip_certs }), )) } pub(super) fn parse_extendedkeyusage(i: &[u8]) -> IResult<&[u8], ExtendedKeyUsage, BerError> { let (ret, seq) = parse_der_sequence_of(parse_der_oid)(i)?; let mut seen = std::collections::HashSet::new(); let mut eku = ExtendedKeyUsage { any: false, server_auth: false, client_auth: false, code_signing: false, email_protection: false, time_stamping: false, ocsp_signing: false, other: Vec::new(), }; for oid in seq.as_sequence().map_err(nom::Err::Failure)?.iter() { let oid = oid.as_oid_val().map_err(nom::Err::Failure)?; if !seen.insert(oid.clone()) { continue; } let asn1 = oid.bytes(); if asn1 == oid!(raw 2.5.29.37.0) { eku.any = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.1) { eku.server_auth = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.2) { eku.client_auth = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.3) { eku.code_signing = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.4) { eku.email_protection = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.8) { eku.time_stamping = true; } else if asn1 == oid!(raw 1.3.6.1.5.5.7.3.9) { eku.ocsp_signing = true; } else { eku.other.push(oid); } } Ok((ret, eku)) } fn parse_extendedkeyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_extendedkeyusage, ParsedExtension::ExtendedKeyUsage)(i) } // DistributionPointName ::= CHOICE { // fullName [0] GeneralNames, // nameRelativeToCRLIssuer [1] RelativeDistinguishedName } fn parse_distributionpointname(i: &[u8]) -> IResult<&[u8], DistributionPointName, BerError> { let (rem, header) = der_read_element_header(i)?; match header.tag.0 { 0 => { let (rem, names) = many1(complete(parse_generalname))(rem)?; Ok((rem, DistributionPointName::FullName(names))) } 1 => { let (rem, rdn) = RelativeDistinguishedName::from_der(rem) .map_err(|_| BerError::BerValueError)?; Ok((rem, DistributionPointName::NameRelativeToCRLIssuer(rdn))) } _ => Err(Err::Error(BerError::InvalidTag)), } } // ReasonFlags ::= BIT STRING { // unused (0), // keyCompromise (1), // cACompromise (2), // affiliationChanged (3), // superseded (4), // cessationOfOperation (5), // certificateHold (6), // privilegeWithdrawn (7), // aACompromise (8) } fn parse_tagged1_reasons(i: &[u8]) -> BerResult { let (rem, obj) = parse_der_tagged_implicit(1, parse_der_content(DerTag::BitString))(i)?; if let DerObjectContent::BitString(_, b) = obj.content { let flags = b .data .iter() .rev() .fold(0, |acc, x| acc << 8 | (x.reverse_bits() as u16)); Ok((rem, ReasonFlags { flags })) } else { Err(nom::Err::Failure(BerError::InvalidTag)) } } fn parse_crlissuer_content(i: &[u8]) -> BerResult> { many1(complete(parse_generalname))(i) } // DistributionPoint ::= SEQUENCE { // distributionPoint [0] DistributionPointName OPTIONAL, // reasons [1] ReasonFlags OPTIONAL, // cRLIssuer [2] GeneralNames OPTIONAL } pub(super) fn parse_crldistributionpoint( i: &[u8], ) -> IResult<&[u8], CRLDistributionPoint, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, distribution_point) = opt(complete(parse_der_tagged_explicit_g(0, |b, _| { parse_distributionpointname(b) })))(content)?; let (rem, reasons) = opt(complete(parse_tagged1_reasons))(rem)?; let (rem, crl_issuer) = opt(complete(parse_der_tagged_implicit_g(2, |i, _, _| { parse_crlissuer_content(i) })))(rem)?; let crl_dp = CRLDistributionPoint { distribution_point, reasons, crl_issuer, }; Ok((rem, crl_dp)) })(i) } pub(super) fn parse_crldistributionpoints( i: &[u8], ) -> IResult<&[u8], CRLDistributionPoints, BerError> { parse_der_sequence_of_v(parse_crldistributionpoint)(i) } fn parse_crldistributionpoints_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_crldistributionpoints, ParsedExtension::CRLDistributionPoints, )(i) } // AuthorityInfoAccessSyntax ::= // SEQUENCE SIZE (1..MAX) OF AccessDescription // // AccessDescription ::= SEQUENCE { // accessMethod OBJECT IDENTIFIER, // accessLocation GeneralName } pub(super) fn parse_authorityinfoaccess( i: &[u8], ) -> IResult<&[u8], AuthorityInfoAccess, BerError> { fn parse_aia(i: &[u8]) -> IResult<&[u8], AccessDescription, BerError> { parse_der_sequence_defined_g(|content, _| { // Read first element, an oid. let (gn, oid) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?; // Parse second element let (rest, gn) = parse_generalname(gn)?; Ok((rest, AccessDescription::new(oid, gn))) })(i) } let (ret, accessdescs) = parse_der_sequence_of_v(parse_aia)(i)?; Ok((ret, AuthorityInfoAccess { accessdescs })) } fn parse_authorityinfoaccess_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_authorityinfoaccess, ParsedExtension::AuthorityInfoAccess, )(i) } fn parse_aki_content<'a>( i: &'a [u8], _hdr: DerObjectHeader<'_>, ) -> IResult<&'a [u8], AuthorityKeyIdentifier<'a>, BerError> { let (i, key_identifier) = opt(complete(parse_der_tagged_implicit_g(0, |d, _, _| { Ok((&[], KeyIdentifier(d))) })))(i)?; let (i, authority_cert_issuer) = opt(complete(parse_der_tagged_implicit_g(1, |d, _, _| { many0(complete(parse_generalname))(d) })))(i)?; let (i, authority_cert_serial) = opt(complete(parse_der_tagged_implicit( 2, parse_der_content(DerTag::Integer), )))(i)?; let authority_cert_serial = authority_cert_serial.and_then(|o| o.as_slice().ok()); let aki = AuthorityKeyIdentifier { key_identifier, authority_cert_issuer, authority_cert_serial, }; Ok((i, aki)) } // RFC 5280 section 4.2.1.1: Authority Key Identifier pub(super) fn parse_authoritykeyidentifier( i: &[u8], ) -> IResult<&[u8], AuthorityKeyIdentifier, BerError> { let (rem, aki) = parse_der_sequence_defined_g(parse_aki_content)(i)?; Ok((rem, aki)) } fn parse_authoritykeyidentifier_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_authoritykeyidentifier, ParsedExtension::AuthorityKeyIdentifier, )(i) } pub(super) fn parse_keyidentifier<'a>( i: &'a [u8], ) -> IResult<&'a [u8], KeyIdentifier, BerError> { let (rest, obj) = parse_der_octetstring(i)?; let id = obj .content .as_slice() .or(Err(Err::Error(BerError::BerTypeError)))?; let ki = KeyIdentifier(id); Ok((rest, ki)) } fn parse_keyidentifier_ext<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { map(parse_keyidentifier, ParsedExtension::SubjectKeyIdentifier)(i) } pub(super) fn parse_keyusage(i: &[u8]) -> IResult<&[u8], KeyUsage, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content .as_bitstring() .or(Err(Err::Error(BerError::BerTypeError)))?; let flags = bitstring .data .iter() .rev() .fold(0, |acc, x| acc << 8 | (x.reverse_bits() as u16)); Ok((rest, KeyUsage { flags })) } fn parse_keyusage_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_keyusage, ParsedExtension::KeyUsage)(i) } pub(super) fn parse_nscerttype(i: &[u8]) -> IResult<&[u8], NSCertType, BerError> { let (rest, obj) = parse_der_bitstring(i)?; let bitstring = obj .content .as_bitstring() .or(Err(Err::Error(BerError::BerTypeError)))?; // bitstring should be 1 byte long if bitstring.data.len() != 1 { return Err(Err::Error(BerError::BerValueError)); } let flags = bitstring.data[0].reverse_bits(); Ok((rest, NSCertType(flags))) } fn parse_nscerttype_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map(parse_nscerttype, ParsedExtension::NSCertType)(i) } // CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation // // PolicyInformation ::= SEQUENCE { // policyIdentifier CertPolicyId, // policyQualifiers SEQUENCE SIZE (1..MAX) OF // PolicyQualifierInfo OPTIONAL } // // CertPolicyId ::= OBJECT IDENTIFIER // // PolicyQualifierInfo ::= SEQUENCE { // policyQualifierId PolicyQualifierId, // qualifier ANY DEFINED BY policyQualifierId } // // -- Implementations that recognize additional policy qualifiers MUST // -- augment the following definition for PolicyQualifierId // // PolicyQualifierId ::= OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice ) pub(super) fn parse_certificatepolicies( i: &[u8], ) -> IResult<&[u8], Vec, BerError> { fn parse_policy_qualifier_info(i: &[u8]) -> IResult<&[u8], PolicyQualifierInfo, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_qualifier_id) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?; let info = PolicyQualifierInfo { policy_qualifier_id, qualifier: rem, }; Ok((&[], info)) })(i) } fn parse_policy_information(i: &[u8]) -> IResult<&[u8], PolicyInformation, BerError> { parse_der_sequence_defined_g(|content, _| { let (rem, policy_id) = map_res(parse_der_oid, |x: DerObject| x.as_oid_val())(content)?; let (rem, policy_qualifiers) = opt(complete(parse_der_sequence_defined_g(|content, _| { many1(complete(parse_policy_qualifier_info))(content) })))(rem)?; let info = PolicyInformation { policy_id, policy_qualifiers, }; Ok((rem, info)) })(i) } parse_der_sequence_of_v(parse_policy_information)(i) } fn parse_certificatepolicies_ext(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { map( parse_certificatepolicies, ParsedExtension::CertificatePolicies, )(i) } // CRLReason ::= ENUMERATED { ... fn parse_reason_code<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { let (rest, obj) = parse_der_enum(i)?; let code = obj .content .as_u32() .or(Err(Err::Error(BerError::BerValueError)))?; if code > 10 { return Err(Err::Error(BerError::BerValueError)); } let ret = ParsedExtension::ReasonCode(ReasonCode(code as u8)); Ok((rest, ret)) } // invalidityDate ::= GeneralizedTime fn parse_invalidity_date<'a>(i: &'a [u8]) -> IResult<&'a [u8], ParsedExtension, BerError> { let (rest, date) = map_res(parse_der_generalizedtime, der_to_utctime)(i)?; Ok((rest, ParsedExtension::InvalidityDate(date))) } // CRLNumber ::= INTEGER (0..MAX) // Note from RFC 3280: "CRL verifiers MUST be able to handle CRLNumber values up to 20 octets." fn parse_crl_number(i: &[u8]) -> IResult<&[u8], ParsedExtension, BerError> { let (rest, num) = map_res(parse_der_integer, |obj| obj.as_biguint())(i)?; Ok((rest, ParsedExtension::CRLNumber(num))) } } /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension pub(crate) fn parse_extension_sequence(i: &[u8]) -> X509Result> { parse_der_sequence_defined_g(|a, _| all_consuming(many0(complete(X509Extension::from_der)))(a))( i, ) } pub(crate) fn parse_extensions(i: &[u8], explicit_tag: DerTag) -> X509Result> { if i.is_empty() { return Ok((i, Vec::new())); } match der_read_element_header(i) { Ok((rem, hdr)) => { if hdr.tag != explicit_tag { return Err(Err::Error(X509Error::InvalidExtensions)); } all_consuming(parse_extension_sequence)(rem) } Err(_) => Err(X509Error::InvalidExtensions.into()), } } /// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension pub(crate) fn parse_extension_envelope_sequence(i: &[u8]) -> X509Result> { let parser = X509ExtensionParser::new().with_deep_parse_extensions(false); parse_der_sequence_defined_g(move |a, _| all_consuming(many0(complete(parser)))(a))(i) } pub(crate) fn parse_extensions_envelope( i: &[u8], explicit_tag: DerTag, ) -> X509Result> { if i.is_empty() { return Ok((i, Vec::new())); } match der_read_element_header(i) { Ok((rem, hdr)) => { if hdr.tag != explicit_tag { return Err(Err::Error(X509Error::InvalidExtensions)); } all_consuming(parse_extension_envelope_sequence)(rem) } Err(_) => Err(X509Error::InvalidExtensions.into()), } } fn der_read_critical(i: &[u8]) -> BerResult { // Some certificates do not respect the DER BOOLEAN constraint (true must be encoded as 0xff) // so we attempt to parse as BER let (rem, obj) = opt(parse_ber_bool)(i)?; let value = obj .map(|o| o.as_bool().unwrap_or_default()) // unwrap cannot fail, we just read a bool .unwrap_or(false) // default critical value ; Ok((rem, value)) } #[cfg(test)] mod tests { use super::*; #[test] fn test_keyusage_flags() { let ku = KeyUsage { flags: 98 }; assert!(!ku.digital_signature()); assert!(ku.non_repudiation()); assert!(!ku.key_encipherment()); assert!(!ku.data_encipherment()); assert!(!ku.key_agreement()); assert!(ku.key_cert_sign()); assert!(ku.crl_sign()); assert!(!ku.encipher_only()); assert!(!ku.decipher_only()); } #[test] fn test_extensions1() { use der_parser::oid; let crt = crate::parse_x509_certificate(include_bytes!("../assets/extension1.der")) .unwrap() .1; let tbs = crt.tbs_certificate; assert_eq!( tbs.basic_constraints().unwrap().1, &BasicConstraints { ca: true, path_len_constraint: Some(1) } ); { let ku = tbs.key_usage().unwrap().1; assert!(ku.digital_signature()); assert!(!ku.non_repudiation()); assert!(ku.key_encipherment()); assert!(ku.data_encipherment()); assert!(ku.key_agreement()); assert!(!ku.key_cert_sign()); assert!(!ku.crl_sign()); assert!(ku.encipher_only()); assert!(ku.decipher_only()); } { let eku = tbs.extended_key_usage().unwrap().1; assert!(!eku.any); assert!(eku.server_auth); assert!(!eku.client_auth); assert!(eku.code_signing); assert!(!eku.email_protection); assert!(eku.time_stamping); assert!(!eku.ocsp_signing); assert_eq!(eku.other, vec![oid!(1.2.3 .4 .0 .42)]); } assert_eq!( tbs.policy_constraints().unwrap().1, &PolicyConstraints { require_explicit_policy: None, inhibit_policy_mapping: Some(10) } ); assert_eq!( tbs.inhibit_anypolicy().unwrap().1, &InhibitAnyPolicy { skip_certs: 2 } ); { let alt_names = &tbs.subject_alternative_name().unwrap().1.general_names; assert_eq!(alt_names[0], GeneralName::RFC822Name("foo@example.com")); assert_eq!(alt_names[1], GeneralName::URI("http://my.url.here/")); assert_eq!( alt_names[2], GeneralName::IPAddress([192, 168, 7, 1].as_ref()) ); assert_eq!( format!( "{}", match alt_names[3] { GeneralName::DirectoryName(ref dn) => dn, _ => unreachable!(), } ), "C=UK, O=My Organization, OU=My Unit, CN=My Name" ); assert_eq!(alt_names[4], GeneralName::DNSName("localhost")); assert_eq!(alt_names[5], GeneralName::RegisteredID(oid!(1.2.90 .0))); assert_eq!( alt_names[6], GeneralName::OtherName(oid!(1.2.3 .4), b"\xA0\x17\x0C\x15some other identifier") ); } { let name_constraints = &tbs.name_constraints().unwrap().1; assert_eq!(name_constraints.permitted_subtrees, None); assert_eq!( name_constraints.excluded_subtrees, Some(vec![ GeneralSubtree { base: GeneralName::IPAddress([192, 168, 0, 0, 255, 255, 0, 0].as_ref()) }, GeneralSubtree { base: GeneralName::RFC822Name("foo.com") }, ]) ); } } #[test] fn test_extensions2() { use der_parser::oid; let crt = crate::parse_x509_certificate(include_bytes!("../assets/extension2.der")) .unwrap() .1; let tbs = crt.tbs_certificate; assert_eq!( tbs.policy_constraints().unwrap().1, &PolicyConstraints { require_explicit_policy: Some(5000), inhibit_policy_mapping: None } ); { let pm = tbs.policy_mappings().unwrap().1.clone().into_hashmap(); let mut pm_ref = HashMap::new(); pm_ref.insert(oid!(2.34.23), vec![oid!(2.2)]); pm_ref.insert(oid!(1.1), vec![oid!(0.0.4)]); pm_ref.insert(oid!(2.2), vec![oid!(2.2.1), oid!(2.2.3)]); assert_eq!(pm, pm_ref); } } #[test] fn test_extensions_crl_distribution_points() { // Extension not present { let crt = crate::parse_x509_certificate(include_bytes!("../assets/crl-ext/crl-no-crl.der")) .unwrap() .1; assert!(crt .tbs_certificate .extensions_map() .unwrap() .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .is_none()); } // CRLDistributionPoints has 1 entry with 1 URI { let crt = crate::parse_x509_certificate(include_bytes!("../assets/crl-ext/crl-simple.der")) .unwrap() .1; let crl = crt .tbs_certificate .extensions_map() .unwrap() .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .unwrap() .parsed_extension(); assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); if let ParsedExtension::CRLDistributionPoints(crl) = crl { assert_eq!(crl.len(), 1); assert!(crl[0].reasons.is_none()); assert!(crl[0].crl_issuer.is_none()); let distribution_point = crl[0].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca.crl") } } } } // CRLDistributionPoints has 2 entries { let crt = crate::parse_x509_certificate(include_bytes!("../assets/crl-ext/crl-complex.der")) .unwrap() .1; let crl = crt .tbs_certificate .extensions_map() .unwrap() .get(&OID_X509_EXT_CRL_DISTRIBUTION_POINTS) .unwrap() .parsed_extension(); assert!(matches!(crl, ParsedExtension::CRLDistributionPoints(_))); if let ParsedExtension::CRLDistributionPoints(crl) = crl { assert_eq!(crl.len(), 2); // First CRL Distribution point let reasons = crl[0].reasons.as_ref().unwrap(); assert!(reasons.key_compromise()); assert!(reasons.ca_compromise()); assert!(!reasons.affilation_changed()); assert!(!reasons.superseded()); assert!(!reasons.cessation_of_operation()); assert!(!reasons.certificate_hold()); assert!(!reasons.privelege_withdrawn()); assert!(reasons.aa_compromise()); assert_eq!( format!("{}", reasons), "Key Compromise, CA Compromise, AA Compromise" ); let issuers = crl[0].crl_issuer.as_ref().unwrap(); assert_eq!(issuers.len(), 1); assert!(matches!(issuers[0], GeneralName::DirectoryName(_))); if let GeneralName::DirectoryName(name) = &issuers[0] { assert_eq!(name.to_string(), "C=US, O=Organisation, CN=Some Name"); } let distribution_point = crl[0].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca.crl") } } // Second CRL Distribution point let reasons = crl[1].reasons.as_ref().unwrap(); assert!(reasons.key_compromise()); assert!(reasons.ca_compromise()); assert!(!reasons.affilation_changed()); assert!(!reasons.superseded()); assert!(!reasons.cessation_of_operation()); assert!(!reasons.certificate_hold()); assert!(!reasons.privelege_withdrawn()); assert!(!reasons.aa_compromise()); assert_eq!(format!("{}", reasons), "Key Compromise, CA Compromise"); assert!(crl[1].crl_issuer.is_none()); let distribution_point = crl[1].distribution_point.as_ref().unwrap(); assert!(matches!( distribution_point, DistributionPointName::FullName(_) )); if let DistributionPointName::FullName(names) = distribution_point { assert_eq!(names.len(), 1); assert!(matches!(names[0], GeneralName::URI(_))); if let GeneralName::URI(uri) = names[0] { assert_eq!(uri, "http://example.com/myca2.crl") } } } } } // Test cases for: // - parsing SubjectAlternativeName // - parsing NameConstraints }