From 612109e8547e0e240474df00f2d9287dd882a8a9 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Fri, 28 Mar 2025 16:04:07 +0100 Subject: [PATCH] der: add any_custom_class.rs --- der/src/asn1.rs | 2 + der/src/asn1/any_custom_class.rs | 252 +++++++++++++++++++++++++++++++ der/src/tag/number.rs | 17 ++- 3 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 der/src/asn1/any_custom_class.rs diff --git a/der/src/asn1.rs b/der/src/asn1.rs index 3bbd0a0f6..62897169e 100644 --- a/der/src/asn1.rs +++ b/der/src/asn1.rs @@ -5,6 +5,7 @@ mod internal_macros; mod any; +mod any_custom_class; pub(crate) mod bit_string; #[cfg(feature = "alloc")] mod bmp_string; @@ -32,6 +33,7 @@ mod videotex_string; pub use self::{ any::AnyRef, + any_custom_class::{AnyCustomClassExplicit, AnyCustomClassImplicit}, bit_string::{BitStringIter, BitStringRef}, choice::Choice, context_specific::{ContextSpecific, ContextSpecificRef}, diff --git a/der/src/asn1/any_custom_class.rs b/der/src/asn1/any_custom_class.rs new file mode 100644 index 000000000..94eecd3f2 --- /dev/null +++ b/der/src/asn1/any_custom_class.rs @@ -0,0 +1,252 @@ +//! 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 peek_decode_optional>( + 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 = tag_number.custom_class(class, true); + Err(any_explicit.tag().unexpected_error(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 peek_decode_optional>( + class: Class, + tag_number: TagNumber, + constructed: bool, + reader: &mut R, + ) -> Result, T::Error> { + peek_decode_optional::<_, _, T::Error, _>(reader, class, tag_number, |reader| { + Self::decode_checked(class, tag_number, constructed, 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, + constructed: bool, + reader: &mut R, + ) -> Result { + let any_implicit = Self::decode(reader)?; + if any_implicit.class == class + && any_implicit.tag_number == tag_number + && any_implicit.constructed == constructed + { + Ok(any_implicit) + } else { + let expected = tag_number.custom_class(class, any_implicit.constructed); + Err(any_implicit.tag().unexpected_error(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 { + self.tag_number + .custom_class(self.class, true) + .expect("class not to be Universal") + } +} + +impl Tagged for AnyCustomClassImplicit { + fn tag(&self) -> Tag { + self.tag_number + .custom_class(self.class, self.constructed) + .expect("class not to be Universal") + } +} + +/// 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, + } +} diff --git a/der/src/tag/number.rs b/der/src/tag/number.rs index ae5b5cb18..a7c3acc6c 100644 --- a/der/src/tag/number.rs +++ b/der/src/tag/number.rs @@ -1,6 +1,6 @@ //! ASN.1 tag numbers -use super::Tag; +use super::{Class, Tag}; use core::fmt; /// ASN.1 tag numbers (i.e. lower 5 bits of a [`Tag`]). @@ -33,7 +33,7 @@ impl TagNumber { } /// Create an `APPLICATION` tag with this tag number. - pub fn application(self, constructed: bool) -> Tag { + pub const fn application(self, constructed: bool) -> Tag { Tag::Application { constructed, number: self, @@ -41,7 +41,7 @@ impl TagNumber { } /// Create a `CONTEXT-SPECIFIC` tag with this tag number. - pub fn context_specific(self, constructed: bool) -> Tag { + pub const fn context_specific(self, constructed: bool) -> Tag { Tag::ContextSpecific { constructed, number: self, @@ -49,12 +49,21 @@ impl TagNumber { } /// Create a `PRIVATE` tag with this tag number. - pub fn private(self, constructed: bool) -> Tag { + pub const fn private(self, constructed: bool) -> Tag { Tag::Private { constructed, number: self, } } + /// Create a `APPLICATION`, `CONTEXT-SPECIFIC` or `PRIVATE` tag with this tag number. + pub const fn custom_class(self, class: Class, constructed: bool) -> Option { + match class { + Class::Universal => None, + Class::Application => Some(self.application(constructed)), + Class::ContextSpecific => Some(self.context_specific(constructed)), + Class::Private => Some(self.private(constructed)), + } + } /// Get the inner value. pub fn value(self) -> u32 {