summaryrefslogtreecommitdiffstats
path: root/vendor/der/src/asn1/context_specific.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/der/src/asn1/context_specific.rs')
-rw-r--r--vendor/der/src/asn1/context_specific.rs354
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
+ );
+ }
+}