diff --git a/x509/src/lib.rs b/x509/src/lib.rs index 84cd41292..05050bd95 100644 --- a/x509/src/lib.rs +++ b/x509/src/lib.rs @@ -20,6 +20,7 @@ mod general_name; pub mod pkix_extensions; pub mod pkix_oids; mod time; +pub mod trust_anchor_format; mod validity; pub use crate::{ diff --git a/x509/src/pkix_extensions.rs b/x509/src/pkix_extensions.rs index 9ac5eef4e..8a493b02f 100644 --- a/x509/src/pkix_extensions.rs +++ b/x509/src/pkix_extensions.rs @@ -326,60 +326,17 @@ pub struct PolicyMapping { /// ``` /// /// [RFC 5280 Section 4.2.1.10]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] pub struct NameConstraints<'a> { /// permittedSubtrees [0] GeneralSubtrees OPTIONAL, - //#[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] + #[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] pub permitted_subtrees: Option>, /// excludedSubtrees [1] GeneralSubtrees OPTIONAL } - //#[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] + #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] pub excluded_subtrees: Option>, } -const PERMITTED_SUBTREES_TAG: TagNumber = TagNumber::new(0); -const EXCLUDED_SUBTREES_TAG: TagNumber = TagNumber::new(1); - -impl<'a> ::der::Decodable<'a> for NameConstraints<'a> { - fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { - decoder.sequence(|decoder| { - let permitted_subtrees = - ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N0)? - .map(|cs| cs.value); - let excluded_subtrees = - ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? - .map(|cs| cs.value); - Ok(Self { - permitted_subtrees, - excluded_subtrees, - }) - }) - } -} - -impl<'a> ::der::Sequence<'a> for NameConstraints<'a> { - fn fields(&self, f: F) -> ::der::Result - where - F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, - { - f(&[ - &self - .permitted_subtrees - .as_ref() - .map(|elem| ContextSpecific { - tag_number: PERMITTED_SUBTREES_TAG, - tag_mode: TagMode::Implicit, - value: elem.clone(), - }), - &self.excluded_subtrees.as_ref().map(|elem| ContextSpecific { - tag_number: EXCLUDED_SUBTREES_TAG, - tag_mode: TagMode::Implicit, - value: elem.clone(), - }), - ]) - } -} - /// GeneralSubtrees as defined in [RFC 5280 Section 4.2.1.10] in support of the Name Constraints extension. /// /// ```text diff --git a/x509/src/trust_anchor_format.rs b/x509/src/trust_anchor_format.rs new file mode 100644 index 000000000..ec2cf8539 --- /dev/null +++ b/x509/src/trust_anchor_format.rs @@ -0,0 +1,268 @@ +//! Trust anchor-related structures as defined in RFC 5914 + +use crate::{Certificate, CertificatePolicies, Extensions, NameConstraints}; +use der::asn1::{BitString, ContextSpecific, OctetString, Utf8String}; +use der::{ + DecodeValue, Decoder, Encodable, EncodeValue, ErrorKind, FixedTag, Length, Sequence, Tag, + TagMode, TagNumber, +}; +use spki::SubjectPublicKeyInfo; +use x501::name::Name; + +/// TrustAnchorInfo ::= SEQUENCE { +/// version TrustAnchorInfoVersion DEFAULT v1, +/// pubKey SubjectPublicKeyInfo, +/// keyId KeyIdentifier, +/// taTitle TrustAnchorTitle OPTIONAL, +/// certPath CertPathControls OPTIONAL, +/// exts \[1\] EXPLICIT Extensions OPTIONAL, +/// taTitleLangTag \[2\] UTF8String OPTIONAL } +/// +/// TrustAnchorInfoVersion ::= INTEGER { v1(1) } +/// +/// TrustAnchorTitle ::= UTF8String (SIZE (1..64)) +#[derive(Clone, Eq, PartialEq)] +pub struct TrustAnchorInfo<'a> { + /// version TrustAnchorInfoVersion DEFAULT v1, + pub version: Option, + + /// pubKey SubjectPublicKeyInfo, + pub pub_key: SubjectPublicKeyInfo<'a>, + + /// keyId KeyIdentifier, + pub key_id: OctetString<'a>, + + /// taTitle TrustAnchorTitle OPTIONAL, + pub ta_title: Option>, + + /// certPath CertPathControls OPTIONAL, + pub cert_path: Option>, + + /// exts \[1\] EXPLICIT Extensions OPTIONAL, + pub extensions: Option>, + + /// taTitleLangTag \[2\] UTF8String OPTIONAL } + pub ta_title_lang_tag: Option>, +} + +// impl<'a> ::der::Decodable<'a> for TrustAnchorInfo<'a> { +// fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { +impl<'a> DecodeValue<'a> for TrustAnchorInfo<'a> { + fn decode_value(decoder: &mut Decoder<'a>, _length: Length) -> der::Result { + let version = match decoder.decode()? { + Some(v) => Some(v), + _ => Some(1), + }; + + let pub_key = decoder.decode()?; + let key_id = decoder.decode()?; + let ta_title = decoder.decode()?; + let cert_path = decoder.decode()?; + let extensions = + ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N1)? + .map(|cs| cs.value); + let ta_title_lang_tag = + ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N2)? + .map(|cs| cs.value); + Ok(Self { + version, + pub_key, + key_id, + ta_title, + cert_path, + extensions, + ta_title_lang_tag, + }) + } +} + +const TAF_EXTENSIONS_TAG: TagNumber = TagNumber::new(1); +const TA_TITLE_LANG_TAG: TagNumber = TagNumber::new(0); +impl<'a> ::der::Sequence<'a> for TrustAnchorInfo<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, + { + #[allow(unused_imports)] + use core::convert::TryFrom; + f(&[ + &::der::asn1::OptionalRef(if self.version == Some(1) { + None + } else { + Some(&self.version) + }), + &self.pub_key, + &self.key_id, + &self.ta_title, + &self.cert_path, + &self.extensions.as_ref().map(|exts| ContextSpecific { + tag_number: TAF_EXTENSIONS_TAG, + tag_mode: TagMode::Explicit, + value: exts.clone(), + }), + &self + .ta_title_lang_tag + .as_ref() + .map(|ta_title_lang_tag| ContextSpecific { + tag_number: TA_TITLE_LANG_TAG, + tag_mode: TagMode::Implicit, + value: *ta_title_lang_tag, + }), + ]) + } +} + +impl<'a> ::core::fmt::Debug for TrustAnchorInfo<'a> { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.write_fmt(format_args!("\n\tVersion: {:02X?}\n", self.version))?; + f.write_fmt(format_args!("\tPublic Key Info: {:?}\n", self.pub_key))?; + f.write_fmt(format_args!("\tKey ID: {:?}\n", self.key_id))?; + f.write_fmt(format_args!("\tTA title: {:?}\n", self.ta_title))?; + f.write_fmt(format_args!( + "\tTA title language tag: {:?}\n", + self.ta_title_lang_tag + ))?; + f.write_fmt(format_args!( + "\tCertificate path controls: {:?}\n", + self.cert_path + ))?; + if let Some(exts) = self.extensions.as_ref() { + for (i, e) in exts.iter().enumerate() { + f.write_fmt(format_args!("\tExtension #{}: {:?}\n", i, e))?; + } + } else { + f.write_fmt(format_args!("\tExtensions: None\n"))?; + } + Ok(()) + } +} + +/// CertPathControls ::= SEQUENCE { +/// taName Name, +/// certificate \[0\] Certificate OPTIONAL, +/// policySet \[1\] CertificatePolicies OPTIONAL, +/// policyFlags \[2\] CertPolicyFlags OPTIONAL, +/// nameConstr \[3\] NameConstraints OPTIONAL, +/// pathLenConstraint\[4\] INTEGER (0..MAX) OPTIONAL} +#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +pub struct CertPathControls<'a> { + /// taName Name, + pub ta_name: Name<'a>, + + /// certificate \[0\] Certificate OPTIONAL, + #[asn1(context_specific = "0", optional = "true", tag_mode = "IMPLICIT")] + pub certificate: Option>, + + /// policySet \[1\] CertificatePolicies OPTIONAL, + #[asn1(context_specific = "1", optional = "true", tag_mode = "IMPLICIT")] + pub policy_set: Option>, + + /// policyFlags \[2\] CertPolicyFlags OPTIONAL, + #[asn1(context_specific = "2", optional = "true", tag_mode = "IMPLICIT")] + pub policy_flags: Option>, + + /// nameConstr \[3\] NameConstraints OPTIONAL, + #[asn1(context_specific = "3", optional = "true", tag_mode = "IMPLICIT")] + pub name_constr: Option>, + + /// pathLenConstraint\[4\] INTEGER (0..MAX) OPTIONAL} + #[asn1(context_specific = "4", optional = "true", tag_mode = "IMPLICIT")] + pub path_len_constraint: Option, +} + +/// CertPolicyFlags ::= BIT STRING { +/// inhibitPolicyMapping (0), +/// requireExplicitPolicy (1), +/// inhibitAnyPolicy (2) } +pub type CertPolicyFlags<'a> = BitString<'a>; + +/// TrustAnchorChoice ::= CHOICE { +/// certificate Certificate, +/// tbsCert \[1\] EXPLICIT TBSCertificate, +/// taInfo \[2\] EXPLICIT TrustAnchorInfo } +#[derive(Clone, Debug, Eq, PartialEq)] +#[allow(clippy::large_enum_variant)] +pub enum TrustAnchorChoice<'a> { + /// certificate Certificate, + Certificate(Certificate<'a>), + // Not supporting TBSCertificate option + // tbsCert \[1\] EXPLICIT TBSCertificate, + //TbsCertificate(TBSCertificate<'a>), + /// taInfo \[2\] EXPLICIT TrustAnchorInfo } + TaInfo(TrustAnchorInfo<'a>), +} + +//const TAC_TBS_CERTIFICATE_TAG: TagNumber = TagNumber::new(1); +const TAC_TA_INFO_TAG: TagNumber = TagNumber::new(2); + +impl<'a> DecodeValue<'a> for TrustAnchorChoice<'a> { + fn decode_value(decoder: &mut Decoder<'a>, _length: Length) -> der::Result { + let t = decoder.peek_tag()?; + let o = t.octet(); + // Context specific support always returns an Option<>, just ignore since OPTIONAL does not apply here + match o { + 0x30 => { + let cert = decoder.decode()?; + Ok(TrustAnchorChoice::Certificate(cert)) + } + // TODO - need DecodeValue on TBSCertificate to support this + // 0xA1 => { + // let on = decoder + // .context_specific::>(TAC_TBS_CERTIFICATE_TAG, TagMode::Explicit)?; + // match on { + // Some(on) => Ok(TrustAnchorChoice::TbsCertificate(on)), + // _ => Err(ErrorKind::Failed.into()), + // } + // } + 0xA2 => { + let on = decoder + .context_specific::>(TAC_TA_INFO_TAG, TagMode::Explicit)?; + match on { + Some(on) => Ok(TrustAnchorChoice::TaInfo(on)), + _ => Err(ErrorKind::Failed.into()), + } + } + _ => Err(ErrorKind::TagUnknown { byte: o }.into()), + } + } +} + +impl<'a> EncodeValue for TrustAnchorChoice<'a> { + fn encode_value(&self, encoder: &mut ::der::Encoder<'_>) -> ::der::Result<()> { + match self { + Self::Certificate(certificate) => certificate.encode(encoder), + // Self::TbsCertificate(variant) => ContextSpecific { + // tag_number: TAC_TBS_CERTIFICATE_TAG, + // tag_mode: TagMode::Explicit, + // value: variant.clone(), + // }.encode(encoder), + Self::TaInfo(variant) => variant.encode(encoder), + } + } + fn value_len(&self) -> ::der::Result<::der::Length> { + match self { + Self::Certificate(certificate) => certificate.encoded_len(), + // Self::TbsCertificate(variant) => ContextSpecific { + // tag_number: TAC_TBS_CERTIFICATE_TAG, + // tag_mode: TagMode::Explicit, + // value: variant.clone(), + // }.encoded_len(), + Self::TaInfo(variant) => variant.encoded_len(), + } + } +} + +//TODO - see why this is necessary to avoid problem at line 78 in context_specific.rs due to mismatched tag +impl<'a> FixedTag for TrustAnchorChoice<'a> { + const TAG: Tag = ::der::Tag::ContextSpecific { + constructed: true, + number: TAC_TA_INFO_TAG, + }; +} + +// Not supporting these structures +// TrustAnchorList ::= SEQUENCE SIZE (1..MAX) OF TrustAnchorChoice +// +// id-ct-trustAnchorList OBJECT IDENTIFIER ::= { iso(1) +// member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) +// id-smime(16) id-ct(1) 34 } diff --git a/x509/tests/examples/eca.der b/x509/tests/examples/eca.der new file mode 100644 index 000000000..fdd35d53b Binary files /dev/null and b/x509/tests/examples/eca.der differ diff --git a/x509/tests/examples/eca_policies.ta b/x509/tests/examples/eca_policies.ta new file mode 100755 index 000000000..717364a2e Binary files /dev/null and b/x509/tests/examples/eca_policies.ta differ diff --git a/x509/tests/examples/entrust.der b/x509/tests/examples/entrust.der new file mode 100644 index 000000000..dbddf4923 Binary files /dev/null and b/x509/tests/examples/entrust.der differ diff --git a/x509/tests/examples/entrust_dnConstraint.ta b/x509/tests/examples/entrust_dnConstraint.ta new file mode 100755 index 000000000..ff784dd75 Binary files /dev/null and b/x509/tests/examples/entrust_dnConstraint.ta differ diff --git a/x509/tests/examples/exostar.der b/x509/tests/examples/exostar.der new file mode 100644 index 000000000..d2047e49e Binary files /dev/null and b/x509/tests/examples/exostar.der differ diff --git a/x509/tests/examples/exostar_policyFlags.ta b/x509/tests/examples/exostar_policyFlags.ta new file mode 100755 index 000000000..46e5f9248 Binary files /dev/null and b/x509/tests/examples/exostar_policyFlags.ta differ diff --git a/x509/tests/examples/raytheon.der b/x509/tests/examples/raytheon.der new file mode 100644 index 000000000..9b577432d Binary files /dev/null and b/x509/tests/examples/raytheon.der differ diff --git a/x509/tests/examples/raytheon_pathLenConstraint.ta b/x509/tests/examples/raytheon_pathLenConstraint.ta new file mode 100644 index 000000000..eb0fd7f35 Binary files /dev/null and b/x509/tests/examples/raytheon_pathLenConstraint.ta differ diff --git a/x509/tests/trust_anchor_format.rs b/x509/tests/trust_anchor_format.rs new file mode 100644 index 000000000..62d4eafcd --- /dev/null +++ b/x509/tests/trust_anchor_format.rs @@ -0,0 +1,410 @@ +use der::Decoder; +use hex_literal::hex; +use x509::der::{DecodeValue, Encodable}; +use x509::trust_anchor_format::TrustAnchorChoice; +use x509::*; + +#[test] +fn decode_ta1() { + // features an ECA cert wrapped in a TrustAnchorInfo that contains a pile of certificate policies + // in the cert path controls field + let der_encoded_tac = include_bytes!("examples/eca_policies.ta"); + let der_encoded_cert = include_bytes!("examples/eca.der"); + + let mut decoder = Decoder::new(der_encoded_tac).unwrap(); + let header = decoder.peek_header().unwrap(); + let tac = TrustAnchorChoice::decode_value(&mut decoder, header.length).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("335BA56F7A55602B814B2614CC79BF4ABA8B32BD"), + tai.key_id.as_bytes() + ); + + let policy_ids: [&str; 42] = [ + "1.2.36.1.334.1.2.1.2", + "1.2.840.113549.5.6.1.3.1.12", + "1.2.840.113549.5.6.1.3.1.18", + "1.3.6.1.4.1.103.100.1.1.3.1", + "1.3.6.1.4.1.13948.1.1.1.2", + "1.3.6.1.4.1.13948.1.1.1.6", + "1.3.6.1.4.1.1569.10.1.1", + "1.3.6.1.4.1.1569.10.1.2", + "1.3.6.1.4.1.16304.3.6.2.12", + "1.3.6.1.4.1.16304.3.6.2.20", + "1.3.6.1.4.1.16334.509.2.6", + "1.3.6.1.4.1.23337.1.1.10", + "1.3.6.1.4.1.23337.1.1.8", + "1.3.6.1.4.1.2396.2.1.2", + "1.3.6.1.4.1.2396.2.1.7", + "1.3.6.1.4.1.24019.1.1.1.18", + "1.3.6.1.4.1.24019.1.1.1.19", + "1.3.6.1.4.1.24019.1.1.1.2", + "1.3.6.1.4.1.24019.1.1.1.7", + "1.3.6.1.4.1.73.15.3.1.12", + "1.3.6.1.4.1.73.15.3.1.5", + "2.16.528.1.1003.1.2.5.1", + "2.16.528.1.1003.1.2.5.2", + "2.16.840.1.101.2.1.11.19", + "2.16.840.1.101.3.2.1.12.2", + "2.16.840.1.101.3.2.1.12.3", + "2.16.840.1.101.3.2.1.3.12", + "2.16.840.1.101.3.2.1.3.13", + "2.16.840.1.101.3.2.1.3.16", + "2.16.840.1.101.3.2.1.3.18", + "2.16.840.1.101.3.2.1.3.24", + "2.16.840.1.101.3.2.1.3.4", + "2.16.840.1.101.3.2.1.3.7", + "2.16.840.1.101.3.2.1.5.4", + "2.16.840.1.101.3.2.1.5.5", + "2.16.840.1.101.3.2.1.6.12", + "2.16.840.1.101.3.2.1.6.4", + "2.16.840.1.113733.1.7.23.3.1.18", + "2.16.840.1.113733.1.7.23.3.1.7", + "2.16.840.1.114027.200.3.10.7.2", + "2.16.840.1.114027.200.3.10.7.4", + "2.16.840.1.114027.200.3.10.7.6", + ]; + + let cert_path = tai.cert_path.as_ref().unwrap(); + let mut counter = 0; + let exts = cert_path.policy_set.as_ref().unwrap(); + let i = exts.iter(); + for ext in i { + assert_eq!(policy_ids[counter], ext.policy_identifier.to_string()); + counter += 1; + } + + counter = 0; + let i = cert_path.ta_name.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.6"); + assert_eq!(atav.value.printable_string().unwrap().to_string(), "US"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "U.S. Government" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!(atav.value.printable_string().unwrap().to_string(), "ECA"); + } else if 3 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.3"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "ECA Root CA 4" + ); + } + counter += 1; + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta2() { + // features an Entrust cert wrapped in a TrustAnchorInfo that contains an excluded subtree in the + // name constraint in the cert path controls field + let der_encoded_tac = include_bytes!("examples/entrust_dnConstraint.ta"); + let der_encoded_cert = include_bytes!("examples/entrust.der"); + + let mut decoder = Decoder::new(der_encoded_tac).unwrap(); + let header = decoder.peek_header().unwrap(); + let tac = TrustAnchorChoice::decode_value(&mut decoder, header.length).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("1A74551E8A85089F505D3E8A46018A819CF99E1E"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + let mut counter = 0; + let i = cert_path.ta_name.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.6"); + assert_eq!(atav.value.printable_string().unwrap().to_string(), "US"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Entrust" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Certification Authorities" + ); + } else if 3 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Entrust Managed Services NFI Root CA" + ); + } + counter += 1; + } + } + + let nc = cert_path.name_constr.as_ref().unwrap(); + counter = 0; + let gsi = nc.excluded_subtrees.as_ref().unwrap().iter(); + for gs in gsi { + match &gs.base { + GeneralName::DirectoryName(dn) => { + let i = dn.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.6"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "US" + ); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "U.S. Government" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "DoD" + ); + } + counter += 1; + } + } + } + _ => panic!("Unexpected GeneralSubtree type"), + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta3() { + // features an Exostar cert wrapped in a TrustAnchorInfo that contains an excluded subtree in the + // name constraint and policy flags in the cert path controls field + let der_encoded_tac = include_bytes!("examples/exostar_policyFlags.ta"); + let der_encoded_cert = include_bytes!("examples/exostar.der"); + + let mut decoder = Decoder::new(der_encoded_tac).unwrap(); + let header = decoder.peek_header().unwrap(); + let tac = TrustAnchorChoice::decode_value(&mut decoder, header.length).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("2EBE91A6776A373CF5FD1DB6DD78C9A6E5F42220"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + let cpf = cert_path.policy_flags.unwrap(); + let b = cpf.raw_bytes(); + if 0x80 != 0x80 & b[0] { + panic!("Missing policy flag bit 0") + } + if 0x40 != 0x40 & b[0] { + panic!("Missing policy flag bit 1") + } + if 0x20 != 0x20 & b[0] { + panic!("Missing policy flag bit 2") + } + if cpf.unused_bits() != 5 { + panic!("Wrong unused bits for policy flags") + } + + let mut counter = 0; + let i = cert_path.ta_name.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.6"); + assert_eq!(atav.value.printable_string().unwrap().to_string(), "US"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Exostar LLC" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Certification Authorities" + ); + } else if 3 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.3"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "Exostar Federated Identity Service Root CA 1" + ); + } + counter += 1; + } + } + + let nc = cert_path.name_constr.as_ref().unwrap(); + counter = 0; + let gsi = nc.excluded_subtrees.as_ref().unwrap().iter(); + for gs in gsi { + match &gs.base { + GeneralName::DirectoryName(dn) => { + let i = dn.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.6"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "US" + ); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "U.S. Government" + ); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "DoD" + ); + } + counter += 1; + } + } + } + _ => panic!("Unexpected GeneralSubtree type"), + } + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +} + +#[test] +fn decode_ta4() { + // features an Exostar cert wrapped in a TrustAnchorInfo that contains path length constraint in + // the cert path controls field + let der_encoded_tac = include_bytes!("examples/raytheon_pathLenConstraint.ta"); + let der_encoded_cert = include_bytes!("examples/raytheon.der"); + + let mut decoder = Decoder::new(der_encoded_tac).unwrap(); + let header = decoder.peek_header().unwrap(); + let tac = TrustAnchorChoice::decode_value(&mut decoder, header.length).unwrap(); + let reencoded_tac = tac.to_vec().unwrap(); + println!("Original : {:02X?}", der_encoded_cert); + println!("Reencoded: {:02X?}", reencoded_tac); + assert_eq!(der_encoded_tac, reencoded_tac.as_slice()); + + match tac { + TrustAnchorChoice::TaInfo(tai) => { + assert_eq!( + tai.pub_key.algorithm.oid.to_string(), + "1.2.840.113549.1.1.1" + ); + + assert_eq!( + &hex!("283086D556154210425CF07B1C11B28389D47920"), + tai.key_id.as_bytes() + ); + + let cert_path = tai.cert_path.as_ref().unwrap(); + + let mut counter = 0; + let i = cert_path.ta_name.iter(); + for rdn in i { + let i1 = rdn.iter(); + for atav in i1 { + if 0 == counter { + assert_eq!(atav.oid.to_string(), "0.9.2342.19200300.100.1.25"); + assert_eq!(atav.value.ia5_string().unwrap().to_string(), "com"); + } else if 1 == counter { + assert_eq!(atav.oid.to_string(), "0.9.2342.19200300.100.1.25"); + assert_eq!(atav.value.ia5_string().unwrap().to_string(), "raytheon"); + } else if 2 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.10"); + assert_eq!(atav.value.printable_string().unwrap().to_string(), "CAs"); + } else if 3 == counter { + assert_eq!(atav.oid.to_string(), "2.5.4.11"); + assert_eq!( + atav.value.printable_string().unwrap().to_string(), + "RaytheonRoot" + ); + } + counter += 1; + } + } + + let pl = cert_path.path_len_constraint.unwrap(); + if 2 != pl { + panic!("Wrong path length constraint"); + } + + let reencoded_cert = cert_path.certificate.to_vec().unwrap(); + assert_eq!(der_encoded_cert, reencoded_cert.as_slice()); + } + _ => panic!("Unexpected TrustAnchorChoice contents"), + } +}