diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 03c02bab2..fe854a78e 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -787,6 +787,140 @@ mod decode_encode_value { } } +#[cfg(all(feature = "derive", feature = "oid"))] +mod custom_application { + use const_oid::ObjectIdentifier; + use der::{Decode, DecodeValue, Encode, EncodeValue, FixedTag, Sequence, Tag, TagNumber}; + use hex_literal::hex; + + const TACHO_CERT_DER: &[u8] = &hex!( + "7F 21 81 C8" // Application 33 + + "7F 4E 81 81" // Application 78 + + "5F 29" // Application 41 + "01 00" + "42 08" // Application 2 + "FD 45 43 20 01 FF FF 01" + "5F 4C 07" // Application 76 + "FF 53 4D 52 44 54 0E" + "7F 49 4D" // Application 73 + "06 08 2A 86 48 CE 3D 03 01 07 86 41 04 + 30 E8 EE D8 05 1D FB 8F 05 BF 4E 34 90 B8 A0 1C + 83 21 37 4E 99 41 67 70 64 28 23 A2 C9 E1 21 16 + D9 27 46 45 94 DD CB CC 79 42 B5 F3 EE 1A A3 AB + A2 5C E1 6B 20 92 00 F0 09 70 D9 CF 83 0A 33 4B" + + + "5F 20 08" // Application 32 + "17 47 52 20 02 FF FF 01" + "5F 25 04" // Application 37 + "62 A3 B0 D0" + "5F 24 04" // Application 36 + "6F F6 49 50" + "5F 37 40" // Application 55 + "6D 3E FD 97 + BE 83 EC 65 5F 51 4D 8C 47 60 DB FD 9B A2 D1 5D + 3C 1A 21 93 CE D7 EA F2 A2 0D 89 CC 4A 4F 0C 4B + E5 3F A3 F9 0F 20 B5 74 67 26 DB 19 9E FF DE 0B + D0 B9 2C B9 D1 5A E2 18 08 6C F0 E2" + ); + + /// EU Tachograph G2 certificate + #[derive(DecodeValue, EncodeValue)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct TachographCertificate<'a> { + /// constructed + #[asn1(application = "78")] + pub body: TachographCertificateBody<'a>, + + /// primitive + #[asn1(application = "55", type = "OCTET STRING")] + pub signature: &'a [u8], + } + + impl FixedTag for TachographCertificate<'_> { + const TAG: Tag = Tag::Application { + number: TagNumber(33), // 7F 21 + constructed: true, + }; + } + + /// EU Tachograph G2 certificate body + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct TachographCertificateBody<'a> { + /// primitive + #[asn1(application = "41", type = "OCTET STRING")] + pub profile_identifier: &'a [u8], + + /// primitive + #[asn1(application = "2", type = "OCTET STRING")] + pub authority_reference: &'a [u8], + + /// primitive + #[asn1(application = "76", type = "OCTET STRING")] + pub holder_authorisation: &'a [u8], + + /// constructed + #[asn1(application = "73")] + pub public_key: CertificatePublicKey<'a>, + + /// primitive + #[asn1(application = "32", type = "OCTET STRING")] + pub holder_reference: &'a [u8], + + /// primitive + #[asn1(application = "37", type = "OCTET STRING")] + pub effective_date: &'a [u8], + + /// primitive + #[asn1(application = "36", type = "OCTET STRING")] + pub expiration_date: &'a [u8], + } + + /// EU Tachograph G2 certificate public key + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct CertificatePublicKey<'a> { + pub domain_parameters: ObjectIdentifier, + + #[asn1(context_specific = "6", type = "OCTET STRING")] + pub public_point: &'a [u8], + } + #[test] + fn decode_tacho_application_tags() { + let tacho_cert = TachographCertificate::from_der(TACHO_CERT_DER).unwrap(); + + let sig = tacho_cert.signature; + assert_eq!(&sig[..2], hex!("6D 3E")); + assert_eq!(tacho_cert.body.profile_identifier, &[0x00]); + assert_eq!( + tacho_cert.body.authority_reference, + hex!("FD 45 43 20 01 FF FF 01") + ); + assert_eq!( + tacho_cert.body.holder_authorisation, + hex!("FF 53 4D 52 44 54 0E") + ); + assert_eq!( + tacho_cert.body.public_key.domain_parameters, + ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") + ); + assert_eq!( + &tacho_cert.body.public_key.public_point[..4], + hex!("04 30 E8 EE") + ); + const GREECE: &[u8] = b"GR "; + assert_eq!(&tacho_cert.body.holder_reference[1..4], GREECE); + + // Re-encode + let mut buf = [0u8; 256]; + let encoded = tacho_cert.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, TACHO_CERT_DER); + } +} + /// Custom derive test cases for the `BitString` macro. #[cfg(feature = "std")] mod bitstring { diff --git a/der_derive/src/attributes.rs b/der_derive/src/attributes.rs index 181a7b2f4..77a890731 100644 --- a/der_derive/src/attributes.rs +++ b/der_derive/src/attributes.rs @@ -94,8 +94,11 @@ pub(crate) struct FieldAttrs { /// Is the inner type constructed? pub constructed: bool, - /// Value of the `#[asn1(context_specific = "...")] attribute if provided. - pub context_specific: Option, + /// Class and number from the following attributes: + /// - `#[asn1(application = "...")]` + /// - `#[asn1(context_specific = "...")]` + /// - `#[asn1(private = "...")]` + pub class_num: Option, /// Indicates name of function that supplies the default value, which will be used in cases /// where encoding is omitted per DER and to omit the encoding per DER @@ -130,7 +133,7 @@ impl FieldAttrs { pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> syn::Result { let mut asn1_type = None; let mut constructed = None; - let mut context_specific = None; + let mut class_num = None; let mut default = None; let mut should_deref = None; let mut extensible = None; @@ -143,11 +146,34 @@ impl FieldAttrs { for attr in parsed_attrs { // `context_specific = "..."` attribute if let Some(tag_number) = attr.parse_value("context_specific")? { - if context_specific.is_some() { - abort!(attr.name, "duplicate ASN.1 `context_specific` attribute"); + if class_num.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); } - context_specific = Some(tag_number); + class_num = Some(ClassNum::ContextSpecific(tag_number)); + // `private = "..."` attribute + } else if let Some(tag_number) = attr.parse_value("private")? { + if class_num.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); + } + + class_num = Some(ClassNum::Private(tag_number)); + // `application = "..."` attribute + } else if let Some(tag_number) = attr.parse_value("application")? { + if class_num.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); + } + + class_num = Some(ClassNum::Application(tag_number)); // `default` attribute } else if attr.parse_value::("default")?.is_some() { if default.is_some() { @@ -205,7 +231,7 @@ impl FieldAttrs { abort!( attr.name, "unknown field-level `asn1` attribute \ - (valid options are `constructed`, `context_specific`, `default`, `deref`, `extensible`, `optional`, `tag_mode`, `type`)", + (valid options are `application`, `constructed`, `context_specific`, `default`, `deref`, `extensible`, `optional`, `private`, `tag_mode`, `type`)", ); } } @@ -213,7 +239,7 @@ impl FieldAttrs { Ok(Self { asn1_type, constructed: constructed.unwrap_or_default(), - context_specific, + class_num, default, should_deref: should_deref.unwrap_or_default(), extensible: extensible.unwrap_or_default(), @@ -224,8 +250,16 @@ impl FieldAttrs { /// Get the expected [`Tag`] for this field. pub fn tag(&self) -> syn::Result> { - match self.context_specific { - Some(tag_number) => Ok(Some(Tag::ContextSpecific { + match self.class_num { + Some(ClassNum::Application(tag_number)) => Ok(Some(Tag::Application { + constructed: self.constructed, + number: tag_number, + })), + Some(ClassNum::ContextSpecific(tag_number)) => Ok(Some(Tag::ContextSpecific { + constructed: self.constructed, + number: tag_number, + })), + Some(ClassNum::Private(tag_number)) => Ok(Some(Tag::Private { constructed: self.constructed, number: tag_number, })), @@ -242,56 +276,8 @@ impl FieldAttrs { /// Get a `der::Decoder` object which respects these field attributes. pub fn decoder(&self) -> TokenStream { - if let Some(tag_number) = self.context_specific { - let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default(); - let tag_number = tag_number.to_tokens(); - - let context_specific = match self.tag_mode { - TagMode::Explicit => { - if self.extensible || self.is_optional() { - quote! { - ::der::asn1::ContextSpecific::<#type_params>::decode_explicit( - reader, - #tag_number - )? - } - } else { - quote! { - match ::der::asn1::ContextSpecific::<#type_params>::decode(reader)? { - field if field.tag_number == #tag_number => Some(field), - _ => None - } - } - } - } - TagMode::Implicit => { - quote! { - ::der::asn1::ContextSpecific::<#type_params>::decode_implicit( - reader, - #tag_number - )? - } - } - }; - - 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? - let constructed = self.constructed; - quote! { - #context_specific.ok_or_else(|| { - der::Tag::ContextSpecific { - number: #tag_number, - constructed: #constructed - }.value_error() - })?.value - } - } + if let Some(class_num) = &self.class_num { + self.custom_class_decoder(class_num) } 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(|| { @@ -310,14 +296,77 @@ impl FieldAttrs { } } + pub fn custom_class_decoder(&self, class_num: &ClassNum) -> TokenStream { + let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or(quote!(_)); + let ClassTokens { + tag_type, + tag_number, + class_type, + .. + } = class_num.to_tokens(type_params, self.tag_mode); + + let context_specific = match self.tag_mode { + TagMode::Explicit => { + if self.extensible || self.is_optional() { + quote! { + #class_type::decode_explicit( + reader, + #tag_number + )? + } + } else { + quote! { + match #class_type::decode(reader)? { + field if field.tag_number == #tag_number => Some(field), + _ => None + } + } + } + } + TagMode::Implicit => { + quote! { + #class_type::decode_implicit( + reader, + #tag_number + )? + } + } + }; + + 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? + let constructed = self.constructed; + quote! { + #context_specific.ok_or_else(|| { + #tag_type { + number: #tag_number, + constructed: #constructed + }.value_error() + })?.value + } + } + } + /// Get tokens to encode the binding using `::der::EncodeValue`. pub fn value_encode(&self, binding: &TokenStream) -> TokenStream { - match self.context_specific { - Some(tag_number) => { - let tag_number = tag_number.to_tokens(); + match &self.class_num { + Some(class_num) => { let tag_mode = self.tag_mode.to_tokens(); + let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or(quote!(_)); + let ClassTokens { + ref_type, + tag_number, + .. + } = class_num.to_tokens(type_params, self.tag_mode); + quote! { - ::der::asn1::ContextSpecificRef { + #ref_type { tag_number: #tag_number, tag_mode: #tag_mode, value: #binding, @@ -407,3 +456,70 @@ impl AttrNameValue { }) } } + +/// Class and tag number +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum ClassNum { + ContextSpecific(TagNumber), + Private(TagNumber), + Application(TagNumber), +} + +pub(crate) struct ClassTokens { + pub tag_type: TokenStream, + pub tag_number: TokenStream, + + pub class_type: TokenStream, + pub ref_type: TokenStream, +} + +impl ClassNum { + pub fn to_tokens(&self, type_params: TokenStream, tag_mode: TagMode) -> ClassTokens { + // Future-proof for potential ContextSpecificExplicit / ContextSpecificImplicit split + match (tag_mode, self) { + (TagMode::Explicit, Self::ContextSpecific(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::ContextSpecific), + class_type: quote!(::der::asn1::ContextSpecific::<#type_params>), + ref_type: quote!(::der::asn1::ContextSpecificRef), + tag_number: tag_number.to_tokens(), + }, + (TagMode::Implicit, Self::ContextSpecific(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::ContextSpecific), + class_type: quote!(::der::asn1::ContextSpecific::<#type_params>), + ref_type: quote!(::der::asn1::ContextSpecificRef), + tag_number: tag_number.to_tokens(), + }, + (TagMode::Explicit, Self::Private(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::Private), + class_type: quote!(::der::asn1::Private::<#type_params>), + ref_type: quote!(::der::asn1::PrivateRef), + tag_number: tag_number.to_tokens(), + }, + (TagMode::Implicit, Self::Private(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::Private), + class_type: quote!(::der::asn1::Private::<#type_params>), + ref_type: quote!(::der::asn1::PrivateRef), + tag_number: tag_number.to_tokens(), + }, + (TagMode::Explicit, Self::Application(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::Application), + class_type: quote!(::der::asn1::Application::<#type_params>), + ref_type: quote!(::der::asn1::ApplicationRef), + tag_number: tag_number.to_tokens(), + }, + (TagMode::Implicit, Self::Application(tag_number)) => ClassTokens { + tag_type: quote!(::der::Tag::Application), + class_type: quote!(::der::asn1::Application::<#type_params>), + ref_type: quote!(::der::asn1::ApplicationRef), + tag_number: tag_number.to_tokens(), + }, + } + } + pub fn tag_number(&self) -> TagNumber { + match self { + ClassNum::ContextSpecific(tag_number) => *tag_number, + ClassNum::Private(tag_number) => *tag_number, + ClassNum::Application(tag_number) => *tag_number, + } + } +} diff --git a/der_derive/src/choice.rs b/der_derive/src/choice.rs index 6ca474802..9700111fc 100644 --- a/der_derive/src/choice.rs +++ b/der_derive/src/choice.rs @@ -149,7 +149,7 @@ impl DeriveChoice { #[allow(clippy::unwrap_used)] mod tests { use super::DeriveChoice; - use crate::{Asn1Type, Tag, TagMode}; + use crate::{Asn1Type, Tag, TagMode, attributes::ClassNum}; use syn::parse_quote; /// Based on `Time` as defined in RFC 5280: @@ -180,7 +180,7 @@ mod tests { let utc_time = &ir.variants[0]; assert_eq!(utc_time.ident, "UtcTime"); assert_eq!(utc_time.attrs.asn1_type, Some(Asn1Type::UtcTime)); - assert_eq!(utc_time.attrs.context_specific, None); + assert_eq!(utc_time.attrs.class_num, None); assert_eq!(utc_time.attrs.tag_mode, TagMode::Explicit); assert_eq!(utc_time.tag, Tag::Universal(Asn1Type::UtcTime)); @@ -190,7 +190,7 @@ mod tests { general_time.attrs.asn1_type, Some(Asn1Type::GeneralizedTime) ); - assert_eq!(general_time.attrs.context_specific, None); + assert_eq!(general_time.attrs.class_num, None); assert_eq!(general_time.attrs.tag_mode, TagMode::Explicit); assert_eq!(general_time.tag, Tag::Universal(Asn1Type::GeneralizedTime)); } @@ -224,8 +224,8 @@ mod tests { assert_eq!(bit_string.ident, "BitString"); assert_eq!(bit_string.attrs.asn1_type, Some(Asn1Type::BitString)); assert_eq!( - bit_string.attrs.context_specific, - Some("0".parse().unwrap()) + bit_string.attrs.class_num, + Some(ClassNum::ContextSpecific("0".parse().unwrap())) ); assert_eq!(bit_string.attrs.tag_mode, TagMode::Implicit); assert_eq!( @@ -239,7 +239,10 @@ mod tests { let time = &ir.variants[1]; assert_eq!(time.ident, "Time"); assert_eq!(time.attrs.asn1_type, Some(Asn1Type::GeneralizedTime)); - assert_eq!(time.attrs.context_specific, Some("1".parse().unwrap())); + assert_eq!( + time.attrs.class_num, + Some(ClassNum::ContextSpecific("1".parse().unwrap())) + ); assert_eq!(time.attrs.tag_mode, TagMode::Implicit); assert_eq!( time.tag, @@ -253,8 +256,8 @@ mod tests { assert_eq!(utf8_string.ident, "Utf8String"); assert_eq!(utf8_string.attrs.asn1_type, Some(Asn1Type::Utf8String)); assert_eq!( - utf8_string.attrs.context_specific, - Some("2".parse().unwrap()) + utf8_string.attrs.class_num, + Some(ClassNum::ContextSpecific("2".parse().unwrap())) ); assert_eq!(utf8_string.attrs.tag_mode, TagMode::Implicit); assert_eq!( diff --git a/der_derive/src/choice/variant.rs b/der_derive/src/choice/variant.rs index 458bf4493..bda4c39d3 100644 --- a/der_derive/src/choice/variant.rs +++ b/der_derive/src/choice/variant.rs @@ -1,6 +1,6 @@ //! Choice variant IR and lowerings -use crate::{FieldAttrs, Tag, TypeAttrs}; +use crate::{FieldAttrs, Tag, TypeAttrs, attributes::ClassTokens}; use proc_macro2::TokenStream; use quote::quote; use syn::{Fields, Ident, Path, Type, Variant}; @@ -123,13 +123,23 @@ impl ChoiceVariant { pub(super) fn to_value_len_tokens(&self) -> TokenStream { let ident = &self.ident; - match self.attrs.context_specific { - Some(tag_number) => { - let tag_number = tag_number.to_tokens(); + match &self.attrs.class_num { + Some(class_num) => { + let type_params = self + .attrs + .asn1_type + .map(|ty| ty.type_path()) + .unwrap_or(quote!(_)); + let ClassTokens { + ref_type, + tag_number, + .. + } = class_num.to_tokens(type_params, self.attrs.tag_mode); + let tag_mode = self.attrs.tag_mode.to_tokens(); quote! { - Self::#ident(variant) => ::der::asn1::ContextSpecificRef { + Self::#ident(variant) => #ref_type { tag_number: #tag_number, tag_mode: #tag_mode, value: variant, @@ -154,7 +164,10 @@ impl ChoiceVariant { #[cfg(test)] mod tests { use super::ChoiceVariant; - use crate::{Asn1Type, FieldAttrs, Tag, TagMode, TagNumber, choice::variant::TagOrPath}; + use crate::{ + Asn1Type, FieldAttrs, Tag, TagMode, TagNumber, attributes::ClassNum, + choice::variant::TagOrPath, + }; use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -254,7 +267,7 @@ mod tests { let ident = Ident::new("ExplicitVariant", Span::call_site()); let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class_num: Some(ClassNum::ContextSpecific(TagNumber(tag_number))), ..Default::default() }; assert_eq!(attrs.tag_mode, TagMode::Explicit); @@ -274,12 +287,12 @@ mod tests { constructed: #constructed, number: #tag_number, } => Ok(Self::ExplicitVariant( - match ::der::asn1::ContextSpecific::<>::decode(reader)? { + match ::der::asn1::ContextSpecific::<_>::decode(reader)? { field if field.tag_number == #tag_number => Some(field), _ => None } .ok_or_else(|| { - der::Tag::ContextSpecific { + ::der::Tag::ContextSpecific { number: #tag_number, constructed: #constructed } @@ -339,7 +352,7 @@ mod tests { let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class_num: Some(ClassNum::ContextSpecific(TagNumber(tag_number))), tag_mode: TagMode::Implicit, ..Default::default() }; @@ -359,12 +372,12 @@ mod tests { constructed: #constructed, number: #tag_number, } => Ok(Self::ImplicitVariant( - ::der::asn1::ContextSpecific::<>::decode_implicit( + ::der::asn1::ContextSpecific::<_>::decode_implicit( reader, #tag_number )? .ok_or_else(|| { - der::Tag::ContextSpecific { + ::der::Tag::ContextSpecific { number: #tag_number, constructed: #constructed } diff --git a/der_derive/src/sequence.rs b/der_derive/src/sequence.rs index 210998873..da11efff9 100644 --- a/der_derive/src/sequence.rs +++ b/der_derive/src/sequence.rs @@ -185,7 +185,7 @@ impl DeriveSequence { #[allow(clippy::bool_assert_comparison)] mod tests { use super::DeriveSequence; - use crate::{Asn1Type, TagMode}; + use crate::{Asn1Type, TagMode, attributes::ClassNum}; use syn::parse_quote; /// X.509 SPKI `AlgorithmIdentifier`. @@ -210,13 +210,13 @@ mod tests { let algorithm_field = &ir.fields[0]; assert_eq!(algorithm_field.ident, "algorithm"); assert_eq!(algorithm_field.attrs.asn1_type, None); - assert_eq!(algorithm_field.attrs.context_specific, None); + assert_eq!(algorithm_field.attrs.class_num, None); assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit); let parameters_field = &ir.fields[1]; assert_eq!(parameters_field.ident, "parameters"); assert_eq!(parameters_field.attrs.asn1_type, None); - assert_eq!(parameters_field.attrs.context_specific, None); + assert_eq!(parameters_field.attrs.class_num, None); assert_eq!(parameters_field.attrs.tag_mode, TagMode::Explicit); } @@ -244,7 +244,7 @@ mod tests { let algorithm_field = &ir.fields[0]; assert_eq!(algorithm_field.ident, "algorithm"); assert_eq!(algorithm_field.attrs.asn1_type, None); - assert_eq!(algorithm_field.attrs.context_specific, None); + assert_eq!(algorithm_field.attrs.class_num, None); assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit); let subject_public_key_field = &ir.fields[1]; @@ -253,7 +253,7 @@ mod tests { subject_public_key_field.attrs.asn1_type, Some(Asn1Type::BitString) ); - assert_eq!(subject_public_key_field.attrs.context_specific, None); + assert_eq!(subject_public_key_field.attrs.class_num, None); assert_eq!(subject_public_key_field.attrs.tag_mode, TagMode::Explicit); } @@ -312,7 +312,7 @@ mod tests { let version_field = &ir.fields[0]; assert_eq!(version_field.ident, "version"); assert_eq!(version_field.attrs.asn1_type, None); - assert_eq!(version_field.attrs.context_specific, None); + assert_eq!(version_field.attrs.class_num, None); assert_eq!(version_field.attrs.extensible, false); assert_eq!(version_field.attrs.optional, false); assert_eq!(version_field.attrs.tag_mode, TagMode::Explicit); @@ -320,7 +320,7 @@ mod tests { let algorithm_field = &ir.fields[1]; assert_eq!(algorithm_field.ident, "private_key_algorithm"); assert_eq!(algorithm_field.attrs.asn1_type, None); - assert_eq!(algorithm_field.attrs.context_specific, None); + assert_eq!(algorithm_field.attrs.class_num, None); assert_eq!(algorithm_field.attrs.extensible, false); assert_eq!(algorithm_field.attrs.optional, false); assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit); @@ -331,7 +331,7 @@ mod tests { private_key_field.attrs.asn1_type, Some(Asn1Type::OctetString) ); - assert_eq!(private_key_field.attrs.context_specific, None); + assert_eq!(private_key_field.attrs.class_num, None); assert_eq!(private_key_field.attrs.extensible, false); assert_eq!(private_key_field.attrs.optional, false); assert_eq!(private_key_field.attrs.tag_mode, TagMode::Explicit); @@ -340,8 +340,8 @@ mod tests { assert_eq!(attributes_field.ident, "attributes"); assert_eq!(attributes_field.attrs.asn1_type, None); assert_eq!( - attributes_field.attrs.context_specific, - Some("0".parse().unwrap()) + attributes_field.attrs.class_num, + Some(ClassNum::ContextSpecific("0".parse().unwrap())) ); assert_eq!(attributes_field.attrs.extensible, true); assert_eq!(attributes_field.attrs.optional, true); @@ -351,8 +351,8 @@ mod tests { assert_eq!(public_key_field.ident, "public_key"); assert_eq!(public_key_field.attrs.asn1_type, Some(Asn1Type::BitString)); assert_eq!( - public_key_field.attrs.context_specific, - Some("1".parse().unwrap()) + public_key_field.attrs.class_num, + Some(ClassNum::ContextSpecific("1".parse().unwrap())) ); assert_eq!(public_key_field.attrs.extensible, true); assert_eq!(public_key_field.attrs.optional, true); @@ -373,6 +373,13 @@ mod tests { #[asn1(context_specific = "2", type = "UTF8String")] utf8_string: String, + + #[asn1(application = "3", type = "OCTET STRING")] + app_octet_string: &[u8], + + #[asn1(private = "4", type = "IA5String")] + private_ia5_string: String, + } }; @@ -382,30 +389,57 @@ mod tests { ir.generics.lifetimes().next().unwrap().lifetime.to_string(), "'a" ); - assert_eq!(ir.fields.len(), 3); + assert_eq!(ir.fields.len(), 5); let bit_string = &ir.fields[0]; assert_eq!(bit_string.ident, "bit_string"); assert_eq!(bit_string.attrs.asn1_type, Some(Asn1Type::BitString)); assert_eq!( - bit_string.attrs.context_specific, - Some("0".parse().unwrap()) + bit_string.attrs.class_num, + Some(ClassNum::ContextSpecific("0".parse().unwrap())) ); assert_eq!(bit_string.attrs.tag_mode, TagMode::Implicit); let time = &ir.fields[1]; assert_eq!(time.ident, "time"); assert_eq!(time.attrs.asn1_type, Some(Asn1Type::GeneralizedTime)); - assert_eq!(time.attrs.context_specific, Some("1".parse().unwrap())); + assert_eq!( + time.attrs.class_num, + Some(ClassNum::ContextSpecific("1".parse().unwrap())) + ); assert_eq!(time.attrs.tag_mode, TagMode::Implicit); let utf8_string = &ir.fields[2]; assert_eq!(utf8_string.ident, "utf8_string"); assert_eq!(utf8_string.attrs.asn1_type, Some(Asn1Type::Utf8String)); assert_eq!( - utf8_string.attrs.context_specific, - Some("2".parse().unwrap()) + utf8_string.attrs.class_num, + Some(ClassNum::ContextSpecific("2".parse().unwrap())) ); assert_eq!(utf8_string.attrs.tag_mode, TagMode::Implicit); + + let app_octet_string = &ir.fields[3]; + assert_eq!(app_octet_string.ident, "app_octet_string"); + assert_eq!( + app_octet_string.attrs.asn1_type, + Some(Asn1Type::OctetString) + ); + assert_eq!( + app_octet_string.attrs.class_num, + Some(ClassNum::Application("3".parse().unwrap())) + ); + assert_eq!(app_octet_string.attrs.tag_mode, TagMode::Implicit); + + let private_ia5_string = &ir.fields[4]; + assert_eq!(private_ia5_string.ident, "private_ia5_string"); + assert_eq!( + private_ia5_string.attrs.asn1_type, + Some(Asn1Type::Ia5String) + ); + assert_eq!( + private_ia5_string.attrs.class_num, + Some(ClassNum::Private("4".parse().unwrap())) + ); + assert_eq!(private_ia5_string.attrs.tag_mode, TagMode::Implicit); } } diff --git a/der_derive/src/sequence/field.rs b/der_derive/src/sequence/field.rs index 6d3928660..58cd3de83 100644 --- a/der_derive/src/sequence/field.rs +++ b/der_derive/src/sequence/field.rs @@ -1,6 +1,9 @@ //! Sequence field IR and lowerings -use crate::{Asn1Type, FieldAttrs, TagMode, TagNumber, TypeAttrs}; +use crate::{ + Asn1Type, FieldAttrs, TagMode, TypeAttrs, + attributes::{ClassNum, ClassTokens}, +}; use proc_macro2::TokenStream; use quote::quote; use syn::{Field, Ident, Path, Type}; @@ -66,7 +69,7 @@ impl SequenceField { ); // TODO(tarcieri): support for context-specific fields with defaults? - if self.attrs.context_specific.is_none() { + if self.attrs.class_num.is_none() { lowerer.apply_default(default, &self.field_type); } } @@ -92,8 +95,8 @@ impl SequenceField { lowerer.apply_asn1_type(ty, attrs.optional); } - if let Some(tag_number) = &attrs.context_specific { - lowerer.apply_context_specific(tag_number, &attrs.tag_mode, attrs.optional); + if let Some(class_num) = &attrs.class_num { + lowerer.apply_class_and_number(class_num, &attrs.tag_mode, attrs.optional); } if let Some(default) = &attrs.default { @@ -220,21 +223,18 @@ impl LowerFieldEncoder { }; } - /// Make this field context-specific. - fn apply_context_specific( - &mut self, - tag_number: &TagNumber, - tag_mode: &TagMode, - optional: bool, - ) { + /// Make this field application, context-specific, or private. + fn apply_class_and_number(&mut self, class_num: &ClassNum, tag_mode: &TagMode, optional: bool) { let encoder = &self.encoder; - let number_tokens = tag_number.to_tokens(); + let type_params = quote!(_); + let ClassTokens { ref_type, .. } = class_num.to_tokens(type_params, *tag_mode); + let number_tokens = class_num.tag_number().to_tokens(); let mode_tokens = tag_mode.to_tokens(); if optional { self.encoder = quote! { #encoder.as_ref().map(|field| { - ::der::asn1::ContextSpecificRef { + #ref_type { tag_number: #number_tokens, tag_mode: #mode_tokens, value: field, @@ -243,7 +243,7 @@ impl LowerFieldEncoder { }; } else { self.encoder = quote! { - ::der::asn1::ContextSpecificRef { + #ref_type { tag_number: #number_tokens, tag_mode: #mode_tokens, value: &#encoder, @@ -256,7 +256,7 @@ impl LowerFieldEncoder { #[cfg(test)] mod tests { use super::SequenceField; - use crate::{FieldAttrs, TagMode, TagNumber}; + use crate::{FieldAttrs, TagMode, TagNumber, attributes::ClassNum}; use proc_macro2::Span; use quote::quote; use syn::{Ident, Path, PathSegment, Type, TypePath, punctuated::Punctuated}; @@ -285,7 +285,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: None, + class_num: None, default: None, extensible: false, optional: false, @@ -326,7 +326,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: Some(TagNumber(0)), + class_num: Some(ClassNum::ContextSpecific(TagNumber(0))), default: None, extensible: false, optional: false, @@ -346,12 +346,12 @@ mod tests { assert_eq!( field.to_decode_tokens().to_string(), quote! { - let implicit_field = ::der::asn1::ContextSpecific::<>::decode_implicit( + let implicit_field = ::der::asn1::ContextSpecific::<_>::decode_implicit( reader, ::der::TagNumber(0u32) )? .ok_or_else(|| { - der::Tag::ContextSpecific { + ::der::Tag::ContextSpecific { number: ::der::TagNumber(0u32), constructed: false } diff --git a/der_derive/src/tag.rs b/der_derive/src/tag.rs index 17b1bd66a..3aa9b7fc8 100644 --- a/der_derive/src/tag.rs +++ b/der_derive/src/tag.rs @@ -15,7 +15,16 @@ pub(crate) enum Tag { /// Universal tags with an associated [`Asn1Type`]. Universal(Asn1Type), - /// Context-specific tags with an associated [`TagNumber`]. + /// `APPLICATION` tags with an associated [`TagNumber`]. + Application { + /// Is the inner ASN.1 type constructed? + constructed: bool, + + /// Context-specific tag number + number: TagNumber, + }, + + /// `CONTEXT-SPECIFIC` tags with an associated [`TagNumber`]. ContextSpecific { /// Is the inner ASN.1 type constructed? constructed: bool, @@ -23,6 +32,15 @@ pub(crate) enum Tag { /// Context-specific tag number number: TagNumber, }, + + /// `PRIVATE` tags with an associated [`TagNumber`]. + Private { + /// Is the inner ASN.1 type constructed? + constructed: bool, + + /// Context-specific tag number + number: TagNumber, + }, } impl Tag { @@ -30,16 +48,23 @@ impl Tag { pub fn to_tokens(self) -> TokenStream { match self { Tag::Universal(ty) => ty.tag(), - Tag::ContextSpecific { + Tag::Application { constructed, number, } => { - let constructed = if constructed { - quote!(true) - } else { - quote!(false) - }; + let number = number.to_tokens(); + quote! { + ::der::Tag::Application { + constructed: #constructed, + number: #number, + } + } + } + Tag::ContextSpecific { + constructed, + number, + } => { let number = number.to_tokens(); quote! { @@ -49,6 +74,19 @@ impl Tag { } } } + Tag::Private { + constructed, + number, + } => { + let number = number.to_tokens(); + + quote! { + ::der::Tag::Private { + constructed: #constructed, + number: #number, + } + } + } } } }