diff options
Diffstat (limited to 'vendor/der/src/asn1/context_specific.rs')
-rw-r--r-- | vendor/der/src/asn1/context_specific.rs | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/vendor/der/src/asn1/context_specific.rs b/vendor/der/src/asn1/context_specific.rs new file mode 100644 index 0000000..101ddf0 --- /dev/null +++ b/vendor/der/src/asn1/context_specific.rs @@ -0,0 +1,354 @@ +//! Context-specific field. + +use crate::{ + asn1::AnyRef, Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, + Header, Length, Reader, Result, 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<T> { + /// 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<T> ContextSpecific<T> { + /// 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<Option<Self>> + 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<Option<Self>> + where + T: DecodeValue<'a> + Tagged, + { + Self::decode_with(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()); + } + + 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>>( + reader: &mut R, + tag_number: TagNumber, + f: F, + ) -> Result<Option<Self>> + where + F: FnOnce(&mut R) -> Result<Self>, + { + 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<T> +where + T: Decode<'a> + Tagged, +{ + fn can_decode(tag: Tag) -> bool { + tag.is_context_specific() + } +} + +impl<'a, T> Decode<'a> for ContextSpecific<T> +where + T: Decode<'a>, +{ + fn decode<R: Reader<'a>>(reader: &mut R) -> Result<Self> { + 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)), + } + } +} + +impl<T> EncodeValue for ContextSpecific<T> +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result<Length> { + 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<()> { + match self.tag_mode { + TagMode::Explicit => self.value.encode(writer), + TagMode::Implicit => self.value.encode_value(writer), + } + } +} + +impl<T> Tagged for ContextSpecific<T> +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<AnyRef<'a>> for ContextSpecific<T> +where + T: Decode<'a>, +{ + type Error = Error; + + fn try_from(any: AnyRef<'a>) -> Result<ContextSpecific<T>> { + 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)), + } + } +} + +impl<T> ValueOrd for ContextSpecific<T> +where + T: EncodeValue + ValueOrd + Tagged, +{ + fn value_cmp(&self, other: &Self) -> Result<Ordering> { + 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, + + /// Tag mode: `EXPLICIT` VS `IMPLICIT`. + pub tag_mode: TagMode, + + /// Value of the field. + pub value: &'a T, +} + +impl<'a, T> ContextSpecificRef<'a, T> { + /// Convert to a [`ContextSpecific`]. + fn encoder(&self) -> ContextSpecific<EncodeValueRef<'a, T>> { + ContextSpecific { + tag_number: self.tag_number, + tag_mode: self.tag_mode, + value: EncodeValueRef(self.value), + } + } +} + +impl<'a, T> EncodeValue for ContextSpecificRef<'a, T> +where + T: EncodeValue + Tagged, +{ + fn value_len(&self) -> Result<Length> { + self.encoder().value_len() + } + + fn encode_value(&self, writer: &mut impl Writer) -> Result<()> { + self.encoder().encode_value(writer) + } +} + +impl<'a, T> Tagged for ContextSpecificRef<'a, T> +where + T: Tagged, +{ + fn tag(&self) -> Tag { + self.encoder().tag() + } +} + +#[cfg(test)] +mod tests { + use super::ContextSpecific; + 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 = ContextSpecific::<BitStringRef<'_>>::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 context_specific_with_explicit_field() { + let tag_number = TagNumber::new(0); + + // Empty message + let mut reader = SliceReader::new(&[]).unwrap(); + assert_eq!( + ContextSpecific::<u8>::decode_explicit(&mut reader, tag_number).unwrap(), + None + ); + + // Message containing a non-context-specific type + let mut reader = SliceReader::new(&hex!("020100")).unwrap(); + assert_eq!( + ContextSpecific::<u8>::decode_explicit(&mut reader, tag_number).unwrap(), + None + ); + + // Message containing an EXPLICIT context-specific field + let mut reader = SliceReader::new(&hex!("A003020100")).unwrap(); + let field = ContextSpecific::<u8>::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 context_specific_with_implicit_field() { + // From RFC8410 Section 10.3: + // <https://datatracker.ietf.org/doc/html/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 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::<BitStringRef<'_>>::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(), + &context_specific_implicit_bytes[3..] + ); + } + + #[test] + fn context_specific_skipping_unknown_field() { + let tag = TagNumber::new(1); + let mut reader = SliceReader::new(&hex!("A003020100A103020101")).unwrap(); + let field = ContextSpecific::<u8>::decode_explicit(&mut reader, tag) + .unwrap() + .unwrap(); + assert_eq!(field.value, 1); + } + + #[test] + fn context_specific_returns_none_on_greater_tag_number() { + let tag = TagNumber::new(0); + let mut reader = SliceReader::new(&hex!("A103020101")).unwrap(); + assert_eq!( + ContextSpecific::<u8>::decode_explicit(&mut reader, tag).unwrap(), + None + ); + } +} |