From 1f43a08b401bf9db7c81516c974570b17a9dfde2 Mon Sep 17 00:00:00 2001 From: Harris Kaufmann Date: Wed, 2 Oct 2024 16:26:26 +0200 Subject: [PATCH] support private tags and tag numbers >30 that are stored in long form --- der/src/asn1.rs | 2 + der/src/asn1/context_specific.rs | 4 +- der/src/asn1/optional.rs | 4 +- der/src/asn1/private.rs | 350 +++++++++++++++++++++++++++ der/src/encode.rs | 2 +- der/src/header.rs | 2 +- der/src/length.rs | 6 +- der/src/reader.rs | 5 +- der/src/tag.rs | 283 ++++++++++++++-------- der/src/tag/class.rs | 9 - der/src/tag/number.rs | 49 +--- der_derive/src/attributes.rs | 107 ++++++-- der_derive/src/choice.rs | 19 +- der_derive/src/choice/variant.rs | 23 +- der_derive/src/sequence.rs | 37 +-- der_derive/src/sequence/field.rs | 42 ++-- der_derive/src/tag.rs | 77 +++--- pkcs1/src/version.rs | 2 +- pkcs8/src/version.rs | 2 +- pkcs8/tests/encrypted_private_key.rs | 2 +- 20 files changed, 734 insertions(+), 293 deletions(-) create mode 100644 der/src/asn1/private.rs diff --git a/der/src/asn1.rs b/der/src/asn1.rs index b04b1b58f..b7d009ab2 100644 --- a/der/src/asn1.rs +++ b/der/src/asn1.rs @@ -20,6 +20,7 @@ mod octet_string; mod oid; mod optional; mod printable_string; +mod private; #[cfg(feature = "real")] mod real; mod sequence; @@ -41,6 +42,7 @@ pub use self::{ null::Null, octet_string::OctetStringRef, printable_string::PrintableStringRef, + private::{Private, PrivateRef}, sequence::{Sequence, SequenceRef}, sequence_of::{SequenceOf, SequenceOfIter}, set_of::{SetOf, SetOfIter}, diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index 6867029da..5be554763 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -89,9 +89,7 @@ impl ContextSpecific { F: FnOnce(&mut R) -> Result, E: From, { - while let Some(octet) = reader.peek_byte() { - let tag = Tag::try_from(octet)?; - + while let Some(tag) = Tag::peek_optional(reader)? { if !tag.is_context_specific() || (tag.number() > tag_number) { break; } else if tag.number() == tag_number { 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..07a7d9a94 --- /dev/null +++ b/der/src/asn1/private.rs @@ -0,0 +1,350 @@ +//! Private 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; + +/// Private field which wraps an owned inner value. +/// +/// This type encodes a field which is whose meaning is specific to a given +/// enterprise and is identified by a [`TagNumber`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct Private { + /// Private 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 Private { + /// Attempt to decode an `EXPLICIT` ASN.1 `PRIVATE` field with the + /// provided [`TagNumber`]. + /// + /// This method has the following behavior: + /// + /// - Returns `Ok(None)` if a [`Private`] 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 [`Private`] 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 `PRIVATE` 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 private 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(tag) = Tag::peek_optional(reader)? { + if !tag.is_private() || (tag.number() != tag_number) { + break; + } else { + return Some(f(reader)).transpose(); + } + } + + Ok(None) + } +} + +impl<'a, T> Choice<'a> for Private +where + T: Decode<'a> + Tagged, +{ + fn can_decode(tag: Tag) -> bool { + tag.is_private() + } +} + +impl<'a, T> Decode<'a> for Private +where + T: Decode<'a>, +{ + type Error = T::Error; + + fn decode>(reader: &mut R) -> Result { + let header = Header::decode(reader)?; + + match header.tag { + Tag::Private { + 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 Private +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 Private +where + T: Tagged, +{ + fn tag(&self) -> Tag { + let constructed = match self.tag_mode { + TagMode::Explicit => true, + TagMode::Implicit => self.value.tag().is_constructed(), + }; + + Tag::Private { + number: self.tag_number, + constructed, + } + } +} + +impl<'a, T> TryFrom> for Private +where + T: Decode<'a>, +{ + type Error = T::Error; + + fn try_from(any: AnyRef<'a>) -> Result, Self::Error> { + match any.tag() { + Tag::Private { + 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 Private +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), + } + } +} + +/// Private field reference. +/// +/// This type encodes a field which is whose meaning is specific to a given +/// enterprise and is identified by a [`TagNumber`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct PrivateRef<'a, T> { + /// Private tag number sans the leading `0b11000000` 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: &'a T, +} + +impl<'a, T> PrivateRef<'a, T> { + /// Convert to a [`Private`]. + fn encoder(&self) -> Private> { + Private { + tag_number: self.tag_number, + tag_mode: self.tag_mode, + value: EncodeValueRef(self.value), + } + } +} + +impl<'a, T> EncodeValue for PrivateRef<'a, T> +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, T> Tagged for PrivateRef<'a, T> +where + T: Tagged, +{ + fn tag(&self) -> Tag { + self.encoder().tag() + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use super::Private; + use crate::{asn1::BitStringRef, Decode, Encode, SliceReader, TagMode, TagNumber}; + use hex_literal::hex; + + // Public key data from `pkcs8` crate's `ed25519-pkcs8-v2.der` + const EXAMPLE_BYTES: &[u8] = + &hex!("A123032100A3A7EAE3A8373830BC47E1167BC50E1DB551999651E0E2DC587623438EAC3F31"); + + #[test] + fn round_trip() { + let field = Private::>::from_der(EXAMPLE_BYTES).unwrap(); + assert_eq!(field.tag_number.value(), 1); + 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 private_with_explicit_field() { + let tag_number = TagNumber::new(0); + + // Empty message + let mut reader = SliceReader::new(&[]).unwrap(); + assert_eq!( + Private::::decode_explicit(&mut reader, tag_number).unwrap(), + None + ); + + // Message containing a non-private type + let mut reader = SliceReader::new(&hex!("020100")).unwrap(); + assert_eq!( + Private::::decode_explicit(&mut reader, tag_number).unwrap(), + None + ); + + // Message containing an EXPLICIT private field + let mut reader = SliceReader::new(&hex!("A003020100")).unwrap(); + let field = Private::::decode_explicit(&mut reader, tag_number) + .unwrap() + .unwrap(); + + assert_eq!(field.tag_number, tag_number); + assert_eq!(field.tag_mode, TagMode::Explicit); + assert_eq!(field.value, 0); + } + + #[test] + fn private_with_implicit_field() { + // From RFC8410 Section 10.3: + // + // + // 81 33: [1] 00 19 BF 44 09 69 84 CD FE 85 41 BA C1 67 DC 3B + // 96 C8 50 86 AA 30 B6 B6 CB 0C 5C 38 AD 70 31 66 + // E1 + let private_implicit_bytes = + hex!("81210019BF44096984CDFE8541BAC167DC3B96C85086AA30B6B6CB0C5C38AD703166E1"); + + let tag_number = TagNumber::new(1); + + let mut reader = SliceReader::new(&private_implicit_bytes).unwrap(); + let field = Private::>::decode_implicit(&mut reader, tag_number) + .unwrap() + .unwrap(); + + assert_eq!(field.tag_number, tag_number); + assert_eq!(field.tag_mode, TagMode::Implicit); + assert_eq!( + field.value.as_bytes().unwrap(), + &private_implicit_bytes[3..] + ); + } + + #[test] + fn private_skipping_unknown_field() { + let tag = TagNumber::new(1); + let mut reader = SliceReader::new(&hex!("A003020100A103020101")).unwrap(); + let field = Private::::decode_explicit(&mut reader, tag) + .unwrap() + .unwrap(); + assert_eq!(field.value, 1); + } + + #[test] + fn private_returns_none_on_greater_tag_number() { + let tag = TagNumber::new(0); + let mut reader = SliceReader::new(&hex!("A103020101")).unwrap(); + assert_eq!( + Private::::decode_explicit(&mut reader, tag).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/reader.rs b/der/src/reader.rs index fe77d0359..aecea7a7b 100644 --- a/der/src/reader.rs +++ b/der/src/reader.rs @@ -118,10 +118,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..66e23ad9c 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -143,14 +143,38 @@ 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_ok() { + if let Ok(tag) = Self::from_der(slice) { + return Ok(Some(tag)); + } + } + } + + Some(Self::from_der(&buf)).transpose() } /// Assert that this [`Tag`] matches the provided expected tag. @@ -174,14 +198,45 @@ 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::N1, + Tag::Integer => TagNumber::N2, + Tag::BitString => TagNumber::N3, + Tag::OctetString => TagNumber::N4, + Tag::Null => TagNumber::N5, + Tag::ObjectIdentifier => TagNumber::N6, + Tag::Real => TagNumber::N9, + Tag::Enumerated => TagNumber::N10, + Tag::Utf8String => TagNumber::N12, + Tag::Sequence => TagNumber::N16, + Tag::Set => TagNumber::N17, + Tag::NumericString => TagNumber::N18, + Tag::PrintableString => TagNumber::N19, + Tag::TeletexString => TagNumber::N20, + Tag::VideotexString => TagNumber::N21, + Tag::Ia5String => TagNumber::N22, + Tag::UtcTime => TagNumber::N23, + Tag::GeneralizedTime => TagNumber::N24, + Tag::VisibleString => TagNumber::N26, + Tag::GeneralString => TagNumber::N27, + Tag::BmpString => TagNumber::N30, + 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 + match self { + Tag::Sequence | Tag::Set => true, + Tag::Application { constructed, .. } + | Tag::ContextSpecific { constructed, .. } + | Tag::Private { constructed, .. } => constructed, + _ => false, + } } /// Is this an application tag? @@ -204,45 +259,6 @@ impl Tag { 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), - } - } - /// Create an [`Error`] for an invalid [`Length`]. pub fn length_error(self) -> Error { ErrorKind::Length { tag: self }.into() @@ -271,85 +287,144 @@ 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()), + }; -impl From for u8 { - fn from(tag: Tag) -> u8 { - tag.octet() + Ok(tag) } } -impl From<&Tag> for u8 { - fn from(tag: &Tag) -> u8 { - u8::from(*tag) +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()))); } -} -impl<'a> Decode<'a> for Tag { - type Error = Error; + let mut multi_byte_tag_number = 0; - fn decode>(reader: &mut R) -> Result { - reader.read_byte().and_then(Self::try_from) + for _ in 0..Tag::MAX_SIZE - 2 { + multi_byte_tag_number <<= 7; + + 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))); + } } + + let byte = reader.read_byte()?; + if multi_byte_tag_number > u16::MAX >> 7 || byte & 0x80 != 0 { + return Err(ErrorKind::TagNumberInvalid.into()); + } + multi_byte_tag_number |= u16::from(byte & 0x7F); + + Ok((constructed, TagNumber::new(multi_byte_tag_number))) } impl Encode for Tag { fn encoded_len(&self) -> Result { - Ok(Length::ONE) + let number = self.number().value(); + + let length = if number <= 30 { + Length::ONE + } else { + Length::new(number.ilog2() as u16 / 7 + 2) + }; + + Ok(length) } fn encode(&self, writer: &mut impl Writer) -> Result<()> { - writer.write_byte(self.into()) + let mut first_byte = self.class() as u8 | u8::from(self.is_constructed()) << 5; + + let number = self.number().value(); + + if number <= 30 { + first_byte |= number as u8; + writer.write_byte(first_byte)?; + } else { + first_byte |= 0x1F; + writer.write_byte(first_byte)?; + + let extra_bytes = number.ilog2() as u16 / 7 + 1; + + for shift in (0..extra_bytes).rev() { + let mut byte = (number >> (shift * 7)) as u8 & 0x7f; + + if shift != 0 { + byte |= 0x80; + } + + writer.write_byte(byte)?; + } + } + + Ok(()) } } 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,7 +487,7 @@ 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) + write!(f, "Tag(0x{:02x}: {})", self.number().value(), self) } } diff --git a/der/src/tag/class.rs b/der/src/tag/class.rs index ffb2a1e75..2a3bba533 100644 --- a/der/src/tag/class.rs +++ b/der/src/tag/class.rs @@ -1,6 +1,5 @@ //! Class of an ASN.1 tag. -use super::{TagNumber, CONSTRUCTED_FLAG}; use core::fmt; /// Class of an ASN.1 tag. @@ -30,14 +29,6 @@ pub enum Class { Private = 0b11000000, } -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 fmt::Display for Class { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs index dfff9a961..133e19248 100644 --- a/der/src/tag/number.rs +++ b/der/src/tag/number.rs @@ -1,25 +1,24 @@ //! 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` @@ -118,20 +117,9 @@ impl TagNumber { /// 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 +147,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) diff --git a/der_derive/src/attributes.rs b/der_derive/src/attributes.rs index fa050cbcb..099b0dc2d 100644 --- a/der_derive/src/attributes.rs +++ b/der_derive/src/attributes.rs @@ -60,7 +60,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 @@ -94,7 +94,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; @@ -107,11 +107,24 @@ 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 (`context_specific`, `private`)" + ); } - context_specific = Some(tag_number); + class = Some(Class::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 (`context_specific`, `private`)" + ); + } + + class = Some(Class::Private(tag_number)); // `default` attribute } else if attr.parse_value::("default")?.is_some() { if default.is_some() { @@ -170,7 +183,7 @@ impl FieldAttrs { Ok(Self { asn1_type, - context_specific, + class, default, extensible: extensible.unwrap_or_default(), optional: optional.unwrap_or_default(), @@ -181,11 +194,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)), @@ -199,22 +209,27 @@ 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 { + if let Some(ref class) = self.class { let type_params = self.asn1_type.map(|ty| ty.type_path()).unwrap_or_default(); - let tag_number = tag_number.to_tokens(); + let ClassTokens { + tag_type, + tag_number, + class_type, + .. + } = class.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( + #class_type::<#type_params>::decode_explicit( reader, #tag_number )? } } else { quote! { - match ::der::asn1::ContextSpecific::<#type_params>::decode(reader)? { + match #class_type::<#type_params>::decode(reader)? { field if field.tag_number == #tag_number => Some(field), _ => None } @@ -223,7 +238,7 @@ impl FieldAttrs { } TagMode::Implicit => { quote! { - ::der::asn1::ContextSpecific::<#type_params>::decode_implicit( + #class_type::<#type_params>::decode_implicit( reader, #tag_number )? @@ -242,7 +257,7 @@ impl FieldAttrs { let constructed = self.constructed; quote! { #context_specific.ok_or_else(|| { - der::Tag::ContextSpecific { + #tag_type { number: #tag_number, constructed: #constructed }.value_error() @@ -265,12 +280,17 @@ 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(); + match self.class { + Some(ref class) => { + let ClassTokens { + tag_number, + ref_type, + .. + } = class.to_tokens(); let tag_mode = self.tag_mode.to_tokens(); + quote! { - ::der::asn1::ContextSpecificRef { + #ref_type { tag_number: #tag_number, tag_mode: #tag_mode, value: #binding, @@ -360,3 +380,48 @@ impl AttrNameValue { }) } } + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) enum Class { + ContextSpecific(TagNumber), + Private(TagNumber), +} + +pub(crate) struct ClassTokens { + pub tag_type: TokenStream, + pub tag_number: TokenStream, + pub class_type: TokenStream, + pub ref_type: TokenStream, +} + +impl Class { + pub fn to_tokens(&self) -> ClassTokens { + match self { + Self::ContextSpecific(tag_number) => ClassTokens { + tag_type: quote!(::der::Tag::ContextSpecific), + tag_number: tag_number.to_tokens(), + class_type: quote!(::der::asn1::ContextSpecific), + ref_type: quote!(::der::asn1::ContextSpecificRef), + }, + Self::Private(tag_number) => ClassTokens { + tag_type: quote!(::der::Tag::Private), + tag_number: tag_number.to_tokens(), + class_type: quote!(::der::asn1::Private), + ref_type: quote!(::der::asn1::PrivateRef), + }, + } + } + + pub fn get_tag(&self, constructed: bool) -> Tag { + match self { + Class::ContextSpecific(number) => Tag::ContextSpecific { + constructed, + number: *number, + }, + Class::Private(number) => Tag::Private { + constructed, + number: *number, + }, + } + } +} diff --git a/der_derive/src/choice.rs b/der_derive/src/choice.rs index 8683c6441..cab0ff3fb 100644 --- a/der_derive/src/choice.rs +++ b/der_derive/src/choice.rs @@ -136,7 +136,7 @@ impl DeriveChoice { #[allow(clippy::unwrap_used)] mod tests { use super::DeriveChoice; - use crate::{Asn1Type, Tag, TagMode}; + use crate::{attributes::Class, Asn1Type, Tag, TagMode}; use syn::parse_quote; /// Based on `Time` as defined in RFC 5280: @@ -167,7 +167,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)); @@ -177,7 +177,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)); } @@ -211,8 +211,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(Class::ContextSpecific("0".parse().unwrap())) ); assert_eq!(bit_string.attrs.tag_mode, TagMode::Implicit); assert_eq!( @@ -226,7 +226,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(Class::ContextSpecific("1".parse().unwrap())) + ); assert_eq!(time.attrs.tag_mode, TagMode::Implicit); assert_eq!( time.tag, @@ -240,8 +243,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(Class::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 c11ecbf36..fae7a75c8 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,13 +123,17 @@ impl ChoiceVariant { pub(super) fn to_value_len_tokens(&self) -> TokenStream { let ident = &self.ident; - match self.attrs.context_specific { - Some(tag_number) => { - let tag_number = tag_number.to_tokens(); + match self.attrs.class { + Some(ref class) => { + let ClassTokens { + tag_number, + ref_type, + .. + } = class.to_tokens(); let tag_mode = self.attrs.tag_mode.to_tokens(); quote! { - Self::#ident(variant) => ::der::asn1::ContextSpecificRef { + Self::#ident(variant) => #ref_type { tag_number: #tag_number, tag_mode: #tag_mode, value: variant, @@ -154,7 +158,10 @@ impl ChoiceVariant { #[cfg(test)] mod tests { use super::ChoiceVariant; - use crate::{choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, TagNumber}; + use crate::{ + attributes::Class, choice::variant::TagOrPath, Asn1Type, FieldAttrs, Tag, TagMode, + TagNumber, + }; use proc_macro2::Span; use quote::quote; use syn::Ident; @@ -254,7 +261,7 @@ mod tests { let ident = Ident::new("ExplicitVariant", Span::call_site()); let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class: Some(Class::ContextSpecific(TagNumber(tag_number))), ..Default::default() }; assert_eq!(attrs.tag_mode, TagMode::Explicit); @@ -339,7 +346,7 @@ mod tests { let attrs = FieldAttrs { constructed, - context_specific: Some(TagNumber(tag_number)), + class: Some(Class::ContextSpecific(TagNumber(tag_number))), tag_mode: TagMode::Implicit, ..Default::default() }; diff --git a/der_derive/src/sequence.rs b/der_derive/src/sequence.rs index 81ca3d729..106e465bd 100644 --- a/der_derive/src/sequence.rs +++ b/der_derive/src/sequence.rs @@ -132,7 +132,7 @@ impl DeriveSequence { #[allow(clippy::bool_assert_comparison)] mod tests { use super::DeriveSequence; - use crate::{Asn1Type, TagMode}; + use crate::{attributes::Class, Asn1Type, TagMode}; use syn::parse_quote; /// X.509 SPKI `AlgorithmIdentifier`. @@ -157,13 +157,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); } @@ -191,7 +191,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]; @@ -200,7 +200,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); } @@ -259,7 +259,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); @@ -267,7 +267,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); @@ -278,7 +278,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); @@ -287,8 +287,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(Class::ContextSpecific("0".parse().unwrap())) ); assert_eq!(attributes_field.attrs.extensible, true); assert_eq!(attributes_field.attrs.optional, true); @@ -298,8 +298,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(Class::ContextSpecific("1".parse().unwrap())) ); assert_eq!(public_key_field.attrs.extensible, true); assert_eq!(public_key_field.attrs.optional, true); @@ -335,23 +335,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(Class::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(Class::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(Class::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..f87c07908 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::{Class, 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,22 +207,21 @@ 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: &Class, tag_mode: &TagMode, optional: bool) { let encoder = &self.encoder; - let number_tokens = tag_number.to_tokens(); let mode_tokens = tag_mode.to_tokens(); + let ClassTokens { + tag_number, + ref_type, + .. + } = class.to_tokens(); if optional { self.encoder = quote! { #encoder.as_ref().map(|field| { - ::der::asn1::ContextSpecificRef { - tag_number: #number_tokens, + #ref_type { + tag_number: #tag_number, tag_mode: #mode_tokens, value: field, } @@ -227,8 +229,8 @@ impl LowerFieldEncoder { }; } else { self.encoder = quote! { - ::der::asn1::ContextSpecificRef { - tag_number: #number_tokens, + #ref_type { + tag_number: #tag_number, tag_mode: #mode_tokens, value: &#encoder, } @@ -240,7 +242,7 @@ impl LowerFieldEncoder { #[cfg(test)] mod tests { use super::SequenceField; - use crate::{FieldAttrs, TagMode, TagNumber}; + use crate::{attributes::Class, FieldAttrs, TagMode, TagNumber}; use proc_macro2::Span; use quote::quote; use syn::{punctuated::Punctuated, Ident, Path, PathSegment, Type, TypePath}; @@ -269,7 +271,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: None, + class: None, default: None, extensible: false, optional: false, @@ -309,7 +311,7 @@ mod tests { let attrs = FieldAttrs { asn1_type: None, - context_specific: Some(TagNumber(0)), + class: Some(Class::ContextSpecific(TagNumber(0))), default: None, extensible: false, optional: false, diff --git a/der_derive/src/tag.rs b/der_derive/src/tag.rs index aab2899b5..7b307b2a4 100644 --- a/der_derive/src/tag.rs +++ b/der_derive/src/tag.rs @@ -22,6 +22,15 @@ pub(crate) enum Tag { /// Context-specific tag number number: TagNumber, }, + + /// Private tags with an associated [`TagNumber`]. + Private { + /// Is the inner ASN.1 type constructed? + constructed: bool, + + /// Private tag number + number: TagNumber, + }, } impl Tag { @@ -33,12 +42,6 @@ impl Tag { constructed, number, } => { - let constructed = if constructed { - quote!(true) - } else { - quote!(false) - }; - let number = number.to_tokens(); quote! { @@ -48,6 +51,19 @@ impl Tag { } } } + Tag::Private { + constructed, + number, + } => { + let number = number.to_tokens(); + + quote! { + ::der::Tag::Private { + constructed: #constructed, + number: #number, + } + } + } } } } @@ -101,48 +117,13 @@ 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)) } } @@ -150,13 +131,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/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/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..8454abac1 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;