diff --git a/Cargo.lock b/Cargo.lock index b9ef1f0c9..1a49b037a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,6 +565,8 @@ dependencies = [ "der", "hex-literal", "spki", + "x501", + "x509", ] [[package]] diff --git a/der/src/asn1/integer/bigint.rs b/der/src/asn1/integer/bigint.rs index 6ab288c2d..2fa60bc4a 100644 --- a/der/src/asn1/integer/bigint.rs +++ b/der/src/asn1/integer/bigint.rs @@ -13,7 +13,7 @@ use crate::{ /// /// Intended for use cases like very large integers that are used in /// cryptographic applications (e.g. keys, signatures). -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] pub struct UIntBytes<'a> { /// Inner value inner: ByteSlice<'a>, diff --git a/pkcs7/Cargo.toml b/pkcs7/Cargo.toml index e04988460..d2b46786e 100644 --- a/pkcs7/Cargo.toml +++ b/pkcs7/Cargo.toml @@ -17,6 +17,8 @@ rust-version = "1.57" [dependencies] der = { version = "=0.6.0-pre.0", features = ["oid"], path = "../der" } spki = { version = "=0.6.0-pre", path = "../spki" } +x509 = { version = "0.0.1", path = "../x509" } +x501 = { version = "0.1.0", path = "../x501" } [dev-dependencies] hex-literal = "0.3" diff --git a/pkcs7/src/cryptographic_message_syntax2004.rs b/pkcs7/src/cryptographic_message_syntax2004.rs new file mode 100644 index 000000000..757a1dcca --- /dev/null +++ b/pkcs7/src/cryptographic_message_syntax2004.rs @@ -0,0 +1,576 @@ +//! Selected structures from RFC5652 + +use core::cmp::Ordering; +use der::asn1::{BitString, ContextSpecific, SetOf, UIntBytes}; +use der::Sequence; +use der::ValueOrd; +use der::{ + Any, DecodeValue, Decoder, Encodable, EncodeValue, ErrorKind, FixedTag, Length, Tag, TagMode, + TagNumber, +}; +use spki::{AlgorithmIdentifier, ObjectIdentifier}; +use x501::attr::AttributeTypeAndValue; +use x501::name::Name; +use x509::SubjectKeyIdentifier; + +/// ContentInfo ::= SEQUENCE { +/// contentType ContentType, +/// content \[0\] EXPLICIT ANY DEFINED BY contentType } +#[derive(Clone, Eq, PartialEq)] +pub struct ContentInfo2004<'a> { + /// contentType ContentType, + pub content_type: ObjectIdentifier, + /// content \[0\] EXPLICIT ANY DEFINED BY contentType } + pub content: Option>, +} + +const CONTENT_TAG: TagNumber = TagNumber::new(0); + +impl<'a> ::der::Decodable<'a> for ContentInfo2004<'a> { + fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { + decoder.sequence(|decoder| { + let content_type = decoder.decode()?; + //let content = decoder.decode()?; + let content = + ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N0)? + .map(|cs| cs.value); + Ok(Self { + content_type, + content, + }) + }) + } +} +impl<'a> ::der::Sequence<'a> for ContentInfo2004<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn Encodable]) -> ::der::Result, + { + f(&[ + &self.content_type, + &self.content.as_ref().map(|content| ContextSpecific { + tag_number: CONTENT_TAG, + tag_mode: TagMode::Explicit, + value: *content, + }), + ]) + } +} + +/// ContentType ::= OBJECT IDENTIFIER +pub type ContentType = ObjectIdentifier; + +/// SignedData ::= SEQUENCE { +/// version CMSVersion, +/// digestAlgorithms DigestAlgorithmIdentifiers, +/// encapContentInfo EncapsulatedContentInfo, +/// certificates \[0\] IMPLICIT CertificateSet OPTIONAL, +/// crls \[1\] IMPLICIT RevocationInfoChoices OPTIONAL, +/// signerInfos SignerInfos } +#[derive(Clone, Eq, PartialEq)] +pub struct SignedData<'a> { + /// version CMSVersion, + pub version: u8, + /// digestAlgorithms DigestAlgorithmIdentifiers, + pub digest_algorithms: DigestAlgorithmIdentifiers<'a>, + /// encapContentInfo EncapsulatedContentInfo, + pub encap_content_info: EncapsulatedContentInfo<'a>, + // Using Any as a means of deferring most of the decoding of the certificates (will still need + // to call to_vec on the resulting Any to restore tag and length values). + /// certificates \[0\] IMPLICIT CertificateSet OPTIONAL, + pub certificates: Option>>, + // TODO support CRLs - placeholder to placate zero length SETs + //pub crls: SetOf, 10>, + /// crls \[1\] IMPLICIT RevocationInfoChoices OPTIONAL, + pub crls: Option>>, + /// signerInfos SignerInfos } + pub signer_infos: SetOf, 10>, +} +const CERTIFICATES_TAG: TagNumber = TagNumber::new(0); +const CRLS_TAG: TagNumber = TagNumber::new(1); + +impl<'a> ::der::Decodable<'a> for SignedData<'a> { + fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { + decoder.sequence(|decoder| { + let version = decoder.decode()?; + let digest_algorithms = decoder.decode()?; + let encap_content_info = decoder.decode()?; + //let certificates = decoder.decode()?; + let certificates = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N0)? + .map(|cs| cs.value); + let crls = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? + .map(|cs| cs.value); + let signer_infos = decoder.decode()?; + Ok(Self { + version, + digest_algorithms, + encap_content_info, + certificates, + crls, + signer_infos, + }) + }) + } +} + +impl<'a> ::der::Sequence<'a> for SignedData<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn Encodable]) -> ::der::Result, + { + f(&[ + &self.version, + &self.digest_algorithms, + &self.encap_content_info, + &self + .certificates + .as_ref() + .map(|certificates| ContextSpecific { + tag_number: CERTIFICATES_TAG, + tag_mode: TagMode::Implicit, + value: certificates.clone(), + }), + &self.crls.as_ref().map(|certificates| ContextSpecific { + tag_number: CRLS_TAG, + tag_mode: TagMode::Implicit, + value: certificates.clone(), + }), + &self.signer_infos, + ]) + } +} + +/// DigestAlgorithmIdentifiers ::= SET OF DigestAlgorithmIdentifier +pub type DigestAlgorithmIdentifiers<'a> = SetOf; +// TODO - make dynamic + +/* + SignerInfos ::= SET OF SignerInfo +*/ + +/// EncapsulatedContentInfo ::= SEQUENCE { +/// eContentType ContentType, +/// eContent \[0\] EXPLICIT OCTET STRING OPTIONAL } +#[derive(Clone, Eq, PartialEq)] +pub struct EncapsulatedContentInfo<'a> { + /// eContentType ContentType, + pub econtent_type: ObjectIdentifier, + /// eContent \[0\] EXPLICIT OCTET STRING OPTIONAL } + pub econtent: Option>, +} +const ECONTENT_TAG: TagNumber = TagNumber::new(0); + +impl<'a> ::der::Decodable<'a> for EncapsulatedContentInfo<'a> { + fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { + decoder.sequence(|decoder| { + let econtent_type = decoder.decode()?; + // let econtent = decoder.decode()?; + let econtent = + ::der::asn1::ContextSpecific::decode_explicit(decoder, ::der::TagNumber::N0)? + .map(|cs| cs.value); + Ok(Self { + econtent_type, + econtent, + }) + }) + } +} +impl<'a> ::der::Sequence<'a> for EncapsulatedContentInfo<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn Encodable]) -> ::der::Result, + { + f(&[ + &self.econtent_type, + &self.econtent.as_ref().map(|econtent| ContextSpecific { + tag_number: ECONTENT_TAG, + tag_mode: TagMode::Explicit, + value: *econtent, + }), + ]) + } +} + +/// SignerInfo ::= SEQUENCE { +/// version CMSVersion, +/// sid SignerIdentifier, +/// digestAlgorithm DigestAlgorithmIdentifier, +/// signedAttrs \[0\] IMPLICIT SignedAttributes OPTIONAL, +/// signatureAlgorithm SignatureAlgorithmIdentifier, +/// signature SignatureValue, +/// unsignedAttrs \[1\] IMPLICIT UnsignedAttributes OPTIONAL } +#[derive(Clone, Eq, PartialEq, Sequence)] +pub struct SignerInfo<'a> { + /// version CMSVersion, + pub version: u8, + /// sid SignerIdentifier, + pub sid: SignerIdentifier<'a>, + /// digestAlgorithm DigestAlgorithmIdentifier, + pub digest_algorithm: AlgorithmIdentifier<'a>, + /// signedAttrs \[0\] IMPLICIT SignedAttributes OPTIONAL, + pub signed_attrs: SignedAttributes<'a>, + /// signatureAlgorithm SignatureAlgorithmIdentifier, + pub signature_algorithm: AlgorithmIdentifier<'a>, + /// signature SignatureValue, + pub signature: BitString<'a>, + /// unsignedAttrs \[1\] IMPLICIT UnsignedAttributes OPTIONAL } + pub unsigned_attrs: UnsignedAttributes<'a>, +} +impl ValueOrd for SignerInfo<'_> { + fn value_cmp(&self, _other: &Self) -> der::Result { + todo!() + } +} + +/// SignerIdentifier ::= CHOICE { +/// issuerAndSerialNumber IssuerAndSerialNumber, +/// subjectKeyIdentifier \[0\] SubjectKeyIdentifier } +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum SignerIdentifier<'a> { + /// issuerAndSerialNumber IssuerAndSerialNumber, + IssuerAndSerialNumber(IssuerAndSerialNumber<'a>), + /// subjectKeyIdentifier \[0\] SubjectKeyIdentifier } + SubjectKeyIdentifier(SubjectKeyIdentifier<'a>), +} + +const SKID_TAG: TagNumber = TagNumber::new(0); +const IASN_TAG: TagNumber = TagNumber::new(30); + +impl<'a> DecodeValue<'a> for SignerIdentifier<'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 { + //TODO FIX + // 0x30 => { + // let on = decoder + // .context_specific::>(IASN_TAG, TagMode::Implicit)?; + // match on { + // Some(on) => Ok(SignerIdentifier::IssuerAndSerialNumber(on)), + // _ => Err(ErrorKind::Failed.into()), + // } + // } + 0xA0 => { + let on = decoder + .context_specific::>(SKID_TAG, TagMode::Implicit)?; + match on { + Some(on) => Ok(SignerIdentifier::SubjectKeyIdentifier(on)), + _ => Err(ErrorKind::Failed.into()), + } + } + _ => Err(ErrorKind::TagUnknown { byte: o }.into()), + } + } +} + +impl<'a> EncodeValue for SignerIdentifier<'a> { + fn encode_value(&self, encoder: &mut ::der::Encoder<'_>) -> ::der::Result<()> { + match self { + Self::IssuerAndSerialNumber(variant) => ContextSpecific { + tag_number: IASN_TAG, + tag_mode: TagMode::Implicit, + value: variant.clone(), + } + .encode(encoder), + Self::SubjectKeyIdentifier(variant) => ContextSpecific { + tag_number: SKID_TAG, + tag_mode: TagMode::Implicit, + value: *variant, + } + .encode(encoder), + } + } + fn value_len(&self) -> ::der::Result<::der::Length> { + match self { + Self::IssuerAndSerialNumber(variant) => ContextSpecific { + tag_number: IASN_TAG, + tag_mode: TagMode::Implicit, + value: variant.clone(), + } + .encoded_len(), + Self::SubjectKeyIdentifier(variant) => ContextSpecific { + tag_number: SKID_TAG, + tag_mode: TagMode::Implicit, + value: *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 SignerIdentifier<'a> { + const TAG: Tag = ::der::Tag::Sequence; +} + +/// SignedAttributes ::= SET SIZE (1..MAX) OF Attribute +pub type SignedAttributes<'a> = SetOf, 10>; + +/// UnsignedAttributes ::= SET SIZE (1..MAX) OF Attribute +pub type UnsignedAttributes<'a> = SetOf, 10>; + +/* + Attribute ::= SEQUENCE { + attrType OBJECT IDENTIFIER, + attrValues SET OF AttributeValue } + + AttributeValue ::= ANY + + SignatureValue ::= OCTET STRING + + EnvelopedData ::= SEQUENCE { + version CMSVersion, + originatorInfo \[0\] IMPLICIT OriginatorInfo OPTIONAL, + recipientInfos RecipientInfos, + encryptedContentInfo EncryptedContentInfo, + unprotectedAttrs \[1\] IMPLICIT UnprotectedAttributes OPTIONAL } + + OriginatorInfo ::= SEQUENCE { + certs \[0\] IMPLICIT CertificateSet OPTIONAL, + crls \[1\] IMPLICIT RevocationInfoChoices OPTIONAL } + + RecipientInfos ::= SET SIZE (1..MAX) OF RecipientInfo + + EncryptedContentInfo ::= SEQUENCE { + contentType ContentType, + contentEncryptionAlgorithm ContentEncryptionAlgorithmIdentifier, + encryptedContent \[0\] IMPLICIT EncryptedContent OPTIONAL } + + EncryptedContent ::= OCTET STRING + + UnprotectedAttributes ::= SET SIZE (1..MAX) OF Attribute + + RecipientInfo ::= CHOICE { + ktri KeyTransRecipientInfo, + kari \[1\] KeyAgreeRecipientInfo, + kekri \[2\] KEKRecipientInfo, + pwri \[3\] PasswordRecipientInfo, + ori \[4\] OtherRecipientInfo } + + EncryptedKey ::= OCTET STRING + + KeyTransRecipientInfo ::= SEQUENCE { + version CMSVersion, -- always set to 0 or 2 + rid RecipientIdentifier, + keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + encryptedKey EncryptedKey } + + RecipientIdentifier ::= CHOICE { + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier \[0\] SubjectKeyIdentifier } + + KeyAgreeRecipientInfo ::= SEQUENCE { + version CMSVersion, -- always set to 3 + originator \[0\] EXPLICIT OriginatorIdentifierOrKey, + ukm \[1\] EXPLICIT UserKeyingMaterial OPTIONAL, + keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + recipientEncryptedKeys RecipientEncryptedKeys } + + OriginatorIdentifierOrKey ::= CHOICE { + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier \[0\] SubjectKeyIdentifier, + originatorKey \[1\] OriginatorPublicKey } + + OriginatorPublicKey ::= SEQUENCE { + algorithm AlgorithmIdentifier, + publicKey BIT STRING } + + RecipientEncryptedKeys ::= SEQUENCE OF RecipientEncryptedKey + + RecipientEncryptedKey ::= SEQUENCE { + rid KeyAgreeRecipientIdentifier, + encryptedKey EncryptedKey } + + KeyAgreeRecipientIdentifier ::= CHOICE { + issuerAndSerialNumber IssuerAndSerialNumber, + rKeyId \[0\] IMPLICIT RecipientKeyIdentifier } + + RecipientKeyIdentifier ::= SEQUENCE { + subjectKeyIdentifier SubjectKeyIdentifier, + date GeneralizedTime OPTIONAL, + other OtherKeyAttribute OPTIONAL } + + SubjectKeyIdentifier ::= OCTET STRING + + KEKRecipientInfo ::= SEQUENCE { + version CMSVersion, -- always set to 4 + kekid KEKIdentifier, + keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + encryptedKey EncryptedKey } + + KEKIdentifier ::= SEQUENCE { + keyIdentifier OCTET STRING, + date GeneralizedTime OPTIONAL, + other OtherKeyAttribute OPTIONAL } + + PasswordRecipientInfo ::= SEQUENCE { + version CMSVersion, -- always set to 0 + keyDerivationAlgorithm \[0\] KeyDerivationAlgorithmIdentifier + OPTIONAL, + keyEncryptionAlgorithm KeyEncryptionAlgorithmIdentifier, + encryptedKey EncryptedKey } + + OtherRecipientInfo ::= SEQUENCE { + oriType OBJECT IDENTIFIER, + oriValue ANY DEFINED BY oriType } + + DigestedData ::= SEQUENCE { + version CMSVersion, + digestAlgorithm DigestAlgorithmIdentifier, + encapContentInfo EncapsulatedContentInfo, + digest Digest } + + Digest ::= OCTET STRING + + EncryptedData ::= SEQUENCE { + version CMSVersion, + encryptedContentInfo EncryptedContentInfo, + unprotectedAttrs \[1\] IMPLICIT UnprotectedAttributes OPTIONAL } + + AuthenticatedData ::= SEQUENCE { + version CMSVersion, + originatorInfo \[0\] IMPLICIT OriginatorInfo OPTIONAL, + recipientInfos RecipientInfos, + macAlgorithm MessageAuthenticationCodeAlgorithm, + digestAlgorithm \[1\] DigestAlgorithmIdentifier OPTIONAL, + encapContentInfo EncapsulatedContentInfo, + authAttrs \[2\] IMPLICIT AuthAttributes OPTIONAL, + mac MessageAuthenticationCode, + unauthAttrs \[3\] IMPLICIT UnauthAttributes OPTIONAL } + + AuthAttributes ::= SET SIZE (1..MAX) OF Attribute + + UnauthAttributes ::= SET SIZE (1..MAX) OF Attribute + + MessageAuthenticationCode ::= OCTET STRING + + DigestAlgorithmIdentifier ::= AlgorithmIdentifier + + SignatureAlgorithmIdentifier ::= AlgorithmIdentifier + + KeyEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier + + ContentEncryptionAlgorithmIdentifier ::= AlgorithmIdentifier + + MessageAuthenticationCodeAlgorithm ::= AlgorithmIdentifier + + KeyDerivationAlgorithmIdentifier ::= AlgorithmIdentifier + + RevocationInfoChoices ::= SET OF RevocationInfoChoice + + RevocationInfoChoice ::= CHOICE { + crl CertificateList, + other \[1\] IMPLICIT OtherRevocationInfoFormat } + + OtherRevocationInfoFormat ::= SEQUENCE { + otherRevInfoFormat OBJECT IDENTIFIER, + otherRevInfo ANY DEFINED BY otherRevInfoFormat } + + CertificateChoices ::= CHOICE { + certificate Certificate, + extendedCertificate \[0\] IMPLICIT ExtendedCertificate, -- Obsolete + v1AttrCert \[1\] IMPLICIT AttributeCertificateV1, -- Obsolete + v2AttrCert \[2\] IMPLICIT AttributeCertificateV2, + other \[3\] IMPLICIT OtherCertificateFormat } + + AttributeCertificateV2 ::= AttributeCertificate + + OtherCertificateFormat ::= SEQUENCE { + otherCertFormat OBJECT IDENTIFIER, + otherCert ANY DEFINED BY otherCertFormat } + + CertificateSet ::= SET OF CertificateChoices +*/ + +/// IssuerAndSerialNumber ::= SEQUENCE { +/// issuer Name, +/// serialNumber CertificateSerialNumber } +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Sequence)] +pub struct IssuerAndSerialNumber<'a> { + /// issuer Name, + pub issuer: Name<'a>, + /// serialNumber CertificateSerialNumber } + pub serial_number: UIntBytes<'a>, +} + +/* + CMSVersion ::= INTEGER { v0(0), v1(1), v2(2), v3(3), v4(4), v5(5) } + + UserKeyingMaterial ::= OCTET STRING + + OtherKeyAttribute ::= SEQUENCE { + keyAttrId OBJECT IDENTIFIER, + keyAttr ANY DEFINED BY keyAttrId OPTIONAL } + + -- Content Type Object Identifiers + + id-ct-contentInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-ct(1) 6 } + + id-data OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs7(7) 1 } + + id-signedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs7(7) 2 } + + id-envelopedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs7(7) 3 } + + id-digestedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs7(7) 5 } + + id-encryptedData OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs7(7) 6 } + + id-ct-authData OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) id-ct(1) 2 } + + -- The CMS Attributes + + MessageDigest ::= OCTET STRING + + SigningTime ::= Time + + Time ::= CHOICE { + utcTime UTCTime, + generalTime GeneralizedTime } + + Countersignature ::= SignerInfo + + -- Attribute Object Identifiers + + id-contentType OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 3 } + + id-messageDigest OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 4 } + + id-signingTime OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 5 } + + id-countersignature OBJECT IDENTIFIER ::= { iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 6 } + + -- Obsolete Extended Certificate syntax from PKCS #6 + + ExtendedCertificateOrCertificate ::= CHOICE { + certificate Certificate, + extendedCertificate \[0\] IMPLICIT ExtendedCertificate } + + ExtendedCertificate ::= SEQUENCE { + extendedCertificateInfo ExtendedCertificateInfo, + signatureAlgorithm SignatureAlgorithmIdentifier, + signature Signature } + + ExtendedCertificateInfo ::= SEQUENCE { + version CMSVersion, + certificate Certificate, + attributes UnauthAttributes } + + Signature ::= BIT STRING + + END -- of CryptographicMessageSyntax2004 +*/ diff --git a/pkcs7/src/lib.rs b/pkcs7/src/lib.rs index 8ca7f3d28..098937212 100644 --- a/pkcs7/src/lib.rs +++ b/pkcs7/src/lib.rs @@ -15,12 +15,15 @@ mod content_type; pub use crate::{content_info::ContentInfo, content_type::ContentType}; +pub mod cryptographic_message_syntax2004; pub mod data_content; pub mod encrypted_data_content; pub mod enveloped_data_content; use der::asn1::ObjectIdentifier; +extern crate alloc; + /// `pkcs-7` Object Identifier (OID). pub const PKCS_7_OID: ObjectIdentifier = ObjectIdentifier::new("1.2.840.113549.1.7"); diff --git a/pkcs7/tests/cryptographic_message_syntax2004.rs b/pkcs7/tests/cryptographic_message_syntax2004.rs new file mode 100644 index 000000000..4927a480b --- /dev/null +++ b/pkcs7/tests/cryptographic_message_syntax2004.rs @@ -0,0 +1,23 @@ +use pkcs7::cryptographic_message_syntax2004::*; +use x509::der::{Decodable, Encodable}; + +#[test] +fn signed_data_parse_test1() { + let der_encoded_sd = include_bytes!("examples/caCertsIssuedTofbcag4.p7c"); + let ci = ContentInfo2004::from_der(der_encoded_sd).unwrap(); + let content = ci.content.unwrap().to_vec().unwrap(); + let _sd = SignedData::from_der(content.as_slice()).unwrap(); + //assert_eq!(1, sd.certificates.unwrap().len()); + + let der_encoded_sd = include_bytes!("examples/DODJITCINTEROPERABILITYROOTCA2_IT.p7c"); + let ci = ContentInfo2004::from_der(der_encoded_sd).unwrap(); + let content = ci.content.unwrap().to_vec().unwrap(); + let sd = SignedData::from_der(content.as_slice()).unwrap(); + assert_eq!(1, sd.certificates.unwrap().len()); + + let der_encoded_sd = include_bytes!("examples/DODROOTCA3_IB.p7c"); + let ci = ContentInfo2004::from_der(der_encoded_sd).unwrap(); + let content = ci.content.unwrap().to_vec().unwrap(); + let sd = SignedData::from_der(content.as_slice()).unwrap(); + assert_eq!(26, sd.certificates.unwrap().len()); +} diff --git a/pkcs7/tests/examples/DODJITCINTEROPERABILITYROOTCA2_IT.p7c b/pkcs7/tests/examples/DODJITCINTEROPERABILITYROOTCA2_IT.p7c new file mode 100644 index 000000000..a37ec8b7c Binary files /dev/null and b/pkcs7/tests/examples/DODJITCINTEROPERABILITYROOTCA2_IT.p7c differ diff --git a/pkcs7/tests/examples/DODROOTCA3_IB.p7c b/pkcs7/tests/examples/DODROOTCA3_IB.p7c new file mode 100644 index 000000000..d02b679f5 Binary files /dev/null and b/pkcs7/tests/examples/DODROOTCA3_IB.p7c differ diff --git a/pkcs7/tests/examples/caCertsIssuedTofbcag4.p7c b/pkcs7/tests/examples/caCertsIssuedTofbcag4.p7c new file mode 100644 index 000000000..cc8de7958 Binary files /dev/null and b/pkcs7/tests/examples/caCertsIssuedTofbcag4.p7c differ diff --git a/x509/src/certificate.rs b/x509/src/certificate.rs index 72cbd2005..0e638bead 100644 --- a/x509/src/certificate.rs +++ b/x509/src/certificate.rs @@ -3,7 +3,7 @@ use crate::Validity; use der::asn1::{BitString, ContextSpecific, ObjectIdentifier, UIntBytes}; -use der::{Sequence, TagMode, TagNumber}; +use der::{DecodeValue, Decoder, Length, Sequence, TagMode, TagNumber}; use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo}; use x501::name::Name; @@ -194,7 +194,7 @@ impl<'a> ::core::fmt::Debug for TBSCertificate<'a> { /// ``` /// /// [RFC 5280 Section 4.1]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1 -#[derive(Clone, Debug, Eq, PartialEq, Sequence)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Certificate<'a> { /// tbsCertificate TBSCertificate, pub tbs_certificate: TBSCertificate<'a>, @@ -204,6 +204,33 @@ pub struct Certificate<'a> { pub signature: BitString<'a>, } +impl<'a> DecodeValue<'a> for Certificate<'a> { + fn decode_value(decoder: &mut Decoder<'a>, _length: Length) -> der::Result { + let tbs_certificate = decoder.decode()?; + let signature_algorithm = decoder.decode()?; + let signature = decoder.decode()?; + Ok(Self { + tbs_certificate, + signature_algorithm, + signature, + }) + // }) + } +} + +impl<'a> ::der::Sequence<'a> for Certificate<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, + { + f(&[ + &self.tbs_certificate, + &self.signature_algorithm, + &self.signature, + ]) + } +} + /// Extension as defined in [RFC 5280 Section 4.1.2.9]. /// /// The ASN.1 definition for Extension objects is below. The extnValue type may be further parsed using a decoder corresponding to the extnID value. 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..36486d591 100644 --- a/x509/src/pkix_extensions.rs +++ b/x509/src/pkix_extensions.rs @@ -340,20 +340,19 @@ pub struct NameConstraints<'a> { 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> DecodeValue<'a> for NameConstraints<'a> { + fn decode_value(decoder: &mut Decoder<'a>, _length: Length) -> der::Result { + 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, }) + // }) } } diff --git a/x509/src/trust_anchor_format.rs b/x509/src/trust_anchor_format.rs new file mode 100644 index 000000000..1dcb78119 --- /dev/null +++ b/x509/src/trust_anchor_format.rs @@ -0,0 +1,344 @@ +//! 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, 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)] +pub struct CertPathControls<'a> { + /// taName Name, + pub ta_name: Name<'a>, + + /// certificate \[0\] Certificate OPTIONAL, + pub certificate: Option>, + + /// policySet \[1\] CertificatePolicies OPTIONAL, + pub policy_set: Option>, + + /// policyFlags \[2\] CertPolicyFlags OPTIONAL, + pub policy_flags: Option>, + + /// nameConstr \[3\] NameConstraints OPTIONAL, + pub name_constr: Option>, + + /// pathLenConstraint\[4\] INTEGER (0..MAX) OPTIONAL} + pub path_len_constraint: Option, +} + +impl<'a> ::der::Decodable<'a> for CertPathControls<'a> { + fn decode(decoder: &mut ::der::Decoder<'a>) -> ::der::Result { + decoder.sequence(|decoder| { + let ta_name = decoder.decode()?; + + let certificate = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N0)? + .map(|cs| cs.value); + let policy_set = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N1)? + .map(|cs| cs.value); + let policy_flags = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N2)? + .map(|cs| cs.value); + + let name_constr = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N3)? + .map(|cs| cs.value); + let path_len_constraint = + ::der::asn1::ContextSpecific::decode_implicit(decoder, ::der::TagNumber::N4)? + .map(|cs| cs.value); + Ok(Self { + ta_name, + certificate, + policy_set, + policy_flags, + name_constr, + path_len_constraint, + }) + }) + } +} +const CPC_CERTIFICATE_TAG: TagNumber = TagNumber::new(0); +const CPC_POLICY_SET_TAG: TagNumber = TagNumber::new(1); +const CPC_POLICY_FLAGS_TAG: TagNumber = TagNumber::new(2); +const CPC_NAME_CONSTRAINTS_TAG: TagNumber = TagNumber::new(3); +const CPC_PATH_LEN_CONSTRAINT_TAG: TagNumber = TagNumber::new(4); +impl<'a> ::der::Sequence<'a> for CertPathControls<'a> { + fn fields(&self, f: F) -> ::der::Result + where + F: FnOnce(&[&dyn der::Encodable]) -> ::der::Result, + { + #[allow(unused_imports)] + use core::convert::TryFrom; + f(&[ + &self.ta_name, + &self.certificate.as_ref().map(|exts| ContextSpecific { + tag_number: CPC_CERTIFICATE_TAG, + tag_mode: TagMode::Implicit, + value: exts.clone(), + }), + &self.policy_set.as_ref().map(|exts| ContextSpecific { + tag_number: CPC_POLICY_SET_TAG, + tag_mode: TagMode::Implicit, + value: exts.clone(), + }), + &self + .policy_flags + .as_ref() + .map(|policy_flags| ContextSpecific { + tag_number: CPC_POLICY_FLAGS_TAG, + tag_mode: TagMode::Implicit, + value: *policy_flags, + }), + &self.name_constr.as_ref().map(|exts| ContextSpecific { + tag_number: CPC_NAME_CONSTRAINTS_TAG, + tag_mode: TagMode::Implicit, + value: exts.clone(), + }), + &self + .path_len_constraint + .as_ref() + .map(|path_len_constraint| ContextSpecific { + tag_number: CPC_PATH_LEN_CONSTRAINT_TAG, + tag_mode: TagMode::Implicit, + value: *path_len_constraint, + }), + ]) + } +} + +/// 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"), + } +}