diff --git a/der/src/asn1.rs b/der/src/asn1.rs index b04b1b58f..c3fa5ba23 100644 --- a/der/src/asn1.rs +++ b/der/src/asn1.rs @@ -5,12 +5,15 @@ mod internal_macros; mod any; +mod any_custom_class; +mod application; mod bit_string; #[cfg(feature = "alloc")] mod bmp_string; mod boolean; mod choice; mod context_specific; +mod custom_class; mod generalized_time; mod ia5_string; mod integer; @@ -20,6 +23,7 @@ mod octet_string; mod oid; mod optional; mod printable_string; +mod private; #[cfg(feature = "real")] mod real; mod sequence; @@ -32,15 +36,23 @@ mod videotex_string; pub use self::{ any::AnyRef, + any_custom_class::{AnyCustomClassExplicit, AnyCustomClassImplicit}, + application::{ + ApplicationExplicit, ApplicationExplicitRef, ApplicationImplicit, ApplicationImplicitRef, + }, bit_string::{BitStringIter, BitStringRef}, choice::Choice, - context_specific::{ContextSpecific, ContextSpecificRef}, + context_specific::{ + ContextSpecificExplicit, ContextSpecificExplicitRef, ContextSpecificImplicit, + ContextSpecificImplicitRef, + }, generalized_time::GeneralizedTime, ia5_string::Ia5StringRef, integer::{int::IntRef, uint::UintRef}, null::Null, octet_string::OctetStringRef, printable_string::PrintableStringRef, + private::{PrivateExplicit, PrivateExplicitRef, PrivateImplicit, PrivateImplicitRef}, sequence::{Sequence, SequenceRef}, sequence_of::{SequenceOf, SequenceOfIter}, set_of::{SetOf, SetOfIter}, diff --git a/der/src/asn1/any_custom_class.rs b/der/src/asn1/any_custom_class.rs new file mode 100644 index 000000000..f7faee08d --- /dev/null +++ b/der/src/asn1/any_custom_class.rs @@ -0,0 +1,265 @@ +//! Less strict Context-specific, Application or Private field. + +use crate::{ + Class, Decode, DecodeValue, Encode, EncodeValue, Error, Header, Length, Reader, Tag, TagNumber, + Tagged, Writer, +}; + +use super::AnyRef; + +/// `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. +/// +/// `EXPLICIT` encoding - always constructed. +pub struct AnyCustomClassExplicit { + /// Value of the field. Should implement [`Decode`] + pub value: T, + + /// Class of the field. + /// + /// Supported classes: [`Class::Application`], [`Class::ContextSpecific`], [`Class::Private`] + pub class: Class, + + /// Tag number without the leading class bits `0b11000000` + /// and without constructed `0b00100000` flag. + pub tag_number: TagNumber, +} + +/// `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. +/// +/// `IMPLICIT` encoding - constructed bit should match inner value's tag. +pub struct AnyCustomClassImplicit { + /// Value of the field. Should implement [`DecodeValue`] + pub value: T, + + /// Class of the field. + /// + /// Supported classes: [`Class::Application`], [`Class::ContextSpecific`], [`Class::Private`] + pub class: Class, + + /// Tag number without the leading class bits `0b11000000` + /// and without constructed `0b00100000` flag. + pub tag_number: TagNumber, + + /// Constructed flag. Should match value's tag constructed flag. + pub constructed: bool, +} + +impl<'a, T> AnyCustomClassExplicit +where + T: Decode<'a>, +{ + /// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. + /// + /// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag. + /// + /// Skips `CONTEXT-SPECIFIC` fields, lower than [`TagNumber`]. + pub fn decode_skipping>( + class: Class, + tag_number: TagNumber, + reader: &mut R, + ) -> Result, T::Error> { + peek_decode_optional(reader, class, tag_number, |reader| { + Self::decode_checked(class, tag_number, reader) + }) + } + + /// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. + /// + /// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag. + pub fn decode_checked>( + class: Class, + tag_number: TagNumber, + reader: &mut R, + ) -> Result { + let any_explicit = Self::decode(reader)?; + + if any_explicit.class == class && any_explicit.tag_number == tag_number { + Ok(any_explicit) + } else { + let expected = expected_tag_constructed(class, tag_number, true); + Err(any_explicit.tag().unexpected_error(Some(expected)).into()) + } + } +} + +impl<'a, T> AnyCustomClassImplicit +where + T: Tagged + DecodeValue<'a> + 'a, +{ + /// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. + /// + /// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag. + /// + /// Skips `CONTEXT-SPECIFIC` fields, lower than [`TagNumber`]. + pub fn decode_skipping>( + class: Class, + tag_number: TagNumber, + reader: &mut R, + ) -> Result, T::Error> { + peek_decode_optional::<_, _, T::Error, _>(reader, class, tag_number, |reader| { + Self::decode_checked(class, tag_number, reader) + }) + } + + /// Decodes `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tagged value. + /// + /// Returns Ok only if both [`Class`] and [`TagNumber`] match the decoded tag. + pub fn decode_checked>( + class: Class, + tag_number: TagNumber, + reader: &mut R, + ) -> Result { + let any_implicit = Self::decode(reader)?; + if any_implicit.class == class && any_implicit.tag_number == tag_number { + Ok(any_implicit) + } else { + let expected = expected_tag_constructed(class, tag_number, true); + Err(any_implicit.tag().unexpected_error(Some(expected)).into()) + } + } +} + +impl<'a, T> Decode<'a> for AnyCustomClassExplicit +where + T: Decode<'a>, +{ + type Error = T::Error; + + fn decode>(reader: &mut R) -> Result { + let header = Header::decode(reader)?; + + if !header.tag.is_constructed() { + return Err(header.tag.non_canonical_error().into()); + } + + Ok(Self { + value: reader.read_nested(header.length, |reader| T::decode(reader))?, + class: header.tag.class(), + tag_number: header.tag.number(), + }) + } +} + +impl<'a, T> Decode<'a> for AnyCustomClassImplicit +where + T: Tagged + DecodeValue<'a> + 'a, +{ + type Error = T::Error; + + fn decode>(reader: &mut R) -> Result { + let header = Header::decode(reader)?; + + let value = reader.read_nested(header.length, |reader| T::decode_value(reader, header))?; + + if header.tag.is_constructed() != value.tag().is_constructed() { + return Err(header.tag.non_canonical_error().into()); + } + Ok(Self { + value, + class: header.tag.class(), + tag_number: header.tag.number(), + constructed: header.tag.is_constructed(), + }) + } +} + +impl EncodeValue for AnyCustomClassExplicit +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.value.encoded_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.value.encode(writer) + } +} + +impl EncodeValue for AnyCustomClassImplicit +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.value.value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.value.encode_value(writer) + } +} + +impl Tagged for AnyCustomClassExplicit { + fn tag(&self) -> Tag { + expected_tag_constructed(self.class, self.tag_number, true) + } +} + +impl Tagged for AnyCustomClassImplicit { + fn tag(&self) -> Tag { + expected_tag_constructed(self.class, self.tag_number, self.constructed) + } +} + +/// Attempt to decode a custom class-tagged field with the given +/// helper callback. +fn peek_decode_optional<'a, F, R: Reader<'a>, E, T>( + reader: &mut R, + expected_class: Class, + expected_number: TagNumber, + f: F, +) -> Result, E> +where + F: FnOnce(&mut R) -> Result, + E: From, +{ + while let Some(tag) = Tag::peek_optional(reader)? { + if is_unskippable_tag(tag, expected_class, expected_number) { + break; + } else if tag.number() == expected_number { + return Some(f(reader)).transpose(); + } else { + AnyRef::decode(reader)?; + } + } + + Ok(None) +} + +/// Returns if this tag is of different class than eg. CONTEXT-SPECIFIC +/// or tag number is higher than expected +fn is_unskippable_tag(tag: Tag, expected_class: Class, expected_number: TagNumber) -> bool { + if expected_class != tag.class() { + return true; + } + match expected_class { + Class::Application => tag.number() > expected_number, + Class::ContextSpecific => tag.number() > expected_number, + Class::Private => tag.number() != expected_number, + + // probably unreachable + Class::Universal => tag.number() != expected_number, + } +} + +pub(crate) const fn expected_tag_constructed( + class: Class, + number: TagNumber, + constructed: bool, +) -> Tag { + match class { + Class::Application => Tag::Application { + constructed, + number, + }, + Class::ContextSpecific => Tag::ContextSpecific { + constructed, + number, + }, + Class::Private => Tag::Private { + constructed, + number, + }, + Class::Universal => Tag::Null, + } +} diff --git a/der/src/asn1/application.rs b/der/src/asn1/application.rs new file mode 100644 index 000000000..452fc55d4 --- /dev/null +++ b/der/src/asn1/application.rs @@ -0,0 +1,48 @@ +//! Application field. + +use crate::tag::CLASS_APPLICATION; + +use super::custom_class::{ + CustomClassExplicit, CustomClassExplicitRef, CustomClassImplicit, CustomClassImplicitRef, +}; + +/// Application class, EXPLICIT +pub type ApplicationExplicit = CustomClassExplicit; + +/// Application class, IMPLICIT +pub type ApplicationImplicit = CustomClassImplicit; + +/// Application class, reference, EXPLICIT +pub type ApplicationExplicitRef<'a, const TAG: u16, T> = + CustomClassExplicitRef<'a, TAG, T, CLASS_APPLICATION>; + +/// Application class, reference, IMPLICIT +pub type ApplicationImplicitRef<'a, const TAG: u16, T> = + CustomClassImplicitRef<'a, TAG, T, CLASS_APPLICATION>; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use crate::{ + asn1::{context_specific::ContextSpecificExplicit, OctetStringRef}, + Decode, Encode, + }; + use hex_literal::hex; + + #[test] + fn round_trip() { + const EXAMPLE_BYTES: &[u8] = &hex!( + "A2 06" + "04 04" + "01020304" + ); + + let field = + ContextSpecificExplicit::<2, OctetStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); + assert_eq!(field.value, OctetStringRef::new(&[1, 2, 3, 4]).unwrap()); + + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, EXAMPLE_BYTES); + } +} diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index 6867029da..09320449a 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -1,266 +1,38 @@ //! Context-specific field. -use crate::{ - asn1::AnyRef, Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, - Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, -}; -use core::cmp::Ordering; - -/// Context-specific field which wraps an owned inner value. -/// -/// This type decodes/encodes a field which is specific to a particular context -/// and is identified by a [`TagNumber`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct ContextSpecific { - /// Context-specific tag number sans the leading `0b10000000` class - /// identifier bit and `0b100000` constructed flag. - pub tag_number: TagNumber, - - /// Tag mode: `EXPLICIT` VS `IMPLICIT`. - pub tag_mode: TagMode, - - /// Value of the field. - pub value: T, -} - -impl ContextSpecific { - /// Attempt to decode an `EXPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the - /// provided [`TagNumber`]. - /// - /// This method has the following behavior which is designed to simplify - /// handling of extension fields, which are denoted in an ASN.1 schema - /// using the `...` ellipsis extension marker: - /// - /// - Skips over [`ContextSpecific`] fields with a tag number lower than - /// the current one, consuming and ignoring them. - /// - Returns `Ok(None)` if a [`ContextSpecific`] field with a higher tag - /// number is encountered. These fields are not consumed in this case, - /// allowing a field with a lower tag number to be omitted, then the - /// higher numbered field consumed as a follow-up. - /// - Returns `Ok(None)` if anything other than a [`ContextSpecific`] field - /// is encountered. - pub fn decode_explicit<'a, R: Reader<'a>>( - reader: &mut R, - tag_number: TagNumber, - ) -> Result, T::Error> - where - T: Decode<'a>, - { - Self::decode_with(reader, tag_number, |reader| Self::decode(reader)) - } - - /// Attempt to decode an `IMPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the - /// provided [`TagNumber`]. - /// - /// This method otherwise behaves the same as `decode_explicit`, - /// but should be used in cases where the particular fields are `IMPLICIT` - /// as opposed to `EXPLICIT`. - pub fn decode_implicit<'a, R: Reader<'a>>( - reader: &mut R, - tag_number: TagNumber, - ) -> Result, T::Error> - where - T: DecodeValue<'a> + Tagged, - { - Self::decode_with::<_, _, T::Error>(reader, tag_number, |reader| { - let header = Header::decode(reader)?; - let value = T::decode_value(reader, header)?; - - if header.tag.is_constructed() != value.tag().is_constructed() { - return Err(header.tag.non_canonical_error().into()); - } - - Ok(Self { - tag_number, - tag_mode: TagMode::Implicit, - value, - }) - }) - } - - /// Attempt to decode a context-specific field with the given - /// helper callback. - fn decode_with<'a, F, R: Reader<'a>, E>( - reader: &mut R, - tag_number: TagNumber, - f: F, - ) -> Result, E> - where - F: FnOnce(&mut R) -> Result, - E: From, - { - while let Some(octet) = reader.peek_byte() { - let tag = Tag::try_from(octet)?; - - if !tag.is_context_specific() || (tag.number() > tag_number) { - break; - } else if tag.number() == tag_number { - return Some(f(reader)).transpose(); - } else { - AnyRef::decode(reader)?; - } - } - - Ok(None) - } -} - -impl<'a, T> Choice<'a> for ContextSpecific -where - T: Decode<'a> + Tagged, -{ - fn can_decode(tag: Tag) -> bool { - tag.is_context_specific() - } -} - -impl<'a, T> Decode<'a> for ContextSpecific -where - T: Decode<'a>, -{ - type Error = T::Error; - - fn decode>(reader: &mut R) -> Result { - let header = Header::decode(reader)?; - - match header.tag { - Tag::ContextSpecific { - number, - constructed: true, - } => Ok(Self { - tag_number: number, - tag_mode: TagMode::default(), - value: reader.read_nested(header.length, |reader| T::decode(reader))?, - }), - tag => Err(tag.unexpected_error(None).into()), - } - } -} - -impl EncodeValue for ContextSpecific -where - T: EncodeValue + Tagged, -{ - fn value_len(&self) -> Result { - match self.tag_mode { - TagMode::Explicit => self.value.encoded_len(), - TagMode::Implicit => self.value.value_len(), - } - } - - fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { - match self.tag_mode { - TagMode::Explicit => self.value.encode(writer), - TagMode::Implicit => self.value.encode_value(writer), - } - } -} - -impl Tagged for ContextSpecific -where - T: Tagged, -{ - fn tag(&self) -> Tag { - let constructed = match self.tag_mode { - TagMode::Explicit => true, - TagMode::Implicit => self.value.tag().is_constructed(), - }; - - Tag::ContextSpecific { - number: self.tag_number, - constructed, - } - } -} - -impl<'a, T> TryFrom> for ContextSpecific -where - T: Decode<'a>, -{ - type Error = T::Error; - - fn try_from(any: AnyRef<'a>) -> Result, Self::Error> { - match any.tag() { - Tag::ContextSpecific { - number, - constructed: true, - } => Ok(Self { - tag_number: number, - tag_mode: TagMode::default(), - value: T::from_der(any.value())?, - }), - tag => Err(tag.unexpected_error(None).into()), - } - } -} - -impl ValueOrd for ContextSpecific -where - T: EncodeValue + ValueOrd + Tagged, -{ - fn value_cmp(&self, other: &Self) -> Result { - match self.tag_mode { - TagMode::Explicit => self.der_cmp(other), - TagMode::Implicit => self.value_cmp(other), - } - } -} - -/// Context-specific field reference. -/// -/// This type encodes a field which is specific to a particular context -/// and is identified by a [`TagNumber`]. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub struct ContextSpecificRef<'a, T> { - /// Context-specific tag number sans the leading `0b10000000` class - /// identifier bit and `0b100000` constructed flag. - pub tag_number: TagNumber, +use crate::tag::CLASS_CONTEXT_SPECIFIC; - /// Tag mode: `EXPLICIT` VS `IMPLICIT`. - pub tag_mode: TagMode, +use super::custom_class::{ + CustomClassExplicit, CustomClassExplicitRef, CustomClassImplicit, CustomClassImplicitRef, +}; - /// Value of the field. - pub value: &'a T, -} +/// Context-specific class, EXPLICIT +pub type ContextSpecificExplicit = + CustomClassExplicit; -impl<'a, T> ContextSpecificRef<'a, T> { - /// Convert to a [`ContextSpecific`]. - fn encoder(&self) -> ContextSpecific> { - ContextSpecific { - tag_number: self.tag_number, - tag_mode: self.tag_mode, - value: EncodeValueRef(self.value), - } - } -} +/// Context-specific class, IMPLICIT +pub type ContextSpecificImplicit = + CustomClassImplicit; -impl<'a, T> EncodeValue for ContextSpecificRef<'a, T> -where - T: EncodeValue + Tagged, -{ - fn value_len(&self) -> Result { - self.encoder().value_len() - } +/// Context-specific class, reference, EXPLICIT +pub type ContextSpecificExplicitRef<'a, const TAG: u16, T> = + CustomClassExplicitRef<'a, TAG, T, CLASS_CONTEXT_SPECIFIC>; - fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { - self.encoder().encode_value(writer) - } -} - -impl<'a, T> Tagged for ContextSpecificRef<'a, T> -where - T: Tagged, -{ - fn tag(&self) -> Tag { - self.encoder().tag() - } -} +/// Context-specific class, reference, IMPLICIT +pub type ContextSpecificImplicitRef<'a, const TAG: u16, T> = + CustomClassImplicitRef<'a, TAG, T, CLASS_CONTEXT_SPECIFIC>; #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use super::ContextSpecific; - use crate::{asn1::BitStringRef, Decode, Encode, SliceReader, TagMode, TagNumber}; + use crate::{ + asn1::{ + context_specific::{ContextSpecificExplicit, ContextSpecificImplicit}, + BitStringRef, ContextSpecificExplicitRef, ContextSpecificImplicitRef, SetOf, + Utf8StringRef, + }, + Decode, Encode, SliceReader, + }; use hex_literal::hex; // Public key data from `pkcs8` crate's `ed25519-pkcs8-v2.der` @@ -269,8 +41,8 @@ mod tests { #[test] fn round_trip() { - let field = ContextSpecific::>::from_der(EXAMPLE_BYTES).unwrap(); - assert_eq!(field.tag_number.value(), 1); + let field = + ContextSpecificExplicit::<1, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); assert_eq!( field.value, BitStringRef::from_bytes(&EXAMPLE_BYTES[5..]).unwrap() @@ -282,36 +54,32 @@ mod tests { } #[test] - fn context_specific_with_explicit_field() { - let tag_number = TagNumber::new(0); - + fn decode_context_specific_with_explicit_field() { // Empty message let mut reader = SliceReader::new(&[]).unwrap(); assert_eq!( - ContextSpecific::::decode_explicit(&mut reader, tag_number).unwrap(), + ContextSpecificExplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), None ); // Message containing a non-context-specific type let mut reader = SliceReader::new(&hex!("020100")).unwrap(); assert_eq!( - ContextSpecific::::decode_explicit(&mut reader, tag_number).unwrap(), + ContextSpecificExplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), None ); // Message containing an EXPLICIT context-specific field let mut reader = SliceReader::new(&hex!("A003020100")).unwrap(); - let field = ContextSpecific::::decode_explicit(&mut reader, tag_number) + let field = ContextSpecificExplicit::<0, u8>::decode_skipping(&mut reader) .unwrap() .unwrap(); - assert_eq!(field.tag_number, tag_number); - assert_eq!(field.tag_mode, TagMode::Explicit); assert_eq!(field.value, 0); } #[test] - fn context_specific_with_implicit_field() { + fn decode_context_specific_with_implicit_field() { // From RFC8410 Section 10.3: // // @@ -321,15 +89,11 @@ mod tests { let context_specific_implicit_bytes = hex!("81210019BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"); - let tag_number = TagNumber::new(1); - let mut reader = SliceReader::new(&context_specific_implicit_bytes).unwrap(); - let field = ContextSpecific::>::decode_implicit(&mut reader, tag_number) + let field = ContextSpecificImplicit::<1, BitStringRef<'_>>::decode_skipping(&mut reader) .unwrap() .unwrap(); - assert_eq!(field.tag_number, tag_number); - assert_eq!(field.tag_mode, TagMode::Implicit); assert_eq!( field.value.as_bytes().unwrap(), &context_specific_implicit_bytes[3..] @@ -337,22 +101,81 @@ mod tests { } #[test] - fn context_specific_skipping_unknown_field() { - let tag = TagNumber::new(1); + fn decode_context_specific_skipping_unknown_field() { let mut reader = SliceReader::new(&hex!("A003020100A103020101")).unwrap(); - let field = ContextSpecific::::decode_explicit(&mut reader, tag) + let field = ContextSpecificExplicit::<1, u8>::decode_skipping(&mut reader) .unwrap() .unwrap(); assert_eq!(field.value, 1); } #[test] - fn context_specific_returns_none_on_greater_tag_number() { - let tag = TagNumber::new(0); + fn decode_context_specific_returns_none_on_greater_tag_number() { let mut reader = SliceReader::new(&hex!("A103020101")).unwrap(); assert_eq!( - ContextSpecific::::decode_explicit(&mut reader, tag).unwrap(), + ContextSpecificExplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), None ); } + + #[test] + fn encode_context_specific_explicit_ref() { + let mut set = SetOf::new(); + set.insert(8u16).unwrap(); + set.insert(7u16).unwrap(); + + let field = ContextSpecificExplicitRef::<2, SetOf> { value: &set }; + + let mut buf = [0u8; 16]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!( + encoded, + &[ + /* CONTEXT-SPECIFIC [2] */ 0xA2, 0x08, /* SET 0x11 | 0x20 */ 0x31, 0x06, + /* INTEGER */ 0x02, 0x01, 0x07, /* INTEGER */ 0x02, 0x01, 0x08 + ] + ); + + let mut reader = SliceReader::new(encoded).unwrap(); + let field = ContextSpecificExplicit::<2, SetOf>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + assert_eq!(field.value.len(), 2); + assert_eq!(field.value.get(0).cloned(), Some(7)); + assert_eq!(field.value.get(1).cloned(), Some(8)); + } + + #[test] + fn encode_context_specific_implicit_ref() { + let hello = Utf8StringRef::new("Hello").unwrap(); + let world = Utf8StringRef::new("world").unwrap(); + + let mut set = SetOf::new(); + set.insert(hello).unwrap(); + set.insert(world).unwrap(); + + let field = ContextSpecificImplicitRef::<2, SetOf, 2>> { value: &set }; + + let mut buf = [0u8; 16]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!( + encoded, + &[ + 0xA2, 0x0E, // CONTEXT-SPECIFIC [2] + 0x0C, 0x05, b'H', b'e', b'l', b'l', b'o', // UTF8String "Hello" + 0x0C, 0x05, b'w', b'o', b'r', b'l', b'd', // UTF8String "world" + ] + ); + + let mut reader = SliceReader::new(encoded).unwrap(); + let field = + ContextSpecificImplicit::<2, SetOf, 2>>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + assert_eq!(field.value.len(), 2); + assert_eq!(field.value.get(0).cloned(), Some(hello)); + assert_eq!(field.value.get(1).cloned(), Some(world)); + } } diff --git a/der/src/asn1/custom_class.rs b/der/src/asn1/custom_class.rs new file mode 100644 index 000000000..09f1823a4 --- /dev/null +++ b/der/src/asn1/custom_class.rs @@ -0,0 +1,352 @@ +//! Context-specific, Application or Private field. + +use super::any_custom_class::{ + expected_tag_constructed, AnyCustomClassExplicit, AnyCustomClassImplicit, +}; +use super::{AnyRef, Choice}; +use crate::encode::Encode; +use crate::{ + Class, Decode, DecodeValue, DerOrd, EncodeValue, EncodeValueRef, Error, FixedTag, Header, + Length, Reader, SliceReader, Tag, TagNumber, Tagged, ValueOrd, Writer, +}; +use core::cmp::Ordering; + +/// Application, Context-specific or Private class field which wraps an owned inner value `T`. +/// +/// This type decodes/encodes a field which is specific to a particular context +/// and is identified by a [`TagNumber`] in `TAG` generic parameter. +/// +/// - Encodes: always with `TAG` and `CLASS` generic parameter, with inner tag and value. +/// +/// - Decodes: only if `TAG` and `CLASS` generic parameter matches data. +/// +/// Note that only 2 most significant bits of `CLASS` are used. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct CustomClassExplicit { + /// Value of the field. + pub value: T, +} + +/// Application, Context-specific or Private class field which wraps an owned inner value `T`. +/// +/// This type decodes/encodes a field which is specific to a particular context +/// and is identified by a [`TagNumber`] in `TAG` generic parameter. +/// +/// - Encodes: always with `TAG` and `CLASS` generic parameter, with inner value only (implicit, without inner tag). +/// +/// - Decodes: only if `TAG` and `CLASS` generic parameter matches data. +/// +/// Note that only 2 most significant bits of `CLASS` are used. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct CustomClassImplicit { + /// Value of the field. + pub value: T, +} + +impl CustomClassExplicit { + /// Attempt to decode an `EXPLICIT` ASN.1 custom-tagged field with the + /// provided [`TagNumber`]. + /// + /// This method has the following behavior: + /// + /// - Returns `Ok(None)` if a [`CustomClass`] field with a different tag + /// number is encountered. These fields are not consumed in this case, + /// allowing a field with a different tag number to be omitted, then the + /// matching field consumed as a follow-up. + /// - Returns `Ok(None)` if anything other than a [`CustomClass`] field + /// is encountered. + pub fn decode_skipping<'a, R: Reader<'a>>(reader: &mut R) -> Result, T::Error> + where + T: Decode<'a>, + { + match AnyCustomClassExplicit::decode_skipping(Class::from(CLASS), TagNumber(TAG), reader) { + Ok(Some(custom)) => Ok(Some(Self { + value: custom.value, + })), + Ok(None) => Ok(None), + Err(err) => Err(err), + } + } +} + +impl CustomClassImplicit { + /// Attempt to decode an `IMPLICIT` ASN.1 custom-tagged field with the + /// provided [`TagNumber`]. + /// + /// This method otherwise behaves the same as `decode_explicit`, + /// but should be used in cases where the particular fields are `IMPLICIT` + /// as opposed to `EXPLICIT`. + pub fn decode_skipping<'a, R: Reader<'a>>(reader: &mut R) -> Result, T::Error> + where + T: DecodeValue<'a> + Tagged + 'a, + { + match AnyCustomClassImplicit::decode_skipping(Class::from(CLASS), TagNumber(TAG), reader) { + Ok(Some(custom)) => Ok(Some(Self { + value: custom.value, + })), + Ok(None) => Ok(None), + Err(err) => Err(err), + } + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> Choice<'a> for CustomClassExplicit +where + T: Decode<'a> + Tagged, +{ + fn can_decode(tag: Tag) -> bool { + tag.class().bits() == CLASS && tag.number() == TagNumber(TAG) + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> Choice<'a> for CustomClassImplicit +where + T: DecodeValue<'a> + FixedTag + 'a, +{ + fn can_decode(tag: Tag) -> bool { + tag.class().bits() == CLASS + && tag.number() == TagNumber(TAG) + && tag.is_constructed() == ::TAG.is_constructed() + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> Decode<'a> for CustomClassExplicit +where + T: Decode<'a>, +{ + type Error = T::Error; + + fn decode>(reader: &mut R) -> Result { + match AnyCustomClassExplicit::::decode_checked( + Class::from(CLASS), + TagNumber(TAG), + reader, + ) { + Ok(custom) => Ok(Self { + value: custom.value, + }), + Err(err) => Err(err), + } + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> Decode<'a> for CustomClassImplicit +where + T: Tagged + DecodeValue<'a> + 'a, +{ + type Error = T::Error; + + fn decode>(reader: &mut R) -> Result { + match AnyCustomClassImplicit::::decode_checked( + Class::from(CLASS), + TagNumber(TAG), + reader, + ) { + Ok(custom) => Ok(Self { + value: custom.value, + }), + Err(err) => Err(err), + } + } +} + +impl EncodeValue for CustomClassExplicit +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.value.encoded_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.value.encode(writer) + } +} + +impl EncodeValue for CustomClassImplicit +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.value.value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.value.encode_value(writer) + } +} + +impl Tagged for CustomClassExplicit { + fn tag(&self) -> Tag { + // ISO/IEC 8825-1:2021 + // 8.14.3 If implicit tagging (see Rec. ITU-T X.680 | ISO/IEC 8824-1, 31.2.7) was not used in the definition of the type, the + // encoding shall be constructed and the contents octets shall be the complete base encoding [Encode]. + let constructed = true; + expected_tag_constructed(Class::from(CLASS), TagNumber(TAG), constructed) + } +} + +impl Tagged for CustomClassImplicit +where + T: FixedTag, +{ + fn tag(&self) -> Tag { + // ISO/IEC 8825-1:2021 + // 8.14.4 If implicit tagging was used in the definition of the type, then: + // a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise; and + // b) the contents octets shall be the same as the contents octets [EncodeValue] of the base encoding. + let constructed = ::TAG.is_constructed(); + expected_tag_constructed(Class::from(CLASS), TagNumber(TAG), constructed) + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> TryFrom> + for CustomClassExplicit +where + T: Decode<'a>, +{ + type Error = T::Error; + + fn try_from(any: AnyRef<'a>) -> Result { + let tag = any.tag(); + // currently accepts constructed and not constructed + if tag.class().bits() == CLASS { + Ok(Self { + value: T::from_der(any.value())?, + }) + } else { + let expected = expected_tag_constructed(Class::from(CLASS), TagNumber(TAG), true); + Err(tag.unexpected_error(Some(expected)).into()) + } + } +} + +impl<'a, T, const TAG: u16, const CLASS: u8> TryFrom> + for CustomClassImplicit +where + T: DecodeValue<'a>, +{ + type Error = T::Error; + + fn try_from(any: AnyRef<'a>) -> Result { + let tag: Tag = any.tag(); + // currently accepts constructed and not constructed + if tag.class().bits() == CLASS { + let content = any.value(); + // TODO(dishmaker): test + let mut reader = SliceReader::new(content)?; + let value = T::decode_value( + &mut reader, + Header { + tag, + length: content.len().try_into()?, + }, + )?; + + Ok(Self { value }) + } else { + let expected = expected_tag_constructed(Class::from(CLASS), TagNumber(TAG), true); + Err(tag.unexpected_error(Some(expected)).into()) + } + } +} + +impl ValueOrd for CustomClassExplicit +where + T: DerOrd, +{ + fn value_cmp(&self, other: &Self) -> Result { + self.value.der_cmp(&other.value) + } +} + +impl ValueOrd for CustomClassImplicit +where + T: ValueOrd, +{ + fn value_cmp(&self, other: &Self) -> Result { + self.value.value_cmp(&other.value) + } +} + +/// Custom class field reference. +/// +/// This type encodes an `EXPLICIT` field with custom class tag, for example [`Class::ContextSpecific`] +/// and is identified by a [`TagNumber`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct CustomClassExplicitRef<'a, const TAG: u16, T, const CLASS: u8> { + /// Value of the field. + pub value: &'a T, +} + +impl<'a, const TAG: u16, T, const CLASS: u8> CustomClassExplicitRef<'a, TAG, T, CLASS> { + /// Convert to a [`CustomClassExplicit`]. + fn encoder(&self) -> CustomClassExplicit, CLASS> { + CustomClassExplicit { + value: EncodeValueRef(self.value), + } + } +} + +impl<'a, const TAG: u16, T, const CLASS: u8> EncodeValue + for CustomClassExplicitRef<'a, TAG, T, CLASS> +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.encoder().value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.encoder().encode_value(writer) + } +} + +impl<'a, const TAG: u16, T, const CLASS: u8> Tagged for CustomClassExplicitRef<'a, TAG, T, CLASS> { + fn tag(&self) -> Tag { + self.encoder().tag() + } +} + +/// Custom class field reference. +/// +/// This type encodes an `EXPLICIT` field with custom class tag, for example [`Class::ContextSpecific`] +/// and is identified by a [`TagNumber`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct CustomClassImplicitRef<'a, const TAG: u16, T, const CLASS: u8> { + /// Value of the field. + pub value: &'a T, +} + +impl<'a, const TAG: u16, T, const CLASS: u8> CustomClassImplicitRef<'a, TAG, T, CLASS> { + /// Convert to a [`CustomClassImplicit`]. + fn encoder(&self) -> CustomClassImplicit, CLASS> { + CustomClassImplicit { + value: EncodeValueRef(self.value), + } + } +} + +impl<'a, const TAG: u16, T, const CLASS: u8> EncodeValue + for CustomClassImplicitRef<'a, TAG, T, CLASS> +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result { + self.encoder().value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<(), Error> { + self.encoder().encode_value(writer) + } +} + +impl<'a, const TAG: u16, T, const CLASS: u8> Tagged for CustomClassImplicitRef<'a, TAG, T, CLASS> +where + T: Tagged, +{ + fn tag(&self) -> Tag { + let constructed = self.value.tag().is_constructed(); + expected_tag_constructed(Class::from(CLASS), TagNumber(TAG), constructed) + } +} diff --git a/der/src/asn1/optional.rs b/der/src/asn1/optional.rs index 26e24d683..5ad8a210a 100644 --- a/der/src/asn1/optional.rs +++ b/der/src/asn1/optional.rs @@ -10,8 +10,8 @@ where type Error = T::Error; fn decode>(reader: &mut R) -> Result, Self::Error> { - if let Some(byte) = reader.peek_byte() { - if T::can_decode(Tag::try_from(byte)?) { + if let Some(tag) = Tag::peek_optional(reader)? { + if T::can_decode(tag) { return T::decode(reader).map(Some); } } diff --git a/der/src/asn1/private.rs b/der/src/asn1/private.rs new file mode 100644 index 000000000..7daaeefe3 --- /dev/null +++ b/der/src/asn1/private.rs @@ -0,0 +1,156 @@ +//! Private field. + +use crate::tag::CLASS_PRIVATE; + +use super::custom_class::{ + CustomClassExplicit, CustomClassExplicitRef, CustomClassImplicit, CustomClassImplicitRef, +}; + +/// Private class, EXPLICIT +pub type PrivateExplicit = CustomClassExplicit; + +/// Private class, IMPLICIT +pub type PrivateImplicit = CustomClassImplicit; + +/// Private class, reference, EXPLICIT +pub type PrivateExplicitRef<'a, const TAG: u16, T> = + CustomClassExplicitRef<'a, TAG, T, CLASS_PRIVATE>; + +/// Private class, reference, IMPLICIT +pub type PrivateImplicitRef<'a, const TAG: u16, T> = + CustomClassImplicitRef<'a, TAG, T, CLASS_PRIVATE>; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::PrivateExplicit; + use super::PrivateImplicit; + use crate::asn1::PrivateExplicitRef; + use crate::asn1::PrivateImplicitRef; + use crate::{asn1::BitStringRef, Decode, Encode, SliceReader}; + use hex_literal::hex; + + const EXAMPLE_BYTES: &[u8] = + &hex!("E123032100A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); + + #[test] + fn round_trip() { + let field = PrivateExplicit::<1, BitStringRef<'_>>::from_der(EXAMPLE_BYTES).unwrap(); + assert_eq!( + field.value, + BitStringRef::from_bytes(&EXAMPLE_BYTES[5..]).unwrap() + ); + + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + assert_eq!(encoded, EXAMPLE_BYTES); + } + + #[test] + fn encode_round_trip() { + let field = PrivateExplicit::<1, u16> { value: 257u16 }; + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + + let mut reader = SliceReader::new(encoded).unwrap(); + let field = PrivateExplicit::<1, u16>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + assert_eq!(field.value, 257u16); + } + + #[test] + fn encode_round_trip_ref_explicit() { + let value: u16 = 257u16; + + let field = PrivateExplicitRef::<1, u16> { value: &value }; + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + + let mut reader = SliceReader::new(encoded).unwrap(); + let field = PrivateExplicit::<1, u16>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + assert_eq!(field.value, 257u16); + } + + #[test] + fn encode_round_trip_ref_implicit() { + let value: u16 = 257u16; + + let field = PrivateImplicitRef::<1, u16> { value: &value }; + let mut buf = [0u8; 128]; + let encoded = field.encode_to_slice(&mut buf).unwrap(); + + let mut reader = SliceReader::new(encoded).unwrap(); + let field = PrivateImplicit::<1, u16>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + assert_eq!(field.value, 257u16); + } + + #[test] + fn private_with_explicit_field() { + // Empty message + let mut reader = SliceReader::new(&[]).unwrap(); + assert_eq!( + PrivateImplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), + None + ); + + // Message containing a non-private type + let mut reader = SliceReader::new(&hex!("020100")).unwrap(); + assert_eq!( + PrivateImplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), + None + ); + + // Message containing an EXPLICIT private field + let mut reader = SliceReader::new(&hex!("E003020100")).unwrap(); + let field = PrivateExplicit::<0, _>::decode_skipping(&mut reader) + .unwrap() + .unwrap(); + + let value: u8 = field.value; + + assert_eq!(value, 0); + + // Message containing an EXPLICIT private field, primitive (not constructed) + let mut reader = SliceReader::new(&hex!("C003020100")).unwrap(); + let result = PrivateExplicit::<0, u8>::decode_skipping(&mut reader); + assert!(result.is_err()); + } + + #[test] + fn private_with_implicit_field() { + let private_implicit_bytes = + hex!("C1210019BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"); + + let mut reader = SliceReader::new(&private_implicit_bytes).unwrap(); + let field = PrivateImplicit::<1, BitStringRef<'_>>::decode(&mut reader).unwrap(); + + assert_eq!( + field.value.as_bytes().unwrap(), + &private_implicit_bytes[3..] + ); + } + + #[test] + fn private_not_skipping_unknown_field() { + let mut reader = SliceReader::new(&hex!("E003020100E103020101")).unwrap(); + let field = PrivateExplicit::<1, u8>::decode_skipping(&mut reader).unwrap(); + assert_eq!(field, None); + } + + #[test] + fn private_returns_none_on_unequal_tag_number() { + let mut reader = SliceReader::new(&hex!("C103020101")).unwrap(); + assert_eq!( + PrivateExplicit::<0, u8>::decode_skipping(&mut reader).unwrap(), + None + ); + } +} diff --git a/der/src/encode.rs b/der/src/encode.rs index eba1f262d..c1c69a1d0 100644 --- a/der/src/encode.rs +++ b/der/src/encode.rs @@ -73,7 +73,7 @@ where { /// Compute the length of this value in bytes when encoded as ASN.1 DER. fn encoded_len(&self) -> Result { - self.value_len().and_then(|len| len.for_tlv()) + self.value_len().and_then(|len| len.for_tlv(self.tag())) } /// Encode this value as ASN.1 DER using the provided [`Writer`]. diff --git a/der/src/header.rs b/der/src/header.rs index 3d2dcd568..4a2bfbf20 100644 --- a/der/src/header.rs +++ b/der/src/header.rs @@ -15,7 +15,7 @@ pub struct Header { impl Header { /// Maximum number of DER octets a header can be in this crate. - pub(crate) const MAX_SIZE: usize = 1 + Length::MAX_SIZE; + pub(crate) const MAX_SIZE: usize = Tag::MAX_SIZE + Length::MAX_SIZE; /// Create a new [`Header`] from a [`Tag`] and a specified length. /// diff --git a/der/src/length.rs b/der/src/length.rs index 0a992366b..a19058982 100644 --- a/der/src/length.rs +++ b/der/src/length.rs @@ -1,6 +1,6 @@ //! Length calculations for encoded ASN.1 DER values -use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Reader, Result, SliceWriter, Writer}; +use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Reader, Result, SliceWriter, Tag, Writer}; use core::{ cmp::Ordering, fmt, @@ -51,8 +51,8 @@ impl Length { /// Get the length of DER Tag-Length-Value (TLV) encoded data if `self` /// is the length of the inner "value" portion of the message. - pub fn for_tlv(self) -> Result { - Self::ONE + self.encoded_len()? + self + pub fn for_tlv(self, tag: Tag) -> Result { + tag.encoded_len()? + self.encoded_len()? + self } /// Perform saturating addition of two lengths. diff --git a/der/src/lib.rs b/der/src/lib.rs index c7b7c05b9..e9c08849a 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -63,8 +63,8 @@ //! - [`Utf8StringRef`]: ASN.1 `UTF8String`. //! //! Context specific fields can be modeled using these generic types: -//! - [`ContextSpecific`]: decoder/encoder for owned context-specific fields -//! - [`ContextSpecificRef`]: encode-only type for references to context-specific fields +//! - [`ContextSpecificExplicit`]: decoder/encoder for owned context-specific fields +//! - [`ContextSpecificExplicitRef`]: encode-only type for references to context-specific fields //! //! ## Example //! The following example implements X.509's `AlgorithmIdentifier` message type @@ -309,8 +309,10 @@ //! //! [`Any`]: asn1::Any //! [`AnyRef`]: asn1::AnyRef -//! [`ContextSpecific`]: asn1::ContextSpecific -//! [`ContextSpecificRef`]: asn1::ContextSpecificRef +//! [`ContextSpecificExplicit`]: asn1::ContextSpecificExplicit +//! [`ContextSpecificExplicitRef`]: asn1::ContextSpecificExplicitRef +//! [`ContextSpecificImplicit`]: asn1::ContextSpecificImplicit +//! [`ContextSpecificImplicitRef`]: asn1::ContextSpecificImplicitRef //! [`BitString`]: asn1::BitString //! [`BitStringRef`]: asn1::BitStringRef //! [`GeneralizedTime`]: asn1::GeneralizedTime diff --git a/der/src/reader.rs b/der/src/reader.rs index fe77d0359..a8ab071f0 100644 --- a/der/src/reader.rs +++ b/der/src/reader.rs @@ -5,8 +5,9 @@ pub(crate) mod pem; pub(crate) mod slice; use crate::{ - asn1::ContextSpecific, Decode, DecodeValue, Encode, EncodingRules, Error, ErrorKind, FixedTag, - Header, Length, Tag, TagMode, TagNumber, + asn1::{AnyCustomClassExplicit, AnyCustomClassImplicit}, + Class, Decode, DecodeValue, Encode, EncodingRules, Error, ErrorKind, FixedTag, Header, Length, + Tag, TagMode, TagNumber, }; #[cfg(feature = "alloc")] @@ -55,10 +56,19 @@ pub trait Reader<'r>: Sized { T: DecodeValue<'r> + FixedTag + 'r, { Ok(match tag_mode { - TagMode::Explicit => ContextSpecific::::decode_explicit(self, tag_number)?, - TagMode::Implicit => ContextSpecific::::decode_implicit(self, tag_number)?, - } - .map(|field| field.value)) + TagMode::Explicit => AnyCustomClassExplicit::::decode_skipping( + Class::ContextSpecific, + tag_number, + self, + )? + .map(|field| field.value), + TagMode::Implicit => AnyCustomClassImplicit::::decode_skipping( + Class::ContextSpecific, + tag_number, + self, + )? + .map(|field| field.value), + }) } /// Decode a value which impls the [`Decode`] trait. @@ -118,10 +128,7 @@ pub trait Reader<'r>: Sized { /// Peek at the next byte in the reader. #[deprecated(since = "0.8.0-rc.1", note = "use `Tag::peek` instead")] fn peek_tag(&self) -> Result { - match self.peek_byte() { - Some(byte) => byte.try_into(), - None => Err(Error::incomplete(self.input_len())), - } + Tag::peek(self) } /// Read a single byte. diff --git a/der/src/tag.rs b/der/src/tag.rs index d91197a62..c76f5d426 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -6,6 +6,7 @@ mod mode; mod number; pub use self::{class::Class, mode::TagMode, number::TagNumber}; +pub use self::{class::CLASS_APPLICATION, class::CLASS_CONTEXT_SPECIFIC, class::CLASS_PRIVATE}; use crate::{Decode, DerOrd, Encode, Error, ErrorKind, Length, Reader, Result, Writer}; use core::{cmp::Ordering, fmt}; @@ -143,14 +144,39 @@ pub enum Tag { } impl Tag { - /// Peek at the next byte in the reader and attempt to decode it as a [`Tag`] value. + /// Maximum number of octets in a DER encoding of a [`Tag`] using the + /// rules implemented by this crate. + pub(crate) const MAX_SIZE: usize = 4; + + /// Peek at the next bytes in the reader and attempt to decode it as a [`Tag`] value. /// /// Does not modify the reader's state. pub fn peek<'a>(reader: &impl Reader<'a>) -> Result { - match reader.peek_byte() { - Some(byte) => byte.try_into(), - None => Err(Error::incomplete(reader.input_len())), + Self::peek_optional(reader)?.ok_or_else(|| Error::incomplete(reader.input_len())) + } + + pub(crate) fn peek_optional<'a>(reader: &impl Reader<'a>) -> Result> { + let mut buf = [0u8; Self::MAX_SIZE]; + + if reader.peek_into(&mut buf[0..1]).is_err() { + return Ok(None); + }; + + if let Ok(tag) = Self::from_der(&buf[0..1]) { + return Ok(Some(tag)); } + + for i in 2..Self::MAX_SIZE { + let slice = &mut buf[0..i]; + if reader.peek_into(slice).is_err() { + continue; + } + if let Ok(tag) = Self::from_der(slice) { + return Ok(Some(tag)); + } + } + + Err(Error::new(ErrorKind::TagNumberInvalid, reader.position())) } /// Assert that this [`Tag`] matches the provided expected tag. @@ -165,7 +191,7 @@ impl Tag { } /// Get the [`Class`] that corresponds to this [`Tag`]. - pub fn class(self) -> Class { + pub const fn class(self) -> Class { match self { Tag::Application { .. } => Class::Application, Tag::ContextSpecific { .. } => Class::ContextSpecific, @@ -174,73 +200,68 @@ impl Tag { } } - /// Get the [`TagNumber`] (lower 6-bits) for this tag. + /// Get the [`TagNumber`] for this tag. pub fn number(self) -> TagNumber { - TagNumber(self.octet() & TagNumber::MASK) + match self { + Tag::Boolean => TagNumber(1), + Tag::Integer => TagNumber(2), + Tag::BitString => TagNumber(3), + Tag::OctetString => TagNumber(4), + Tag::Null => TagNumber(5), + Tag::ObjectIdentifier => TagNumber(6), + Tag::Real => TagNumber(9), + Tag::Enumerated => TagNumber(10), + Tag::Utf8String => TagNumber(12), + Tag::Sequence => TagNumber(16), + Tag::Set => TagNumber(17), + Tag::NumericString => TagNumber(18), + Tag::PrintableString => TagNumber(19), + Tag::TeletexString => TagNumber(20), + Tag::VideotexString => TagNumber(21), + Tag::Ia5String => TagNumber(22), + Tag::UtcTime => TagNumber(23), + Tag::GeneralizedTime => TagNumber(24), + Tag::VisibleString => TagNumber(26), + Tag::GeneralString => TagNumber(27), + Tag::BmpString => TagNumber(30), + Tag::Application { number, .. } => number, + Tag::ContextSpecific { number, .. } => number, + Tag::Private { number, .. } => number, + } } /// Does this tag represent a constructed (as opposed to primitive) field? - pub fn is_constructed(self) -> bool { - self.octet() & CONSTRUCTED_FLAG != 0 + pub const fn is_constructed(self) -> bool { + match self { + Tag::Sequence | Tag::Set => true, + Tag::Application { constructed, .. } + | Tag::ContextSpecific { constructed, .. } + | Tag::Private { constructed, .. } => constructed, + _ => false, + } } /// Is this an application tag? - pub fn is_application(self) -> bool { - self.class() == Class::Application + pub const fn is_application(self) -> bool { + matches!(self, Tag::Application { .. }) } /// Is this a context-specific tag? - pub fn is_context_specific(self) -> bool { - self.class() == Class::ContextSpecific + pub const fn is_context_specific(self) -> bool { + matches!(self, Tag::ContextSpecific { .. }) } /// Is this a private tag? - pub fn is_private(self) -> bool { - self.class() == Class::Private + pub const fn is_private(self) -> bool { + matches!(self, Tag::Private { .. }) } /// Is this a universal tag? - pub fn is_universal(self) -> bool { - self.class() == Class::Universal - } - - /// Get the octet encoding for this [`Tag`]. - pub fn octet(self) -> u8 { - match self { - Tag::Boolean => 0x01, - Tag::Integer => 0x02, - Tag::BitString => 0x03, - Tag::OctetString => 0x04, - Tag::Null => 0x05, - Tag::ObjectIdentifier => 0x06, - Tag::Real => 0x09, - Tag::Enumerated => 0x0A, - Tag::Utf8String => 0x0C, - Tag::Sequence => 0x10 | CONSTRUCTED_FLAG, - Tag::Set => 0x11 | CONSTRUCTED_FLAG, - Tag::NumericString => 0x12, - Tag::PrintableString => 0x13, - Tag::TeletexString => 0x14, - Tag::VideotexString => 0x15, - Tag::Ia5String => 0x16, - Tag::UtcTime => 0x17, - Tag::GeneralizedTime => 0x18, - Tag::VisibleString => 0x1A, - Tag::GeneralString => 0x1B, - Tag::BmpString => 0x1E, - Tag::Application { - constructed, - number, - } - | Tag::ContextSpecific { - constructed, - number, - } - | Tag::Private { - constructed, - number, - } => self.class().octet(constructed, number), - } + pub const fn is_universal(self) -> bool { + !matches!( + self, + Tag::Application { .. } | Tag::ContextSpecific { .. } | Tag::Private { .. } + ) } /// Create an [`Error`] for an invalid [`Length`]. @@ -271,85 +292,162 @@ impl Tag { } } -impl TryFrom for Tag { +impl<'a> Decode<'a> for Tag { type Error = Error; - fn try_from(byte: u8) -> Result { - let constructed = byte & CONSTRUCTED_FLAG != 0; - let number = TagNumber::try_from(byte & TagNumber::MASK)?; - - match byte { - 0x01 => Ok(Tag::Boolean), - 0x02 => Ok(Tag::Integer), - 0x03 => Ok(Tag::BitString), - 0x04 => Ok(Tag::OctetString), - 0x05 => Ok(Tag::Null), - 0x06 => Ok(Tag::ObjectIdentifier), - 0x09 => Ok(Tag::Real), - 0x0A => Ok(Tag::Enumerated), - 0x0C => Ok(Tag::Utf8String), - 0x12 => Ok(Tag::NumericString), - 0x13 => Ok(Tag::PrintableString), - 0x14 => Ok(Tag::TeletexString), - 0x15 => Ok(Tag::VideotexString), - 0x16 => Ok(Tag::Ia5String), - 0x17 => Ok(Tag::UtcTime), - 0x18 => Ok(Tag::GeneralizedTime), - 0x1A => Ok(Tag::VisibleString), - 0x1B => Ok(Tag::GeneralString), - 0x1E => Ok(Tag::BmpString), - 0x30 => Ok(Tag::Sequence), // constructed - 0x31 => Ok(Tag::Set), // constructed - 0x40..=0x7E => Ok(Tag::Application { - constructed, - number, - }), - 0x80..=0xBE => Ok(Tag::ContextSpecific { - constructed, - number, - }), - 0xC0..=0xFE => Ok(Tag::Private { - constructed, - number, - }), - _ => Err(ErrorKind::TagUnknown { byte }.into()), - } + fn decode>(reader: &mut R) -> Result { + let first_byte = reader.read_byte()?; + + let tag = match first_byte { + 0x01 => Tag::Boolean, + 0x02 => Tag::Integer, + 0x03 => Tag::BitString, + 0x04 => Tag::OctetString, + 0x05 => Tag::Null, + 0x06 => Tag::ObjectIdentifier, + 0x09 => Tag::Real, + 0x0A => Tag::Enumerated, + 0x0C => Tag::Utf8String, + 0x12 => Tag::NumericString, + 0x13 => Tag::PrintableString, + 0x14 => Tag::TeletexString, + 0x15 => Tag::VideotexString, + 0x16 => Tag::Ia5String, + 0x17 => Tag::UtcTime, + 0x18 => Tag::GeneralizedTime, + 0x1A => Tag::VisibleString, + 0x1B => Tag::GeneralString, + 0x1E => Tag::BmpString, + 0x30 => Tag::Sequence, // constructed + 0x31 => Tag::Set, // constructed + 0x40..=0x7F => { + let (constructed, number) = parse_parts(first_byte, reader)?; + + Tag::Application { + constructed, + number, + } + } + 0x80..=0xBF => { + let (constructed, number) = parse_parts(first_byte, reader)?; + + Tag::ContextSpecific { + constructed, + number, + } + } + 0xC0..=0xFF => { + let (constructed, number) = parse_parts(first_byte, reader)?; + + Tag::Private { + constructed, + number, + } + } + byte => return Err(ErrorKind::TagUnknown { byte }.into()), + }; + + Ok(tag) } } -impl From for u8 { - fn from(tag: Tag) -> u8 { - tag.octet() +fn parse_parts<'a, R: Reader<'a>>(first_byte: u8, reader: &mut R) -> Result<(bool, TagNumber)> { + let constructed = first_byte & CONSTRUCTED_FLAG != 0; + let first_number_part = first_byte & TagNumber::MASK; + + if first_number_part != TagNumber::MASK { + return Ok((constructed, TagNumber::new(first_number_part.into()))); } + + let mut multi_byte_tag_number: u16 = 0; + + for _ in 0..Tag::MAX_SIZE - 1 { + multi_byte_tag_number = multi_byte_tag_number + .checked_mul(0x80) + .ok_or_else(|| Error::new(ErrorKind::TagNumberInvalid, reader.position()))?; + + let byte = reader.read_byte()?; + multi_byte_tag_number |= u16::from(byte & 0x7F); + + if byte & 0x80 == 0 { + return Ok((constructed, TagNumber::new(multi_byte_tag_number))); + } + } + + Err(Error::new(ErrorKind::TagNumberInvalid, reader.position())) } -impl From<&Tag> for u8 { - fn from(tag: &Tag) -> u8 { - u8::from(*tag) +/// Length of encoded tag depends only on number it encodes +fn tag_length(tag_number: u16) -> Length { + if tag_number <= 30 { + Length::ONE + } else if tag_number < 0x80 { + Length::new(2) + } else if tag_number < 0x80 * 0x80 { + Length::new(3) + } else { + Length::new(4) } } -impl<'a> Decode<'a> for Tag { - type Error = Error; +fn tag_class_number_bytes( + class: Class, + tag_number: TagNumber, + constructed: bool, + buf: &mut [u8; Tag::MAX_SIZE], +) -> &[u8] { + let mut first_byte = class as u8; + if constructed { + first_byte |= CONSTRUCTED_FLAG; + } + let num = tag_number.value(); + tag_number_bytes(first_byte, num, buf) +} - fn decode>(reader: &mut R) -> Result { - reader.read_byte().and_then(Self::try_from) +/// Tag contains class bits, constructed flag and number +#[allow(clippy::cast_possible_truncation)] +fn tag_number_bytes(first_byte: u8, num: u16, buf: &mut [u8; Tag::MAX_SIZE]) -> &[u8] { + if num <= 30 { + buf[0] = first_byte | num as u8; + &buf[..1] + } else if num < 0x80 { + buf[0] = first_byte | 0x1F; + buf[1] = num as u8; + &buf[..2] + } else if num < 0x80 * 0x80 { + buf[0] = first_byte | 0x1F; + buf[1] = 0x80 | (num >> 7) as u8; + buf[2] = (num & 0x7F) as u8; + &buf[..3] + } else { + buf[0] = first_byte | 0x1F; + buf[1] = 0x80 | (num >> 14) as u8; + buf[2] = 0x80 | (num >> 7) as u8; + buf[3] = (num & 0x7F) as u8; + &buf[..4] } } impl Encode for Tag { fn encoded_len(&self) -> Result { - Ok(Length::ONE) + Ok(tag_length(self.number().value())) } fn encode(&self, writer: &mut impl Writer) -> Result<()> { - writer.write_byte(self.into()) + let mut buf = [0u8; Tag::MAX_SIZE]; + let tag_bytes = + tag_class_number_bytes(self.class(), self.number(), self.is_constructed(), &mut buf); + writer.write(tag_bytes) } } impl DerOrd for Tag { fn der_cmp(&self, other: &Self) -> Result { - Ok(self.octet().cmp(&other.octet())) + Ok(self + .class() + .cmp(&other.class()) + .then_with(|| self.is_constructed().cmp(&other.is_constructed())) + .then_with(|| self.number().cmp(&other.number()))) } } @@ -412,14 +510,18 @@ impl fmt::Display for Tag { impl fmt::Debug for Tag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Tag(0x{:02x}: {})", u8::from(*self), self) + let mut buf = [0u8; Tag::MAX_SIZE]; + let tag_bytes = + tag_class_number_bytes(self.class(), self.number(), self.is_constructed(), &mut buf); + write!(f, "Tag({:02X?}: {})", tag_bytes, self) } } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::{Class, Tag, TagNumber}; - use crate::{Length, Reader, SliceReader}; + use crate::{Decode, Encode, Length, Reader, SliceReader}; #[test] fn tag_class() { @@ -477,11 +579,58 @@ mod tests { } #[test] - #[allow(clippy::unwrap_used)] fn peek() { let reader = SliceReader::new(&[0x02]).unwrap(); assert_eq!(reader.position(), Length::ZERO); assert_eq!(Tag::peek(&reader).unwrap(), Tag::Integer); assert_eq!(reader.position(), Length::ZERO); // Position unchanged } + + #[test] + fn decode_application() { + const TAG_APPLICATION: [u8; 2] = [0x7F, 0x21]; + let mut reader = SliceReader::new(&TAG_APPLICATION).unwrap(); + let tag = Tag::decode(&mut reader).unwrap(); + + assert_eq!( + tag, + Tag::Application { + constructed: true, + number: TagNumber(33) + } + ); + + let mut buf = [0u8; 8]; + let encoded = tag.encode_to_slice(&mut buf).unwrap(); + + assert_eq!(TAG_APPLICATION, encoded); + } + + #[test] + fn decode_private_out_of_range() { + const TAG_PRIVATE: [u8; 4] = [0xFF, 0xFF, 0xFF, 0x7f]; + let mut reader = SliceReader::new(&TAG_PRIVATE).unwrap(); + let result = Tag::decode(&mut reader); + assert!(result.is_err()); + } + + #[test] + fn decode_private() { + const TAG_PRIVATE: [u8; 4] = [0xFF, 0x83, 0xFF, 0x70]; + let mut reader = SliceReader::new(&TAG_PRIVATE).unwrap(); + let tag = Tag::decode(&mut reader).unwrap(); + + assert_eq!( + tag, + Tag::Private { + constructed: true, + number: TagNumber(0xfff0) + } + ); + + let mut buf = [0u8; 8]; + let encoded = tag.encode_to_slice(&mut buf).unwrap(); + + assert_eq!(TAG_PRIVATE, encoded); + } } diff --git a/der/src/tag/class.rs b/der/src/tag/class.rs index ffb2a1e75..eddba8d77 100644 --- a/der/src/tag/class.rs +++ b/der/src/tag/class.rs @@ -1,21 +1,25 @@ //! Class of an ASN.1 tag. -use super::{TagNumber, CONSTRUCTED_FLAG}; use core::fmt; +pub const CLASS_UNIVERSAL: u8 = 0b00000000; +pub const CLASS_APPLICATION: u8 = 0b01000000; +pub const CLASS_CONTEXT_SPECIFIC: u8 = 0b10000000; +pub const CLASS_PRIVATE: u8 = 0b11000000; + /// Class of an ASN.1 tag. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] #[repr(u8)] pub enum Class { /// `UNIVERSAL`: built-in types whose meaning is the same in all /// applications. - Universal = 0b00000000, + Universal = CLASS_UNIVERSAL, /// `APPLICATION`: types whose meaning is specific to an application, /// /// Types in two different applications may have the same /// application-specific tag and different meanings. - Application = 0b01000000, + Application = CLASS_APPLICATION, /// `CONTEXT-SPECIFIC`: types whose meaning is specific to a given /// structured type. @@ -24,17 +28,21 @@ pub enum Class { /// with the same underlying tag within the context of a given structured /// type, and component types in two different structured types may have /// the same tag and different meanings. - ContextSpecific = 0b10000000, + ContextSpecific = CLASS_CONTEXT_SPECIFIC, /// `PRIVATE`: types whose meaning is specific to a given enterprise. - Private = 0b11000000, + Private = CLASS_PRIVATE, } -impl Class { - /// Compute the identifier octet for a tag number of this class. - #[allow(clippy::arithmetic_side_effects)] - pub(super) fn octet(self, constructed: bool, number: TagNumber) -> u8 { - self as u8 | number.value() | (u8::from(constructed) * CONSTRUCTED_FLAG) +impl From for Class { + fn from(value: u8) -> Self { + match (value >> 6) & 0b11 { + 0b00 => Class::Universal, + 0b01 => Class::Application, + 0b10 => Class::ContextSpecific, + 0b11 => Class::Private, + _ => unreachable!(), + } } } @@ -48,3 +56,10 @@ impl fmt::Display for Class { }) } } + +impl Class { + /// Returns 2 bits of class (mask 0b11000000) + pub const fn bits(&self) -> u8 { + *self as u8 + } +} diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs index dfff9a961..0bc2d023d 100644 --- a/der/src/tag/number.rs +++ b/der/src/tag/number.rs @@ -1,137 +1,32 @@ //! ASN.1 tag numbers use super::Tag; -use crate::{Error, ErrorKind, Result}; use core::fmt; /// ASN.1 tag numbers (i.e. lower 5 bits of a [`Tag`]). /// /// From X.690 Section 8.1.2.2: /// +/// Tag numbers ranging from zero to 30 (inclusive) can be represented as a +/// single identifier octet. +/// /// > bits 5 to 1 shall encode the number of the tag as a binary integer with /// > bit 5 as the most significant bit. /// -/// This library supports tag numbers ranging from zero to 30 (inclusive), -/// which can be represented as a single identifier octet. -/// /// Section 8.1.2.4 describes how to support multi-byte tag numbers, which are -/// encoded by using a leading tag number of 31 (`0b11111`). This library -/// deliberately does not support this: tag numbers greater than 30 are -/// disallowed. +/// encoded by using a leading tag number of 31 (`0b11111`). +/// +/// This library supports tag numbers with 16 bit values #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub struct TagNumber(pub(super) u8); +pub struct TagNumber(pub u16); impl TagNumber { - /// Tag number `0` - pub const N0: Self = Self(0); - - /// Tag number `1` - pub const N1: Self = Self(1); - - /// Tag number `2` - pub const N2: Self = Self(2); - - /// Tag number `3` - pub const N3: Self = Self(3); - - /// Tag number `4` - pub const N4: Self = Self(4); - - /// Tag number `5` - pub const N5: Self = Self(5); - - /// Tag number `6` - pub const N6: Self = Self(6); - - /// Tag number `7` - pub const N7: Self = Self(7); - - /// Tag number `8` - pub const N8: Self = Self(8); - - /// Tag number `9` - pub const N9: Self = Self(9); - - /// Tag number `10` - pub const N10: Self = Self(10); - - /// Tag number `11` - pub const N11: Self = Self(11); - - /// Tag number `12` - pub const N12: Self = Self(12); - - /// Tag number `13` - pub const N13: Self = Self(13); - - /// Tag number `14` - pub const N14: Self = Self(14); - - /// Tag number `15` - pub const N15: Self = Self(15); - - /// Tag number `16` - pub const N16: Self = Self(16); - - /// Tag number `17` - pub const N17: Self = Self(17); - - /// Tag number `18` - pub const N18: Self = Self(18); - - /// Tag number `19` - pub const N19: Self = Self(19); - - /// Tag number `20` - pub const N20: Self = Self(20); - - /// Tag number `21` - pub const N21: Self = Self(21); - - /// Tag number `22` - pub const N22: Self = Self(22); - - /// Tag number `23` - pub const N23: Self = Self(23); - - /// Tag number `24` - pub const N24: Self = Self(24); - - /// Tag number `25` - pub const N25: Self = Self(25); - - /// Tag number `26` - pub const N26: Self = Self(26); - - /// Tag number `27` - pub const N27: Self = Self(27); - - /// Tag number `28` - pub const N28: Self = Self(28); - - /// Tag number `29` - pub const N29: Self = Self(29); - - /// Tag number `30` - pub const N30: Self = Self(30); - /// Mask value used to obtain the tag number from a tag octet. pub(super) const MASK: u8 = 0b11111; - /// Maximum tag number supported (inclusive). - const MAX: u8 = 30; - /// Create a new tag number (const-friendly). - /// - /// Panics if the tag number is greater than `30`. - /// For a fallible conversion, use [`TryFrom`] instead. - pub const fn new(byte: u8) -> Self { - #[allow(clippy::panic)] - if byte > Self::MAX { - panic!("tag number out of range"); - } - - Self(byte) + pub const fn new(number: u16) -> Self { + Self(number) } /// Create an `APPLICATION` tag with this tag number. @@ -159,28 +54,11 @@ impl TagNumber { } /// Get the inner value. - pub fn value(self) -> u8 { + pub fn value(self) -> u16 { self.0 } } -impl TryFrom for TagNumber { - type Error = Error; - - fn try_from(byte: u8) -> Result { - match byte { - 0..=Self::MAX => Ok(Self(byte)), - _ => Err(ErrorKind::TagNumberInvalid.into()), - } - } -} - -impl From for u8 { - fn from(tag_number: TagNumber) -> u8 { - tag_number.0 - } -} - impl fmt::Display for TagNumber { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) @@ -192,7 +70,7 @@ impl fmt::Display for TagNumber { #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for TagNumber { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Ok(Self::new(u.int_in_range(0..=Self::MAX)?)) + Ok(Self::new(u.int_in_range(0..=u16::MAX)?)) } fn size_hint(depth: usize) -> (usize, Option) { diff --git a/der/src/writer/slice.rs b/der/src/writer/slice.rs index 082ca4775..8d381faac 100644 --- a/der/src/writer/slice.rs +++ b/der/src/writer/slice.rs @@ -1,8 +1,8 @@ //! Slice writer. use crate::{ - asn1::*, Encode, EncodeValue, ErrorKind, Header, Length, Result, Tag, TagMode, TagNumber, - Tagged, Writer, + asn1::*, Class, Encode, EncodeValue, EncodeValueRef, ErrorKind, Header, Length, Result, Tag, + TagMode, TagNumber, Tagged, Writer, }; /// [`Writer`] which encodes DER into a mutable output byte slice. @@ -76,12 +76,21 @@ impl<'a> SliceWriter<'a> { where T: EncodeValue + Tagged, { - ContextSpecificRef { - tag_number, - tag_mode, - value, + match tag_mode { + TagMode::Explicit => AnyCustomClassExplicit { + tag_number, + class: Class::ContextSpecific, + value: EncodeValueRef(value), + } + .encode(self), + TagMode::Implicit => AnyCustomClassImplicit { + tag_number, + class: Class::ContextSpecific, + value: EncodeValueRef(value), + constructed: value.tag().is_constructed(), + } + .encode(self), } - .encode(self) } /// Encode an ASN.1 `SEQUENCE` of the given length. @@ -137,14 +146,39 @@ impl<'a> Writer for SliceWriter<'a> { #[allow(clippy::unwrap_used)] mod tests { use super::SliceWriter; - use crate::{Encode, ErrorKind, Length}; + use crate::{Encode, ErrorKind, Length, TagMode, TagNumber}; #[test] fn overlength_message() { let mut buffer = []; let mut writer = SliceWriter::new(&mut buffer); let err = false.encode(&mut writer).err().unwrap(); + assert_eq!(err.kind(), ErrorKind::Overlength); assert_eq!(err.position(), Some(Length::ONE)); } + + #[test] + fn context_specific_explicit() { + let mut buffer = [0u8; 16]; + let mut writer = SliceWriter::new(&mut buffer); + writer + .context_specific(TagNumber(1), TagMode::Explicit, &false) + .unwrap(); + + let written = writer.finish().unwrap(); + assert_eq!(written, &[0xA1, 0x03, /* BOOLEAN */ 0x01, 0x01, 0x00]); + } + + #[test] + fn context_specific_implicit() { + let mut buffer = [0u8; 16]; + let mut writer = SliceWriter::new(&mut buffer); + writer + .context_specific(TagNumber(1), TagMode::Implicit, &false) + .unwrap(); + + let written = writer.finish().unwrap(); + assert_eq!(written, &[0x81, 0x01, 0x00]); + } } diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 510e9a621..30dca887c 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -514,6 +514,138 @@ mod sequence { } } +#[cfg(all(feature = "derive", feature = "oid"))] +mod sequence_application { + use const_oid::ObjectIdentifier; + use der::{asn1::ApplicationImplicit, Decode, Encode, Sequence}; + use hex_literal::hex; + + const TACHO_CERT_DER: &[u8] = &hex!( + "7F 21 81 C8" // Application 33 + + "7F 4E 81 81" // Appliction 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 certificate + pub type TachographCertificate<'a> = ApplicationImplicit<33, TachographCertificateInner<'a>>; + + /// EU Tachograph certificate inner sequence + #[derive(Sequence)] + #[asn1(tag_mode = "IMPLICIT")] + pub struct TachographCertificateInner<'a> { + /// constructed + #[asn1(application = "78")] + pub body: TachographCertificateBody<'a>, + + /// primitive + #[asn1(application = "55", type = "OCTET STRING")] + pub signature: &'a [u8], + } + + /// EU Tachograph 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 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.value.signature; + assert_eq!(&sig[..2], hex!("6D 3E")); + assert_eq!(tacho_cert.value.body.profile_identifier, &[0x00]); + assert_eq!( + tacho_cert.value.body.authority_reference, + hex!("FD 45 43 20 01 FF FF 01") + ); + assert_eq!( + tacho_cert.value.body.holder_authorisation, + hex!("FF 53 4D 52 44 54 0E") + ); + assert_eq!( + tacho_cert.value.body.public_key.domain_parameters, + ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") + ); + assert_eq!( + &tacho_cert.value.body.public_key.public_point[..4], + hex!("04 30 E8 EE") + ); + const GREECE: &[u8] = b"GR "; + assert_eq!(&tacho_cert.value.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); + } +} + 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 diff --git a/der_derive/src/attributes.rs b/der_derive/src/attributes.rs index 911adcbd6..80ede0667 100644 --- a/der_derive/src/attributes.rs +++ b/der_derive/src/attributes.rs @@ -92,7 +92,7 @@ pub(crate) struct FieldAttrs { pub asn1_type: Option, /// Value of the `#[asn1(context_specific = "...")] attribute if provided. - pub context_specific: Option, + pub class: 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 @@ -126,7 +126,7 @@ impl FieldAttrs { /// Parse attributes from a struct field or enum variant. pub fn parse(attrs: &[Attribute], type_attrs: &TypeAttrs) -> syn::Result { let mut asn1_type = None; - let mut context_specific = None; + let mut class = None; let mut default = None; let mut extensible = None; let mut optional = None; @@ -139,11 +139,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.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); } - context_specific = Some(tag_number); + class = Some(ClassNum::ContextSpecific(tag_number)); + // `private = "..."` attribute + } else if let Some(tag_number) = attr.parse_value("private")? { + if class.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); + } + + class = Some(ClassNum::Private(tag_number)); + // `application = "..."` attribute + } else if let Some(tag_number) = attr.parse_value("application")? { + if class.is_some() { + abort!( + attr.name, + "duplicate ASN.1 class attribute (`application`, `context_specific`, `private`)" + ); + } + + class = Some(ClassNum::Application(tag_number)); // `default` attribute } else if attr.parse_value::("default")?.is_some() { if default.is_some() { @@ -202,7 +225,7 @@ impl FieldAttrs { Ok(Self { asn1_type, - context_specific, + class, default, extensible: extensible.unwrap_or_default(), optional: optional.unwrap_or_default(), @@ -213,11 +236,8 @@ 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 { - constructed: self.constructed, - number: tag_number, - })), + match self.class { + Some(ref class) => Ok(Some(class.get_tag(self.constructed))), None => match self.tag_mode { TagMode::Explicit => Ok(self.asn1_type.map(Tag::Universal)), @@ -231,37 +251,28 @@ 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 let Some(ref class) = self.class { + let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or(quote!(_)); + let ClassTokens { + tag_type, + tag_number, + class_type, + .. + } = class.to_tokens(type_params, self.tag_mode); + + // let context_specific = + // if self.tag_mode == TagMode::Implicit || self.extensible || self.is_optional() { + // quote! { + // #class_type::decode_skipping( + // reader + // )? + // } + // } else { + // quote! { + // #class_type::decode_skipping(reader)? + // } + // }; + let context_specific = quote! { #class_type::decode_skipping(reader)? }; if self.is_optional() { if let Some(default) = &self.default { @@ -269,12 +280,16 @@ impl FieldAttrs { } else { quote!(#context_specific.map(|cs| cs.value)) } + // }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 { + #tag_type { number: #tag_number, constructed: #constructed }.value_error() @@ -297,14 +312,20 @@ impl FieldAttrs { /// 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(); - let tag_mode = self.tag_mode.to_tokens(); + match self.class { + Some(ref class) => { + let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or(quote!(_)); + let ClassTokens { ref_type, .. } = class.to_tokens(type_params, self.tag_mode); + + let binding = if self.asn1_type.is_none() { + quote!(#binding) + } else { + // TODO(dishmaker): needed because of From<&str> for Utf8StringRef + // eg. #[asn1(type = "UTF8String")] Utf8String(String) + quote!(&#binding.try_into()?) + }; quote! { - ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: #tag_mode, + #ref_type { value: #binding, }.encode_value(encoder) } @@ -392,3 +413,98 @@ impl AttrNameValue { }) } } + +#[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 { + match (tag_mode, self) { + (TagMode::Explicit, Self::ContextSpecific(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + ClassTokens { + tag_type: quote!(::der::Tag::ContextSpecific), + class_type: quote!(::der::asn1::ContextSpecificExplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::ContextSpecificExplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + (TagMode::Implicit, Self::ContextSpecific(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + ClassTokens { + tag_type: quote!(::der::Tag::ContextSpecific), + class_type: quote!(::der::asn1::ContextSpecificImplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::ContextSpecificImplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + (TagMode::Explicit, Self::Private(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + + ClassTokens { + tag_type: quote!(::der::Tag::Private), + class_type: quote!(::der::asn1::PrivateExplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::PrivateExplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + (TagMode::Implicit, Self::Private(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + + ClassTokens { + tag_type: quote!(::der::Tag::Private), + class_type: quote!(::der::asn1::PrivateImplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::PrivateImplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + (TagMode::Explicit, Self::Application(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + ClassTokens { + tag_type: quote!(::der::Tag::Application), + class_type: quote!(::der::asn1::ApplicationExplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::ApplicationExplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + (TagMode::Implicit, Self::Application(tag_number)) => { + let tag_number_u16 = tag_number.to_tokens_u16(); + ClassTokens { + tag_type: quote!(::der::Tag::Application), + class_type: quote!(::der::asn1::ApplicationImplicit::<#tag_number_u16, #type_params>), + ref_type: quote!(::der::asn1::ApplicationImplicitRef::<'_, #tag_number_u16, #type_params>), + tag_number: tag_number.to_tokens(), + } + } + } + } + + pub fn get_tag(&self, constructed: bool) -> Tag { + match self { + ClassNum::ContextSpecific(number) => Tag::ContextSpecific { + constructed, + number: *number, + }, + ClassNum::Private(number) => Tag::Private { + constructed, + number: *number, + }, + ClassNum::Application(number) => Tag::Application { + constructed, + number: *number, + }, + } + } +} diff --git a/der_derive/src/choice.rs b/der_derive/src/choice.rs index 8cd50ca01..e81d99b27 100644 --- a/der_derive/src/choice.rs +++ b/der_derive/src/choice.rs @@ -145,7 +145,7 @@ impl DeriveChoice { #[allow(clippy::unwrap_used)] mod tests { use super::DeriveChoice; - use crate::{Asn1Type, Tag, TagMode}; + use crate::{attributes::ClassNum, Asn1Type, Tag, TagMode}; use syn::parse_quote; /// Based on `Time` as defined in RFC 5280: @@ -176,7 +176,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, None); assert_eq!(utc_time.attrs.tag_mode, TagMode::Explicit); assert_eq!(utc_time.tag, Tag::Universal(Asn1Type::UtcTime)); @@ -186,7 +186,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, None); assert_eq!(general_time.attrs.tag_mode, TagMode::Explicit); assert_eq!(general_time.tag, Tag::Universal(Asn1Type::GeneralizedTime)); } @@ -220,8 +220,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, + Some(ClassNum::ContextSpecific("0".parse().unwrap())) ); assert_eq!(bit_string.attrs.tag_mode, TagMode::Implicit); assert_eq!( @@ -235,7 +235,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, + Some(ClassNum::ContextSpecific("1".parse().unwrap())) + ); assert_eq!(time.attrs.tag_mode, TagMode::Implicit); assert_eq!( time.tag, @@ -249,8 +252,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, + 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 2b2eadd34..ea534ae84 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::{attributes::ClassTokens, FieldAttrs, Tag, TypeAttrs}; use proc_macro2::TokenStream; use quote::quote; use syn::{Fields, Ident, Path, Type, Variant}; @@ -123,16 +123,26 @@ 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(); - let tag_mode = self.attrs.tag_mode.to_tokens(); - + match self.attrs.class { + Some(ref class) => { + let type_params: TokenStream = self + .attrs + .asn1_type + .map(|ty| ty.type_path()) + .unwrap_or(quote!(_)); + let ClassTokens { ref_type, .. } = + class.to_tokens(type_params, self.attrs.tag_mode); + + let variant_into = if self.attrs.asn1_type.is_none() { + quote! { variant } + } else { + // TODO(dishmaker): needed because of From<&str> for Utf8StringRef + // eg. #[asn1(type = "UTF8String")] Utf8String(String) + quote! { &variant.try_into()? } + }; quote! { - Self::#ident(variant) => ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: #tag_mode, - value: variant, + Self::#ident(variant) => #ref_type { + value: #variant_into, }.value_len(), } } @@ -154,7 +164,10 @@ impl ChoiceVariant { #[cfg(test)] mod tests { use super::ChoiceVariant; - use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber}; + use crate::{ + attributes::ClassNum, choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, + TagNumber, + }; use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -249,23 +262,23 @@ mod tests { #[test] fn explicit() { - for tag_number in [0, 1, 2, 3] { + for tag_number_u16 in [0, 1, 2, 3] { for constructed in [false, true] { let ident = Ident::new("ExplicitVariant", Span::call_site()); let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class: Some(ClassNum::ContextSpecific(TagNumber(tag_number_u16))), ..Default::default() }; assert_eq!(attrs.tag_mode, TagMode::Explicit); let tag = TagOrPath::Tag(Tag::ContextSpecific { constructed, - number: TagNumber(tag_number), + number: TagNumber(tag_number_u16), }); let variant = ChoiceVariant { ident, attrs, tag }; - let tag_number = TagNumber(tag_number).to_tokens(); + let tag_number = TagNumber(tag_number_u16).to_tokens(); assert_eq!( variant.to_decode_tokens().to_string(), @@ -274,12 +287,9 @@ mod tests { constructed: #constructed, number: #tag_number, } => Ok(Self::ExplicitVariant( - match ::der::asn1::ContextSpecific::<>::decode(reader)? { - field if field.tag_number == #tag_number => Some(field), - _ => None - } + ::der::asn1::ContextSpecificExplicit::<#tag_number_u16, _>::decode_skipping(reader)? .ok_or_else(|| { - der::Tag::ContextSpecific { + ::der::Tag::ContextSpecific { number: #tag_number, constructed: #constructed } @@ -294,9 +304,7 @@ mod tests { assert_eq!( variant.to_encode_value_tokens().to_string(), quote! { - Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: ::der::TagMode::Explicit, + Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificExplicitRef::<'_, #tag_number_u16, _> { value: variant, } .encode_value(encoder), @@ -307,9 +315,7 @@ mod tests { assert_eq!( variant.to_value_len_tokens().to_string(), quote! { - Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: ::der::TagMode::Explicit, + Self::ExplicitVariant(variant) => ::der::asn1::ContextSpecificExplicitRef::<'_, #tag_number_u16, _> { value: variant, } .value_len(), @@ -333,24 +339,24 @@ mod tests { #[test] fn implicit() { - for tag_number in [0, 1, 2, 3] { + for tag_number_u16 in [0, 1, 2, 3] { for constructed in [false, true] { let ident = Ident::new("ImplicitVariant", Span::call_site()); let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class: Some(ClassNum::ContextSpecific(TagNumber(tag_number_u16))), tag_mode: TagMode::Implicit, ..Default::default() }; let tag = TagOrPath::Tag(Tag::ContextSpecific { constructed, - number: TagNumber(tag_number), + number: TagNumber(tag_number_u16), }); let variant = ChoiceVariant { ident, attrs, tag }; - let tag_number = TagNumber(tag_number).to_tokens(); + let tag_number = TagNumber(tag_number_u16).to_tokens(); assert_eq!( variant.to_decode_tokens().to_string(), @@ -359,12 +365,9 @@ mod tests { constructed: #constructed, number: #tag_number, } => Ok(Self::ImplicitVariant( - ::der::asn1::ContextSpecific::<>::decode_implicit( - reader, - #tag_number - )? + ::der::asn1::ContextSpecificImplicit::<#tag_number_u16, _>::decode_skipping(reader)? .ok_or_else(|| { - der::Tag::ContextSpecific { + ::der::Tag::ContextSpecific { number: #tag_number, constructed: #constructed } @@ -379,9 +382,7 @@ mod tests { assert_eq!( variant.to_encode_value_tokens().to_string(), quote! { - Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: ::der::TagMode::Implicit, + Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificImplicitRef::<'_, #tag_number_u16, _> { value: variant, } .encode_value(encoder), @@ -392,9 +393,7 @@ mod tests { assert_eq!( variant.to_value_len_tokens().to_string(), quote! { - Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificRef { - tag_number: #tag_number, - tag_mode: ::der::TagMode::Implicit, + Self::ImplicitVariant(variant) => ::der::asn1::ContextSpecificImplicitRef::<'_, #tag_number_u16, _> { value: variant, } .value_len(), diff --git a/der_derive/src/sequence.rs b/der_derive/src/sequence.rs index f347c727f..6bcad49bb 100644 --- a/der_derive/src/sequence.rs +++ b/der_derive/src/sequence.rs @@ -138,7 +138,7 @@ impl DeriveSequence { #[allow(clippy::bool_assert_comparison)] mod tests { use super::DeriveSequence; - use crate::{Asn1Type, TagMode}; + use crate::{attributes::ClassNum, Asn1Type, TagMode}; use syn::parse_quote; /// X.509 SPKI `AlgorithmIdentifier`. @@ -163,13 +163,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, 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, None); assert_eq!(parameters_field.attrs.tag_mode, TagMode::Explicit); } @@ -197,7 +197,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, None); assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit); let subject_public_key_field = &ir.fields[1]; @@ -206,7 +206,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, None); assert_eq!(subject_public_key_field.attrs.tag_mode, TagMode::Explicit); } @@ -265,7 +265,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, None); assert_eq!(version_field.attrs.extensible, false); assert_eq!(version_field.attrs.optional, false); assert_eq!(version_field.attrs.tag_mode, TagMode::Explicit); @@ -273,7 +273,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, None); assert_eq!(algorithm_field.attrs.extensible, false); assert_eq!(algorithm_field.attrs.optional, false); assert_eq!(algorithm_field.attrs.tag_mode, TagMode::Explicit); @@ -284,7 +284,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, 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); @@ -293,8 +293,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, + Some(ClassNum::ContextSpecific("0".parse().unwrap())) ); assert_eq!(attributes_field.attrs.extensible, true); assert_eq!(attributes_field.attrs.optional, true); @@ -304,8 +304,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, + Some(ClassNum::ContextSpecific("1".parse().unwrap())) ); assert_eq!(public_key_field.attrs.extensible, true); assert_eq!(public_key_field.attrs.optional, true); @@ -341,23 +341,26 @@ mod tests { 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, + 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, + 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, + Some(ClassNum::ContextSpecific("2".parse().unwrap())) ); assert_eq!(utf8_string.attrs.tag_mode, TagMode::Implicit); } diff --git a/der_derive/src/sequence/field.rs b/der_derive/src/sequence/field.rs index d91599c0a..df50c6ea6 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::{ + attributes::{ClassNum, ClassTokens}, + Asn1Type, FieldAttrs, TagMode, TypeAttrs, +}; use proc_macro2::TokenStream; use quote::quote; use syn::{Field, Ident, Path, Type}; @@ -65,8 +68,8 @@ impl SequenceField { "`type` and `default` are mutually exclusive" ); - // TODO(tarcieri): support for context-specific fields with defaults? - if self.attrs.context_specific.is_none() { + // TODO(tarcieri): support for fields with defaults? + if self.attrs.class.is_none() { lowerer.apply_default(default, &self.field_type); } } @@ -88,8 +91,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) = &attrs.class { + lowerer.apply_class(class, &attrs.tag_mode, attrs.optional) } if let Some(default) = &attrs.default { @@ -204,32 +207,23 @@ impl LowerFieldEncoder { }; } - /// Make this field context-specific. - fn apply_context_specific( - &mut self, - tag_number: &TagNumber, - tag_mode: &TagMode, - optional: bool, - ) { + /// Apply the non-universal class. + fn apply_class(&mut self, class: &ClassNum, tag_mode: &TagMode, optional: bool) { let encoder = &self.encoder; - let number_tokens = tag_number.to_tokens(); - let mode_tokens = tag_mode.to_tokens(); + let type_params = quote!(_); + let ClassTokens { ref_type, .. } = class.to_tokens(type_params, *tag_mode); if optional { self.encoder = quote! { #encoder.as_ref().map(|field| { - ::der::asn1::ContextSpecificRef { - tag_number: #number_tokens, - tag_mode: #mode_tokens, + #ref_type { value: field, } }) }; } else { self.encoder = quote! { - ::der::asn1::ContextSpecificRef { - tag_number: #number_tokens, - tag_mode: #mode_tokens, + #ref_type { value: &#encoder, } }; @@ -240,7 +234,7 @@ impl LowerFieldEncoder { #[cfg(test)] mod tests { use super::SequenceField; - use crate::{FieldAttrs, TagMode, TagNumber}; + use crate::{attributes::ClassNum, FieldAttrs, TagMode, TagNumber}; use proc_macro2::Span; use quote::quote; use syn::{punctuated::Punctuated, Ident, Path, PathSegment, Type, TypePath}; @@ -269,7 +263,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: None, + class: None, default: None, extensible: false, optional: false, @@ -309,7 +303,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: Some(TagNumber(0)), + class: Some(ClassNum::ContextSpecific(TagNumber(0))), default: None, extensible: false, optional: false, @@ -328,13 +322,10 @@ mod tests { assert_eq!( field.to_decode_tokens().to_string(), quote! { - let implicit_field = ::der::asn1::ContextSpecific::<>::decode_implicit( - reader, - ::der::TagNumber::N0 - )? + let implicit_field = ::der::asn1::ContextSpecificImplicit::<0u16, _>::decode_skipping(reader)? .ok_or_else(|| { - der::Tag::ContextSpecific { - number: ::der::TagNumber::N0, + ::der::Tag::ContextSpecific { + number: ::der::TagNumber(0u16), constructed: false } .value_error() @@ -347,9 +338,7 @@ mod tests { assert_eq!( field.to_encode_tokens().to_string(), quote! { - ::der::asn1::ContextSpecificRef { - tag_number: ::der::TagNumber::N0, - tag_mode: ::der::TagMode::Implicit, + ::der::asn1::ContextSpecificImplicitRef::<'_, 0u16, _> { value: &self.implicit_field, } } diff --git a/der_derive/src/tag.rs b/der_derive/src/tag.rs index a1cf529cb..93ce1320f 100644 --- a/der_derive/src/tag.rs +++ b/der_derive/src/tag.rs @@ -23,6 +23,24 @@ 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, + + /// Private tag number + number: TagNumber, + }, + + /// Application tags with an associated [`TagNumber`]. + Application { + /// Is the inner ASN.1 type constructed? + constructed: bool, + + /// Application tag number + number: TagNumber, + }, } impl Tag { @@ -34,12 +52,6 @@ impl Tag { constructed, number, } => { - let constructed = if constructed { - quote!(true) - } else { - quote!(false) - }; - let number = number.to_tokens(); quote! { @@ -49,6 +61,32 @@ impl Tag { } } } + Tag::Private { + constructed, + number, + } => { + let number = number.to_tokens(); + + quote! { + ::der::Tag::Private { + constructed: #constructed, + number: #number, + } + } + } + Tag::Application { + constructed, + number, + } => { + let number: TokenStream = number.to_tokens(); + + quote! { + ::der::Tag::Application { + constructed: #constructed, + number: #number, + } + } + } } } } @@ -71,6 +109,7 @@ pub(crate) enum TagMode { impl TagMode { /// Lower this [`TagMode`] to a [`TokenStream`] with the `der` /// crate's corresponding enum variant for this tag mode. + #[allow(dead_code)] pub fn to_tokens(self) -> TokenStream { match self { TagMode::Explicit => quote!(::der::TagMode::Explicit), @@ -117,48 +156,18 @@ impl Display for TagMode { /// ASN.1 tag numbers (i.e. lower 5 bits of a [`Tag`]). #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] -pub(crate) struct TagNumber(pub u8); +pub(crate) struct TagNumber(pub u16); impl TagNumber { - /// Maximum tag number supported (inclusive). - pub const MAX: u8 = 30; - /// Get tokens describing this tag. pub fn to_tokens(self) -> TokenStream { - match self.0 { - 0 => quote!(::der::TagNumber::N0), - 1 => quote!(::der::TagNumber::N1), - 2 => quote!(::der::TagNumber::N2), - 3 => quote!(::der::TagNumber::N3), - 4 => quote!(::der::TagNumber::N4), - 5 => quote!(::der::TagNumber::N5), - 6 => quote!(::der::TagNumber::N6), - 7 => quote!(::der::TagNumber::N7), - 8 => quote!(::der::TagNumber::N8), - 9 => quote!(::der::TagNumber::N9), - 10 => quote!(::der::TagNumber::N10), - 11 => quote!(::der::TagNumber::N11), - 12 => quote!(::der::TagNumber::N12), - 13 => quote!(::der::TagNumber::N13), - 14 => quote!(::der::TagNumber::N14), - 15 => quote!(::der::TagNumber::N15), - 16 => quote!(::der::TagNumber::N16), - 17 => quote!(::der::TagNumber::N17), - 18 => quote!(::der::TagNumber::N18), - 19 => quote!(::der::TagNumber::N19), - 20 => quote!(::der::TagNumber::N20), - 21 => quote!(::der::TagNumber::N21), - 22 => quote!(::der::TagNumber::N22), - 23 => quote!(::der::TagNumber::N23), - 24 => quote!(::der::TagNumber::N24), - 25 => quote!(::der::TagNumber::N25), - 26 => quote!(::der::TagNumber::N26), - 27 => quote!(::der::TagNumber::N27), - 28 => quote!(::der::TagNumber::N28), - 29 => quote!(::der::TagNumber::N29), - 30 => quote!(::der::TagNumber::N30), - _ => unreachable!("tag number out of range: {}", self), - } + let number = self.0; + quote!(::der::TagNumber(#number)) + } + + pub fn to_tokens_u16(self) -> TokenStream { + let number = self.0; + quote!(#number) } } @@ -166,13 +175,9 @@ impl FromStr for TagNumber { type Err = ParseError; fn from_str(s: &str) -> Result { - let n = s.parse::().map_err(|_| ParseError)?; + let n = s.parse::().map_err(|_| ParseError)?; - if n <= Self::MAX { - Ok(Self(n)) - } else { - Err(ParseError) - } + Ok(Self(n)) } } diff --git a/gss-api/src/lib.rs b/gss-api/src/lib.rs index 59316485d..1a4f50808 100644 --- a/gss-api/src/lib.rs +++ b/gss-api/src/lib.rs @@ -140,7 +140,7 @@ mod tests { AnyRef::new( Tag::ContextSpecific { constructed: true, - number: TagNumber::N0 + number: TagNumber(0) }, &inner_bytes ) @@ -153,7 +153,7 @@ mod tests { inner_context_token: AnyRef::new( Tag::ContextSpecific { constructed: true, - number: TagNumber::N0, + number: TagNumber(0), }, &inner_bytes, ) diff --git a/pkcs1/src/params.rs b/pkcs1/src/params.rs index 52ce89642..5bbcc47da 100644 --- a/pkcs1/src/params.rs +++ b/pkcs1/src/params.rs @@ -2,7 +2,7 @@ use crate::{Error, Result}; use der::{ - asn1::{AnyRef, ContextSpecificRef, ObjectIdentifier}, + asn1::{AnyRef, ContextSpecificExplicitRef, ObjectIdentifier}, oid::AssociatedOid, Decode, DecodeValue, Encode, EncodeValue, FixedTag, Length, Reader, Sequence, Tag, TagMode, TagNumber, Writer, @@ -117,51 +117,46 @@ impl<'a> RsaPssParams<'a> { } } - fn context_specific_hash(&self) -> Option>> { + fn context_specific_hash( + &self, + ) -> Option>> { if self.hash == SHA_1_AI { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N0, - tag_mode: TagMode::Explicit, - value: &self.hash, - }) + Some(ContextSpecificExplicitRef { value: &self.hash }) } } fn context_specific_mask_gen( &self, - ) -> Option>>> { + ) -> Option>>> + { if self.mask_gen == default_mgf1_sha1() { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N1, - tag_mode: TagMode::Explicit, + Some(ContextSpecificExplicitRef { value: &self.mask_gen, }) } } - fn context_specific_salt_len(&self) -> Option> { + fn context_specific_salt_len(&self) -> Option> { if self.salt_len == RsaPssParams::SALT_LEN_DEFAULT { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N2, - tag_mode: TagMode::Explicit, + Some(ContextSpecificExplicitRef { value: &self.salt_len, }) } } - fn context_specific_trailer_field(&self) -> Option> { + fn context_specific_trailer_field( + &self, + ) -> Option> { if self.trailer_field == TrailerField::default() { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N3, - tag_mode: TagMode::Explicit, + Some(ContextSpecificExplicitRef { value: &self.trailer_field, }) } @@ -186,16 +181,16 @@ impl<'a> DecodeValue<'a> for RsaPssParams<'a> { reader.read_nested(header.length, |reader| { Ok(Self { hash: reader - .context_specific(TagNumber::N0, TagMode::Explicit)? + .context_specific(TagNumber(0), TagMode::Explicit)? .unwrap_or(SHA_1_AI), mask_gen: reader - .context_specific(TagNumber::N1, TagMode::Explicit)? + .context_specific(TagNumber(1), TagMode::Explicit)? .unwrap_or_else(default_mgf1_sha1), salt_len: reader - .context_specific(TagNumber::N2, TagMode::Explicit)? + .context_specific(TagNumber(2), TagMode::Explicit)? .unwrap_or(RsaPssParams::SALT_LEN_DEFAULT), trailer_field: reader - .context_specific(TagNumber::N3, TagMode::Explicit)? + .context_specific(TagNumber(3), TagMode::Explicit)? .unwrap_or_default(), }) }) @@ -294,27 +289,24 @@ impl<'a> RsaOaepParams<'a> { } } - fn context_specific_hash(&self) -> Option>> { + fn context_specific_hash( + &self, + ) -> Option>> { if self.hash == SHA_1_AI { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N0, - tag_mode: TagMode::Explicit, - value: &self.hash, - }) + Some(ContextSpecificExplicitRef { value: &self.hash }) } } fn context_specific_mask_gen( &self, - ) -> Option>>> { + ) -> Option>>> + { if self.mask_gen == default_mgf1_sha1() { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N1, - tag_mode: TagMode::Explicit, + Some(ContextSpecificExplicitRef { value: &self.mask_gen, }) } @@ -322,13 +314,11 @@ impl<'a> RsaOaepParams<'a> { fn context_specific_p_source( &self, - ) -> Option>> { + ) -> Option>> { if self.p_source == default_pempty_string() { None } else { - Some(ContextSpecificRef { - tag_number: TagNumber::N2, - tag_mode: TagMode::Explicit, + Some(ContextSpecificExplicitRef { value: &self.p_source, }) } @@ -351,13 +341,13 @@ impl<'a> DecodeValue<'a> for RsaOaepParams<'a> { reader.read_nested(header.length, |reader| { Ok(Self { hash: reader - .context_specific(TagNumber::N0, TagMode::Explicit)? + .context_specific(TagNumber(0), TagMode::Explicit)? .unwrap_or(SHA_1_AI), mask_gen: reader - .context_specific(TagNumber::N1, TagMode::Explicit)? + .context_specific(TagNumber(1), TagMode::Explicit)? .unwrap_or_else(default_mgf1_sha1), p_source: reader - .context_specific(TagNumber::N2, TagMode::Explicit)? + .context_specific(TagNumber(2), TagMode::Explicit)? .unwrap_or_else(default_pempty_string), }) }) diff --git a/pkcs1/src/version.rs b/pkcs1/src/version.rs index f880253f2..fcbbcf383 100644 --- a/pkcs1/src/version.rs +++ b/pkcs1/src/version.rs @@ -60,7 +60,7 @@ impl<'a> Decode<'a> for Version { impl Encode for Version { fn encoded_len(&self) -> der::Result { - der::Length::ONE.for_tlv() + der::Length::ONE.for_tlv(Self::TAG) } fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { diff --git a/pkcs12/src/safe_bag.rs b/pkcs12/src/safe_bag.rs index 333530821..2bd2319fc 100644 --- a/pkcs12/src/safe_bag.rs +++ b/pkcs12/src/safe_bag.rs @@ -64,12 +64,7 @@ impl ::der::EncodeValue for SafeBag { use ::der::Encode as _; [ self.bag_id.encoded_len()?, - ::der::asn1::ContextSpecificRef { - tag_number: ::der::TagNumber::N0, - tag_mode: ::der::TagMode::Explicit, - value: &content, - } - .encoded_len()?, + ::der::asn1::ContextSpecificExplicitRef::<0, _> { value: &content }.encoded_len()?, self.bag_attributes.encoded_len()?, ] .into_iter() @@ -79,12 +74,7 @@ impl ::der::EncodeValue for SafeBag { use ::der::Encode as _; self.bag_id.encode(writer)?; let content = AnyRef::from_der(&self.bag_value)?; - ::der::asn1::ContextSpecificRef { - tag_number: ::der::TagNumber::N0, - tag_mode: ::der::TagMode::Explicit, - value: &content, - } - .encode(writer)?; + ::der::asn1::ContextSpecificExplicitRef::<0, _> { value: &content }.encode(writer)?; self.bag_attributes.encode(writer)?; Ok(()) } diff --git a/pkcs12/tests/cert_tests.rs b/pkcs12/tests/cert_tests.rs index c25f8d390..3889d72c3 100644 --- a/pkcs12/tests/cert_tests.rs +++ b/pkcs12/tests/cert_tests.rs @@ -4,7 +4,7 @@ use const_oid::db::{ rfc5912::ID_SHA_256, }; use der::{ - asn1::{ContextSpecific, OctetString}, + asn1::{AnyCustomClassExplicit, OctetString}, Decode, Encode, }; use hex_literal::hex; @@ -199,8 +199,7 @@ fn decode_sample_pfx() { for cert_bag in cert_bags { match cert_bag.bag_id { pkcs12::PKCS_12_CERT_BAG_OID => { - let cs: der::asn1::ContextSpecific = - ContextSpecific::from_der(&cert_bag.bag_value).unwrap(); + let cs = AnyCustomClassExplicit::::from_der(&cert_bag.bag_value).unwrap(); let cb = cs.value; assert_eq!( include_bytes!("examples/cert.der"), @@ -242,8 +241,10 @@ fn decode_sample_pfx() { for safe_bag in safe_bags { match safe_bag.bag_id { pkcs12::PKCS_12_PKCS8_KEY_BAG_OID => { - let cs: ContextSpecific> = - ContextSpecific::from_der(&safe_bag.bag_value).unwrap(); + let cs = AnyCustomClassExplicit::>::from_der( + &safe_bag.bag_value, + ) + .unwrap(); let mut ciphertext = cs.value.encrypted_data.as_bytes().to_vec(); let plaintext = cs .value @@ -606,8 +607,7 @@ fn decode_sample_pfx2() { for safe_bag in safe_bags { match safe_bag.bag_id { pkcs12::PKCS_12_CERT_BAG_OID => { - let cs: ContextSpecific = - ContextSpecific::from_der(&safe_bag.bag_value).unwrap(); + let cs = AnyCustomClassExplicit::::from_der(&safe_bag.bag_value).unwrap(); assert_eq!( include_bytes!("examples/cert.der"), cs.value.cert_value.as_bytes() @@ -628,8 +628,10 @@ fn decode_sample_pfx2() { for safe_bag in safe_bags { match safe_bag.bag_id { pkcs12::PKCS_12_PKCS8_KEY_BAG_OID => { - let cs: ContextSpecific> = - ContextSpecific::from_der(&safe_bag.bag_value).unwrap(); + let cs = AnyCustomClassExplicit::>::from_der( + &safe_bag.bag_value, + ) + .unwrap(); let mut ciphertext = cs.value.encrypted_data.as_bytes().to_vec(); let plaintext = cs .value diff --git a/pkcs8/src/private_key_info.rs b/pkcs8/src/private_key_info.rs index 9d6392e99..dc1fc474b 100644 --- a/pkcs8/src/private_key_info.rs +++ b/pkcs8/src/private_key_info.rs @@ -3,9 +3,9 @@ use crate::{Error, Result, Version}; use core::fmt; use der::{ - asn1::{AnyRef, BitStringRef, ContextSpecific, OctetStringRef}, - Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader, Sequence, TagMode, - TagNumber, Writer, + asn1::{AnyRef, BitStringRef, ContextSpecificImplicit, OctetStringRef}, + Class, Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, Length, Reader, Sequence, + TagNumber, Tagged, Writer, }; use spki::AlgorithmIdentifier; @@ -28,7 +28,10 @@ use der::pem::PemLabel; use subtle::{Choice, ConstantTimeEq}; /// Context-specific tag number for the public key. -const PUBLIC_KEY_TAG: TagNumber = TagNumber::N1; +const PUBLIC_KEY_TAG: u16 = 1; + +/// Context-specific tag number for the public key. +const PUBLIC_KEY_TAG_NUMBER: TagNumber = TagNumber(PUBLIC_KEY_TAG); /// PKCS#8 `PrivateKeyInfo`. /// @@ -174,14 +177,12 @@ where PubKey: BitStringLike, { /// Get a `BIT STRING` representation of the public key, if present. - fn public_key_bit_string(&self) -> Option>> { + fn public_key_bit_string( + &self, + ) -> Option>> { self.public_key.as_ref().map(|pk| { let value = pk.as_bit_string(); - ContextSpecific { - tag_number: PUBLIC_KEY_TAG, - tag_mode: TagMode::Implicit, - value, - } + ContextSpecificImplicit { value } }) } } @@ -200,14 +201,16 @@ where let version = Version::decode(reader)?; let algorithm = reader.decode()?; let private_key = Key::decode(reader)?; + let public_key = - reader.context_specific::(PUBLIC_KEY_TAG, TagMode::Implicit)?; + ContextSpecificImplicit::::decode_skipping(reader)?; + let public_key = public_key.map(|key| key.value); if version.has_public_key() != public_key.is_some() { return Err(reader.error( der::Tag::ContextSpecific { constructed: true, - number: PUBLIC_KEY_TAG, + number: PUBLIC_KEY_TAG_NUMBER, } .value_error() .kind(), @@ -216,7 +219,10 @@ where // Ignore any remaining extension fields while !reader.is_finished() { - reader.decode::>>()?; + let any = reader.decode::>()?; + if any.tag().class() != Class::ContextSpecific { + return Err(reader.error(any.tag().value_error().kind())); + } } Ok(Self { diff --git a/pkcs8/src/version.rs b/pkcs8/src/version.rs index d5a3c5747..352e9728f 100644 --- a/pkcs8/src/version.rs +++ b/pkcs8/src/version.rs @@ -35,7 +35,7 @@ impl<'a> Decode<'a> for Version { impl Encode for Version { fn encoded_len(&self) -> der::Result { - der::Length::from(1u8).for_tlv() + der::Length::from(1u8).for_tlv(Self::TAG) } fn encode(&self, writer: &mut impl Writer) -> der::Result<()> { diff --git a/pkcs8/tests/encrypted_private_key.rs b/pkcs8/tests/encrypted_private_key.rs index a1560d37c..e06b3091a 100644 --- a/pkcs8/tests/encrypted_private_key.rs +++ b/pkcs8/tests/encrypted_private_key.rs @@ -4,7 +4,7 @@ use der::asn1::OctetStringRef; use hex_literal::hex; -use pkcs8::{pkcs5::pbes2, EncryptedPrivateKeyInfoRef, PrivateKeyInfoRef}; +use pkcs8::{pkcs5::pbes2, EncryptedPrivateKeyInfoRef}; #[cfg(feature = "alloc")] use der::Encode; @@ -13,7 +13,7 @@ use der::Encode; use der::EncodePem; #[cfg(feature = "encryption")] -use pkcs8::EncryptedPrivateKeyInfoOwned; +use pkcs8::{EncryptedPrivateKeyInfoOwned, PrivateKeyInfoRef}; /// Ed25519 PKCS#8 private key plaintext encoded as ASN.1 DER #[cfg(feature = "encryption")] diff --git a/sec1/src/private_key.rs b/sec1/src/private_key.rs index b1c832350..6d06d720d 100644 --- a/sec1/src/private_key.rs +++ b/sec1/src/private_key.rs @@ -8,9 +8,8 @@ use crate::{EcParameters, Error, Result}; use core::fmt; use der::{ - asn1::{BitStringRef, ContextSpecific, ContextSpecificRef, OctetStringRef}, - Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag, TagMode, - TagNumber, Writer, + asn1::{BitStringRef, ContextSpecificExplicit, ContextSpecificExplicitRef, OctetStringRef}, + Decode, DecodeValue, Encode, EncodeValue, Header, Length, Reader, Sequence, Tag, Writer, }; #[cfg(all(feature = "alloc", feature = "zeroize"))] @@ -31,10 +30,10 @@ use der::pem::PemLabel; const VERSION: u8 = 1; /// Context-specific tag number for the elliptic curve parameters. -const EC_PARAMETERS_TAG: TagNumber = TagNumber::new(0); +const EC_PARAMETERS_TAG: u16 = 0; /// Context-specific tag number for the public key. -const PUBLIC_KEY_TAG: TagNumber = TagNumber::new(1); +const PUBLIC_KEY_TAG: u16 = 1; /// SEC1 elliptic curve private key. /// @@ -71,25 +70,19 @@ pub struct EcPrivateKey<'a> { } impl<'a> EcPrivateKey<'a> { - fn context_specific_parameters(&self) -> Option> { - self.parameters.as_ref().map(|params| ContextSpecificRef { - tag_number: EC_PARAMETERS_TAG, - tag_mode: TagMode::Explicit, - value: params, - }) + fn context_specific_parameters( + &self, + ) -> Option> { + self.parameters + .as_ref() + .map(|params| ContextSpecificExplicitRef { value: params }) } fn context_specific_public_key( &self, - ) -> der::Result>>> { + ) -> der::Result>>> { self.public_key - .map(|pk| { - BitStringRef::from_bytes(pk).map(|value| ContextSpecific { - tag_number: PUBLIC_KEY_TAG, - tag_mode: TagMode::Explicit, - value, - }) - }) + .map(|pk| BitStringRef::from_bytes(pk).map(|value| ContextSpecificExplicit { value })) .transpose() } } @@ -104,9 +97,19 @@ impl<'a> DecodeValue<'a> for EcPrivateKey<'a> { } let private_key = OctetStringRef::decode(reader)?.as_bytes(); - let parameters = reader.context_specific(EC_PARAMETERS_TAG, TagMode::Explicit)?; - let public_key = reader - .context_specific::>(PUBLIC_KEY_TAG, TagMode::Explicit)? + + let parameters = + ContextSpecificExplicit::::decode_skipping( + reader, + )?; + let parameters = parameters.map(|p| p.value); + + let public_key = + ContextSpecificExplicit::>::decode_skipping( + reader, + )?; + let public_key = public_key + .map(|key| key.value) .map(|bs| bs.as_bytes().ok_or_else(|| Tag::BitString.value_error())) .transpose()?; diff --git a/x509-cert/tests/certificate.rs b/x509-cert/tests/certificate.rs index 87b001a36..aeb87693c 100644 --- a/x509-cert/tests/certificate.rs +++ b/x509-cert/tests/certificate.rs @@ -1,7 +1,9 @@ //! Certificate tests use der::{ - asn1::{BitStringRef, ContextSpecific, ObjectIdentifier, PrintableStringRef, Utf8StringRef}, + asn1::{ + BitStringRef, ContextSpecificExplicit, ObjectIdentifier, PrintableStringRef, Utf8StringRef, + }, Decode, DecodeValue, Encode, FixedTag, Header, Reader, Tag, Tagged, }; use hex_literal::hex; @@ -90,7 +92,7 @@ impl<'a> DecodeValue<'a> for DeferDecodeTbsCertificate<'a> { header: Header, ) -> der::Result> { reader.read_nested(header.length, |reader| { - let version = ContextSpecific::decode_explicit(reader, ::der::TagNumber::N0)? + let version = ContextSpecificExplicit::<0, u8>::decode_skipping(reader)? .map(|cs| cs.value) .unwrap_or_else(Default::default); diff --git a/x509-cert/tests/pkix_extensions.rs b/x509-cert/tests/pkix_extensions.rs index ff7c1d3de..1467c5dac 100644 --- a/x509-cert/tests/pkix_extensions.rs +++ b/x509-cert/tests/pkix_extensions.rs @@ -1,7 +1,10 @@ //! Certificate tests #![allow(clippy::bool_assert_comparison)] +use const_oid::db::rfc5280::*; +use const_oid::db::rfc5912::ID_CE_CERTIFICATE_POLICIES; use const_oid::AssociatedOid; use der::asn1::{Ia5StringRef, OctetString, PrintableStringRef, Utf8StringRef}; +use der::TagNumber; use der::{Decode, Encode, ErrorKind, Length, Tag, Tagged}; use hex_literal::hex; use x509_cert::ext::pkix::crl::dp::{DistributionPoint, ReasonFlags, Reasons}; @@ -11,9 +14,6 @@ use x509_cert::ext::Extensions; use x509_cert::name::Name; use x509_cert::{serial_number::SerialNumber, Certificate, Version}; -use const_oid::db::rfc5280::*; -use const_oid::db::rfc5912::ID_CE_CERTIFICATE_POLICIES; - fn spin_over_exts(exts: &Extensions) { for ext in exts { match ext.extn_id { @@ -840,8 +840,6 @@ fn decode_cert() { #[test] fn decode_idp() { - use der::TagNumber; - // IDP from 04A8739769B3C090A11DCDFABA3CF33F4BEF21F3.crl in PKITS 2048 in ficam-scvp-testing repo let idp = IssuingDistributionPoint::from_der(&hex!("30038201FF")).unwrap(); assert_eq!(idp.only_contains_ca_certs, true); @@ -1109,10 +1107,13 @@ fn decode_idp() { panic!("Expected FullName") } } +} - //--------------------------------- - // Negative tests - //--------------------------------- +//--------------------------------- +// Negative tests +//--------------------------------- +#[test] +fn decode_idp_negative_reasonflags() { // Value contains more than length value indicates let reason_flags = ReasonFlags::from_der(&hex!("0302079F80")); let err = reason_flags.err().unwrap(); @@ -1135,19 +1136,6 @@ fn decode_idp() { err.kind() ); - // Value incomplete relative to length value - let idp = - IssuingDistributionPoint::from_der(&hex!("3067A060A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C8304079F80")); - let err = idp.err().unwrap(); - assert_eq!(err.position().unwrap(), 103u8.into()); - assert_eq!( - ErrorKind::Incomplete { - expected_len: 106u8.into(), - actual_len: 105u8.into() - }, - err.kind() - ); - // Truncated let reason_flags = ReasonFlags::from_der(&hex!("0303079F")); let err = reason_flags.err().unwrap(); @@ -1160,9 +1148,9 @@ fn decode_idp() { ); // Nonsensical tag where BIT STRING tag should be - let reason_flags = ReasonFlags::from_der(&hex!("FF03079F80")); + let reason_flags = ReasonFlags::from_der(&hex!("2503079F80")); let err = reason_flags.err().unwrap(); - assert_eq!(ErrorKind::TagNumberInvalid, err.kind()); + assert_eq!(ErrorKind::TagUnknown { byte: 0x25 }, err.kind()); // INTEGER tag where BIT STRING expected let reason_flags = ReasonFlags::from_der(&hex!("0203079F80")); @@ -1174,7 +1162,26 @@ fn decode_idp() { }, err.kind() ); +} +#[test] +fn decode_idp_negative_incomplete() { + // Value incomplete relative to length value + let idp = + IssuingDistributionPoint::from_der(&hex!("3067A060A05EA45C305A310B3009060355040613025553311F301D060355040A131654657374204365727469666963617465732032303137311C301A060355040B13136F6E6C79536F6D65526561736F6E7320434133310C300A0603550403130343524C8304079F80")); + let err = idp.err().unwrap(); + assert_eq!(err.position().unwrap(), 105u8.into()); + assert_eq!( + ErrorKind::Incomplete { + expected_len: 106u8.into(), + actual_len: 105u8.into() + }, + err.kind() + ); +} + +#[test] +fn decode_idp_negative_constructed() { // Context specific tag that should be primitive is constructed let idp = IssuingDistributionPoint::from_der(&hex!("3003A201FF")); let err = idp.err().unwrap(); @@ -1187,19 +1194,34 @@ fn decode_idp() { }, err.kind() ); +} +#[test] +fn decode_idp_negative_bool_long() { // Boolean value is two bytes long let idp = IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358402FFFF")); let err = idp.err().unwrap(); - assert_eq!(ErrorKind::Length { tag: Tag::Boolean }, err.kind()); + assert_eq!( + ErrorKind::Incomplete { + expected_len: Length::new(365), + actual_len: Length::new(364) + }, + err.kind() + ); +} +#[test] +fn decode_idp_negative_bool_invalid() { // Boolean value is neither 0x00 nor 0xFF let idp = IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C20434135840175")); let err = idp.err().unwrap(); assert_eq!(ErrorKind::Noncanonical { tag: Tag::Boolean }, err.kind()); +} +#[test] +fn decode_idp_negative_length_rdn() { // Length on second RDN in first name indicates more bytes than are present let idp = IssuingDistributionPoint::from_der(&hex!("30820168A0820161A082015DA4753073310B3009060355040613025553311F301D060355040A13995465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434136A4753073310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353129302706035504031320696E6469726563742043524C20666F7220696E64697265637443524C20434137A46D306B310B3009060355040613025553311F301D060355040A13165465737420436572746966696361746573203230313731183016060355040B130F696E64697265637443524C204341353121301F0603550403131843524C3120666F7220696E64697265637443524C204341358401FF"));