//! 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"))] // TODO: fix needless_question_mark in the derive crate #![allow(clippy::bool_assert_comparison, clippy::needless_question_mark)] /// 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: /// /// /// ```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> { match self { Self::BitString(bs) => Some(*bs), _ => None, } } pub fn time(&self) -> Option { 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>, /// 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>, /// 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>, } /// 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, 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, #[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, #[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() ); } } mod infer_default { //! When another crate might define a PartialEq for another type, the use of //! `default="Default::default"` in the der derivation will not provide enough //! information for `der_derive` crate to figure out. //! //! This provides a reproduction for that case. This is intended to fail when we //! compile tests. //! ``` //! error[E0282]: type annotations needed //! --> der/tests/derive.rs:480:26 //! | //!480 | #[asn1(default = "Default::default")] //! | ^^^^^^^^^^^^^^^^^^ cannot infer type //! //!error[E0283]: type annotations needed //! --> der/tests/derive.rs:478:14 //! | //!478 | #[derive(Sequence)] //! | ^^^^^^^^ cannot infer type //! | //!note: multiple `impl`s satisfying `bool: PartialEq<_>` found //! --> der/tests/derive.rs:472:5 //! | //!472 | impl PartialEq for bool { //! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //! = note: and another `impl` found in the `core` crate: //! - impl PartialEq for bool //! where the constant `host` has type `bool`; //! = note: required for `&bool` to implement `PartialEq<&_>` //! = note: this error originates in the derive macro `Sequence` (in Nightly builds, run with -Z macro-backtrace for more info) //! ``` use der::Sequence; struct BooleanIsh; impl PartialEq for bool { fn eq(&self, _other: &BooleanIsh) -> bool { unimplemented!("This is only here to mess up the compiler's type inference") } } #[derive(Sequence)] struct Foo { #[asn1(default = "Default::default")] pub use_default_default: bool, #[asn1(default = "something_true")] pub use_custom: bool, } fn something_true() -> bool { todo!() } }