| //! Tests for custom derive support. |
| //! |
| //! # Debugging with `cargo expand` |
| //! |
| //! To expand the Rust code generated by the proc macro when debugging |
| //! issues related to these tests, run: |
| //! |
| //! $ cargo expand --test derive --all-features |
| |
| #![cfg(all(feature = "derive", feature = "alloc"))] |
| |
| /// Custom derive test cases for the `Choice` macro. |
| mod choice { |
| /// `Choice` with `EXPLICIT` tagging. |
| mod explicit { |
| use der::{ |
| asn1::{GeneralizedTime, UtcTime}, |
| Choice, Decode, Encode, SliceWriter, |
| }; |
| use hex_literal::hex; |
| use std::time::Duration; |
| |
| /// Custom derive test case for the `Choice` macro. |
| /// |
| /// Based on `Time` as defined in RFC 5280: |
| /// <https://tools.ietf.org/html/rfc5280#page-117> |
| /// |
| /// ```text |
| /// Time ::= CHOICE { |
| /// utcTime UTCTime, |
| /// generalTime GeneralizedTime } |
| /// ``` |
| #[derive(Choice)] |
| pub enum Time { |
| #[asn1(type = "UTCTime")] |
| UtcTime(UtcTime), |
| |
| #[asn1(type = "GeneralizedTime")] |
| GeneralTime(GeneralizedTime), |
| } |
| |
| impl Time { |
| fn to_unix_duration(self) -> Duration { |
| match self { |
| Time::UtcTime(t) => t.to_unix_duration(), |
| Time::GeneralTime(t) => t.to_unix_duration(), |
| } |
| } |
| } |
| |
| const UTC_TIMESTAMP_DER: &[u8] = &hex!("17 0d 39 31 30 35 30 36 32 33 34 35 34 30 5a"); |
| const GENERAL_TIMESTAMP_DER: &[u8] = |
| &hex!("18 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); |
| |
| #[test] |
| fn decode() { |
| let utc_time = Time::from_der(UTC_TIMESTAMP_DER).unwrap(); |
| assert_eq!(utc_time.to_unix_duration().as_secs(), 673573540); |
| |
| let general_time = Time::from_der(GENERAL_TIMESTAMP_DER).unwrap(); |
| assert_eq!(general_time.to_unix_duration().as_secs(), 673573540); |
| } |
| |
| #[test] |
| fn encode() { |
| let mut buf = [0u8; 128]; |
| |
| let utc_time = Time::from_der(UTC_TIMESTAMP_DER).unwrap(); |
| let mut encoder = SliceWriter::new(&mut buf); |
| utc_time.encode(&mut encoder).unwrap(); |
| assert_eq!(UTC_TIMESTAMP_DER, encoder.finish().unwrap()); |
| |
| let general_time = Time::from_der(GENERAL_TIMESTAMP_DER).unwrap(); |
| let mut encoder = SliceWriter::new(&mut buf); |
| general_time.encode(&mut encoder).unwrap(); |
| assert_eq!(GENERAL_TIMESTAMP_DER, encoder.finish().unwrap()); |
| } |
| } |
| |
| /// `Choice` with `IMPLICIT` tagging. |
| mod implicit { |
| use der::{ |
| asn1::{BitStringRef, GeneralizedTime}, |
| Choice, Decode, Encode, SliceWriter, |
| }; |
| use hex_literal::hex; |
| |
| /// `Choice` macro test case for `IMPLICIT` tagging. |
| #[derive(Choice, Debug, Eq, PartialEq)] |
| #[asn1(tag_mode = "IMPLICIT")] |
| pub enum ImplicitChoice<'a> { |
| #[asn1(context_specific = "0", type = "BIT STRING")] |
| BitString(BitStringRef<'a>), |
| |
| #[asn1(context_specific = "1", type = "GeneralizedTime")] |
| Time(GeneralizedTime), |
| |
| #[asn1(context_specific = "2", type = "UTF8String")] |
| Utf8String(String), |
| } |
| |
| impl<'a> ImplicitChoice<'a> { |
| pub fn bit_string(&self) -> Option<BitStringRef<'a>> { |
| match self { |
| Self::BitString(bs) => Some(*bs), |
| _ => None, |
| } |
| } |
| |
| pub fn time(&self) -> Option<GeneralizedTime> { |
| match self { |
| Self::Time(time) => Some(*time), |
| _ => None, |
| } |
| } |
| } |
| |
| const BITSTRING_DER: &[u8] = &hex!("80 04 00 01 02 03"); |
| const TIME_DER: &[u8] = &hex!("81 0f 31 39 39 31 30 35 30 36 32 33 34 35 34 30 5a"); |
| |
| #[test] |
| fn decode() { |
| let cs_bit_string = ImplicitChoice::from_der(BITSTRING_DER).unwrap(); |
| assert_eq!( |
| cs_bit_string.bit_string().unwrap().as_bytes().unwrap(), |
| &[1, 2, 3] |
| ); |
| |
| let cs_time = ImplicitChoice::from_der(TIME_DER).unwrap(); |
| assert_eq!( |
| cs_time.time().unwrap().to_unix_duration().as_secs(), |
| 673573540 |
| ); |
| } |
| |
| #[test] |
| fn encode() { |
| let mut buf = [0u8; 128]; |
| |
| let cs_bit_string = ImplicitChoice::from_der(BITSTRING_DER).unwrap(); |
| let mut encoder = SliceWriter::new(&mut buf); |
| cs_bit_string.encode(&mut encoder).unwrap(); |
| assert_eq!(BITSTRING_DER, encoder.finish().unwrap()); |
| |
| let cs_time = ImplicitChoice::from_der(TIME_DER).unwrap(); |
| let mut encoder = SliceWriter::new(&mut buf); |
| cs_time.encode(&mut encoder).unwrap(); |
| assert_eq!(TIME_DER, encoder.finish().unwrap()); |
| } |
| } |
| } |
| |
| /// Custom derive test cases for the `Enumerated` macro. |
| mod enumerated { |
| use der::{Decode, Encode, Enumerated, SliceWriter}; |
| use hex_literal::hex; |
| |
| /// X.509 `CRLReason`. |
| #[derive(Enumerated, Copy, Clone, Debug, Eq, PartialEq)] |
| #[repr(u32)] |
| pub enum CrlReason { |
| Unspecified = 0, |
| KeyCompromise = 1, |
| CaCompromise = 2, |
| AffiliationChanged = 3, |
| Superseded = 4, |
| CessationOfOperation = 5, |
| CertificateHold = 6, |
| RemoveFromCrl = 8, |
| PrivilegeWithdrawn = 9, |
| AaCompromised = 10, |
| } |
| |
| const UNSPECIFIED_DER: &[u8] = &hex!("0a 01 00"); |
| const KEY_COMPROMISE_DER: &[u8] = &hex!("0a 01 01"); |
| |
| #[test] |
| fn decode() { |
| let unspecified = CrlReason::from_der(UNSPECIFIED_DER).unwrap(); |
| assert_eq!(CrlReason::Unspecified, unspecified); |
| |
| let key_compromise = CrlReason::from_der(KEY_COMPROMISE_DER).unwrap(); |
| assert_eq!(CrlReason::KeyCompromise, key_compromise); |
| } |
| |
| #[test] |
| fn encode() { |
| let mut buf = [0u8; 128]; |
| |
| let mut encoder = SliceWriter::new(&mut buf); |
| CrlReason::Unspecified.encode(&mut encoder).unwrap(); |
| assert_eq!(UNSPECIFIED_DER, encoder.finish().unwrap()); |
| |
| let mut encoder = SliceWriter::new(&mut buf); |
| CrlReason::KeyCompromise.encode(&mut encoder).unwrap(); |
| assert_eq!(KEY_COMPROMISE_DER, encoder.finish().unwrap()); |
| } |
| } |
| |
| /// Custom derive test cases for the `Sequence` macro. |
| #[cfg(feature = "oid")] |
| mod sequence { |
| use core::marker::PhantomData; |
| use der::{ |
| asn1::{AnyRef, ObjectIdentifier, SetOf}, |
| Decode, Encode, Sequence, ValueOrd, |
| }; |
| use hex_literal::hex; |
| |
| pub fn default_false_example() -> bool { |
| false |
| } |
| |
| // Issuing distribution point extension as defined in [RFC 5280 Section 5.2.5] and as identified by the [`PKIX_PE_SUBJECTINFOACCESS`](constant.PKIX_PE_SUBJECTINFOACCESS.html) OID. |
| // |
| // ```text |
| // IssuingDistributionPoint ::= SEQUENCE { |
| // distributionPoint [0] DistributionPointName OPTIONAL, |
| // onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, |
| // onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, |
| // onlySomeReasons [3] ReasonFlags OPTIONAL, |
| // indirectCRL [4] BOOLEAN DEFAULT FALSE, |
| // onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE } |
| // -- at most one of onlyContainsUserCerts, onlyContainsCACerts, |
| // -- and onlyContainsAttributeCerts may be set to TRUE. |
| // ``` |
| // |
| // [RFC 5280 Section 5.2.5]: https://datatracker.ietf.org/doc/html/rfc5280#section-5.2.5 |
| #[derive(Sequence)] |
| pub struct IssuingDistributionPointExample { |
| // Omit distributionPoint and only_some_reasons because corresponding structs are not |
| // available here and are not germane to the example |
| // distributionPoint [0] DistributionPointName OPTIONAL, |
| //#[asn1(context_specific="0", optional="true", tag_mode="IMPLICIT")] |
| //pub distribution_point: Option<DistributionPointName<'a>>, |
| /// onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE, |
| #[asn1( |
| context_specific = "1", |
| default = "default_false_example", |
| tag_mode = "IMPLICIT" |
| )] |
| pub only_contains_user_certs: bool, |
| |
| /// onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE, |
| #[asn1( |
| context_specific = "2", |
| default = "default_false_example", |
| tag_mode = "IMPLICIT" |
| )] |
| pub only_contains_cacerts: bool, |
| |
| // onlySomeReasons [3] ReasonFlags OPTIONAL, |
| //#[asn1(context_specific="3", optional="true", tag_mode="IMPLICIT")] |
| //pub only_some_reasons: Option<ReasonFlags<'a>>, |
| /// indirectCRL [4] BOOLEAN DEFAULT FALSE, |
| #[asn1( |
| context_specific = "4", |
| default = "default_false_example", |
| tag_mode = "IMPLICIT" |
| )] |
| pub indirect_crl: bool, |
| |
| /// onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE |
| #[asn1( |
| context_specific = "5", |
| default = "default_false_example", |
| tag_mode = "IMPLICIT" |
| )] |
| pub only_contains_attribute_certs: bool, |
| |
| /// Test handling of PhantomData. |
| pub phantom: PhantomData<()>, |
| } |
| |
| // 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. |
| // |
| // ```text |
| // Extension ::= SEQUENCE { |
| // extnID OBJECT IDENTIFIER, |
| // critical BOOLEAN DEFAULT FALSE, |
| // extnValue OCTET STRING |
| // -- contains the DER encoding of an ASN.1 value |
| // -- corresponding to the extension type identified |
| // -- by extnID |
| // } |
| // ``` |
| // |
| // [RFC 5280 Section 4.1.2.9]: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.9 |
| #[derive(Clone, Debug, Eq, PartialEq, Sequence)] |
| pub struct ExtensionExample<'a> { |
| /// extnID OBJECT IDENTIFIER, |
| pub extn_id: ObjectIdentifier, |
| |
| /// critical BOOLEAN DEFAULT FALSE, |
| #[asn1(default = "default_false_example")] |
| pub critical: bool, |
| |
| /// extnValue OCTET STRING |
| #[asn1(type = "OCTET STRING")] |
| pub extn_value: &'a [u8], |
| } |
| |
| /// X.509 `AlgorithmIdentifier` |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] |
| pub struct AlgorithmIdentifier<'a> { |
| pub algorithm: ObjectIdentifier, |
| pub parameters: Option<AnyRef<'a>>, |
| } |
| |
| /// X.509 `SubjectPublicKeyInfo` (SPKI) |
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] |
| pub struct SubjectPublicKeyInfo<'a> { |
| pub algorithm: AlgorithmIdentifier<'a>, |
| #[asn1(type = "BIT STRING")] |
| pub subject_public_key: &'a [u8], |
| } |
| |
| /// PKCS#8v2 `OneAsymmetricKey` |
| #[derive(Sequence)] |
| pub struct OneAsymmetricKey<'a> { |
| pub version: u8, |
| pub private_key_algorithm: AlgorithmIdentifier<'a>, |
| #[asn1(type = "OCTET STRING")] |
| pub private_key: &'a [u8], |
| #[asn1(context_specific = "0", extensible = "true", optional = "true")] |
| pub attributes: Option<SetOf<AnyRef<'a>, 1>>, |
| #[asn1( |
| context_specific = "1", |
| extensible = "true", |
| optional = "true", |
| type = "BIT STRING" |
| )] |
| pub public_key: Option<&'a [u8]>, |
| } |
| |
| /// X.509 extension |
| // TODO(tarcieri): tests for code derived with the `default` attribute |
| #[derive(Clone, Debug, Eq, PartialEq, Sequence, ValueOrd)] |
| pub struct Extension<'a> { |
| extn_id: ObjectIdentifier, |
| #[asn1(default = "critical_default")] |
| critical: bool, |
| #[asn1(type = "OCTET STRING")] |
| extn_value: &'a [u8], |
| } |
| |
| /// Default value of the `critical` bit |
| fn critical_default() -> bool { |
| false |
| } |
| |
| const ID_EC_PUBLIC_KEY_OID: ObjectIdentifier = |
| ObjectIdentifier::new_unwrap("1.2.840.10045.2.1"); |
| |
| const PRIME256V1_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7"); |
| |
| const ALGORITHM_IDENTIFIER_DER: &[u8] = |
| &hex!("30 13 06 07 2a 86 48 ce 3d 02 01 06 08 2a 86 48 ce 3d 03 01 07"); |
| |
| #[derive(Sequence)] |
| #[asn1(tag_mode = "IMPLICIT")] |
| pub struct TypeCheckExpandedSequenceFieldAttributeCombinations<'a> { |
| pub simple: bool, |
| #[asn1(type = "BIT STRING")] |
| pub typed: &'a [u8], |
| #[asn1(context_specific = "0")] |
| pub context_specific: bool, |
| #[asn1(optional = "true")] |
| pub optional: Option<bool>, |
| #[asn1(default = "default_false_example")] |
| pub default: bool, |
| #[asn1(type = "BIT STRING", context_specific = "1")] |
| pub typed_context_specific: &'a [u8], |
| #[asn1(context_specific = "2", optional = "true")] |
| pub context_specific_optional: Option<bool>, |
| #[asn1(context_specific = "3", default = "default_false_example")] |
| pub context_specific_default: bool, |
| #[asn1(type = "BIT STRING", context_specific = "4", optional = "true")] |
| pub typed_context_specific_optional: Option<&'a [u8]>, |
| } |
| |
| #[test] |
| fn idp_test() { |
| let idp = IssuingDistributionPointExample::from_der(&hex!("30038101FF")).unwrap(); |
| assert_eq!(idp.only_contains_user_certs, true); |
| assert_eq!(idp.only_contains_cacerts, false); |
| assert_eq!(idp.indirect_crl, false); |
| assert_eq!(idp.only_contains_attribute_certs, false); |
| |
| let idp = IssuingDistributionPointExample::from_der(&hex!("30038201FF")).unwrap(); |
| assert_eq!(idp.only_contains_user_certs, false); |
| assert_eq!(idp.only_contains_cacerts, true); |
| assert_eq!(idp.indirect_crl, false); |
| assert_eq!(idp.only_contains_attribute_certs, false); |
| |
| let idp = IssuingDistributionPointExample::from_der(&hex!("30038401FF")).unwrap(); |
| assert_eq!(idp.only_contains_user_certs, false); |
| assert_eq!(idp.only_contains_cacerts, false); |
| assert_eq!(idp.indirect_crl, true); |
| assert_eq!(idp.only_contains_attribute_certs, false); |
| |
| let idp = IssuingDistributionPointExample::from_der(&hex!("30038501FF")).unwrap(); |
| assert_eq!(idp.only_contains_user_certs, false); |
| assert_eq!(idp.only_contains_cacerts, false); |
| assert_eq!(idp.indirect_crl, false); |
| assert_eq!(idp.only_contains_attribute_certs, true); |
| } |
| |
| // demonstrates default field that is not context specific |
| #[test] |
| fn extension_test() { |
| let ext1 = ExtensionExample::from_der(&hex!( |
| "300F" // 0 15: SEQUENCE { |
| "0603551D13" // 2 3: OBJECT IDENTIFIER basicConstraints (2 5 29 19) |
| "0101FF" // 7 1: BOOLEAN TRUE |
| "0405" // 10 5: OCTET STRING, encapsulates { |
| "3003" // 12 3: SEQUENCE { |
| "0101FF" // 14 1: BOOLEAN TRUE |
| )) |
| .unwrap(); |
| assert_eq!(ext1.critical, true); |
| |
| let ext2 = ExtensionExample::from_der(&hex!( |
| "301F" // 0 31: SEQUENCE { |
| "0603551D23" // 2 3: OBJECT IDENTIFIER authorityKeyIdentifier (2 5 29 35) |
| "0418" // 7 24: OCTET STRING, encapsulates { |
| "3016" // 9 22: SEQUENCE { |
| "8014E47D5FD15C9586082C05AEBE75B665A7D95DA866" // 11 20: [0] E4 7D 5F D1 5C 95 86 08 2C 05 AE BE 75 B6 65 A7 D9 5D A8 66 |
| )) |
| .unwrap(); |
| assert_eq!(ext2.critical, false); |
| } |
| |
| #[test] |
| fn decode() { |
| let algorithm_identifier = AlgorithmIdentifier::from_der(ALGORITHM_IDENTIFIER_DER).unwrap(); |
| |
| assert_eq!(ID_EC_PUBLIC_KEY_OID, algorithm_identifier.algorithm); |
| assert_eq!( |
| PRIME256V1_OID, |
| ObjectIdentifier::try_from(algorithm_identifier.parameters.unwrap()).unwrap() |
| ); |
| } |
| |
| #[test] |
| fn encode() { |
| let parameters_oid = PRIME256V1_OID; |
| |
| let algorithm_identifier = AlgorithmIdentifier { |
| algorithm: ID_EC_PUBLIC_KEY_OID, |
| parameters: Some(AnyRef::from(¶meters_oid)), |
| }; |
| |
| assert_eq!( |
| ALGORITHM_IDENTIFIER_DER, |
| algorithm_identifier.to_der().unwrap() |
| ); |
| } |
| } |