diff --git a/der/derive/src/attributes.rs b/der/derive/src/attributes.rs index be0bc4bee..cd92e0cb6 100644 --- a/der/derive/src/attributes.rs +++ b/der/derive/src/attributes.rs @@ -81,10 +81,17 @@ pub(crate) struct FieldAttrs { } impl FieldAttrs { + /// is_optional return true when either an optional or default ASN.1 attribute is associated + /// with a field. Default signifies optionality due to omission of default values in DER encodings. + fn is_optional(&self) -> bool { + self.optional || self.default.is_some() + } + /// Parse attributes from a struct field or enum variant. pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> Self { let mut asn1_type = None; let mut context_specific = None; + let mut default = None; let mut extensible = None; let mut optional = None; @@ -184,7 +191,7 @@ impl FieldAttrs { let context_specific = match self.tag_mode { TagMode::Explicit => { - if self.extensible || self.optional { + if self.extensible || self.is_optional() { quote! { ::der::asn1::ContextSpecific::<#type_params>::decode_explicit( decoder, @@ -210,8 +217,12 @@ impl FieldAttrs { } }; - if self.optional { - quote!(#context_specific.map(|cs| cs.value)) + if self.is_optional() { + if let Some(default) = &self.default { + quote!(#context_specific.map(|cs| cs.value).unwrap_or_else(#default)) + } else { + quote!(#context_specific.map(|cs| cs.value)) + } } else { // TODO(tarcieri): better error handling? quote! { @@ -223,6 +234,11 @@ impl FieldAttrs { })?.value } } + } else if let Some(default) = &self.default { + let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default(); + self.asn1_type.map(|ty| ty.decoder()).unwrap_or_else( + || quote!(decoder.decode::>()?.unwrap_or_else(#default)), + ) } else { self.asn1_type .map(|ty| ty.decoder()) diff --git a/der/derive/src/sequence.rs b/der/derive/src/sequence.rs index 3dee77b51..9393b2f5c 100644 --- a/der/derive/src/sequence.rs +++ b/der/derive/src/sequence.rs @@ -165,7 +165,8 @@ impl SequenceField { let #ident = #decoder.try_into()?; } } - } else if let Some(default) = &self.attrs.default { + } else if self.attrs.context_specific.is_none() && self.attrs.default.is_some() { + let default = &self.attrs.default; quote! { let #ident = decoder.decode::>()?.unwrap_or_else(#default); } diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 18105b958..4f3a4c24f 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -206,6 +206,98 @@ mod sequence { }; 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, + } + + // 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> { @@ -261,6 +353,62 @@ mod sequence { 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"); + #[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 =