From fde7eec824f653d919df6a1f7b7264c7fa9196a8 Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:35:26 +0100 Subject: [PATCH 1/2] der: add `IsConstructed` trait, impl'ed on any `FixedTag` der_derive: impl IsConstructed on derive(Choice) der: draft of test for IsConstructed vs Tagged edge case der: test: generic CHOICE inside [0] IMPLICIT CHOICE Revert "der: test: generic CHOICE inside [0] IMPLICIT CHOICE" This reverts commit dda121528bed8c94f4274047cb5a087b335d5df1. Revert "der: draft of test for IsConstructed vs Tagged edge case" This reverts commit 5daa9ba53e03ec363f2e3846948ff3b4da09c296. der: add IsConstructed test docs --- der/src/asn1/context_specific.rs | 5 ++- der/src/lib.rs | 2 +- der/src/tag.rs | 13 ++++++- der/tests/derive.rs | 63 ++++++++++++++++++++++++++++++++ der_derive/src/choice.rs | 4 ++ 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/der/src/asn1/context_specific.rs b/der/src/asn1/context_specific.rs index 129995711..55723b156 100644 --- a/der/src/asn1/context_specific.rs +++ b/der/src/asn1/context_specific.rs @@ -3,6 +3,7 @@ use crate::{ Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header, Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef, + tag::IsConstructed, }; use core::cmp::Ordering; @@ -60,13 +61,13 @@ impl ContextSpecific { tag_number: TagNumber, ) -> Result, T::Error> where - T: DecodeValue<'a> + Tagged, + T: DecodeValue<'a> + IsConstructed, { 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() { + if header.tag.is_constructed() != T::CONSTRUCTED { return Err(header.tag.non_canonical_error().into()); } diff --git a/der/src/lib.rs b/der/src/lib.rs index cd0b88cc0..a247b8a56 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -377,7 +377,7 @@ pub use crate::{ length::{IndefiniteLength, Length}, ord::{DerOrd, ValueOrd}, reader::{Reader, slice::SliceReader}, - tag::{Class, FixedTag, Tag, TagMode, TagNumber, Tagged}, + tag::{Class, FixedTag, IsConstructed, Tag, TagMode, TagNumber, Tagged}, writer::{Writer, slice::SliceWriter}, }; diff --git a/der/src/tag.rs b/der/src/tag.rs index da2260d6c..ce374a319 100644 --- a/der/src/tag.rs +++ b/der/src/tag.rs @@ -32,6 +32,17 @@ impl Tagged for T { } } +/// Types which have a constant ASN.1 constructed bit. +pub trait IsConstructed { + /// ASN.1 constructed bit + const CONSTRUCTED: bool; +} + +/// Types which are [`FixedTag`] always known if they are constructed (or primitive). +impl IsConstructed for T { + const CONSTRUCTED: bool = T::TAG.is_constructed(); +} + /// ASN.1 tags. /// /// Tags are the leading identifier octet of the Tag-Length-Value encoding @@ -229,7 +240,7 @@ impl Tag { } /// Does this tag represent a constructed (as opposed to primitive) field? - pub fn is_constructed(self) -> bool { + pub const fn is_constructed(self) -> bool { match self { Tag::Sequence | Tag::Set => true, Tag::Application { constructed, .. } diff --git a/der/tests/derive.rs b/der/tests/derive.rs index d2d677071..4009144ad 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -706,6 +706,69 @@ mod decode_value { } } +/// Custom derive test cases for the `DecodeValue` + `EncodeValue` macro combo. +mod decode_encode_value { + use der::{ + Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, IsConstructed, Length, + Sequence, SliceReader, SliceWriter, Tag, TagNumber, + }; + use hex_literal::hex; + + /// Example of a structure, that does not have a tag + #[derive(DecodeValue, EncodeValue, Default, Eq, PartialEq, Debug)] + #[asn1(tag_mode = "IMPLICIT")] + struct DecodeEncodeCheck<'a> { + #[asn1(type = "OCTET STRING", context_specific = "5")] + field: &'a [u8], + } + impl IsConstructed for DecodeEncodeCheck<'_> { + const CONSTRUCTED: bool = true; + } + + // TODO(dishmaker): fix test after IMPLICIT/EXPLICIT trait split + // #[derive(Sequence, Default, Eq, PartialEq, Debug)] + // #[asn1(tag_mode = "IMPLICIT")] + // struct ImplicitWrapper<'a> { + // #[asn1(context_specific = "0")] + // implicit_decode_encode: DecodeEncodeCheck<'a>, + // } + + #[test] + fn sequence_decode_encode_custom_implicit() { + // TODO(dishmaker): fix test after IMPLICIT/EXPLICIT trait split + // let obj = ImplicitWrapper { + // implicit_decode_encode: DecodeEncodeCheck { + // field: &[0x33, 0x44], + // }, + // }; + + // let der_encoded = obj.to_der().unwrap(); + + // assert_eq!(der_encoded, hex!("80 04 85 02 33 44")); + // let obj_decoded = ImplicitWrapper::from_der(&der_encoded); + // assert_eq!(obj, obj_decoded); + + let header = Header { + tag: Tag::ContextSpecific { + constructed: true, + number: TagNumber(0), + }, + length: Length::new(6u16), + }; + let obj = DecodeEncodeCheck { + field: &[0x33, 0x44], + }; + + let mut buf = [0u8; 100]; + let mut writer = SliceWriter::new(&mut buf); + obj.encode_value(&mut writer).unwrap(); + let len = obj.value_len().unwrap(); + + let mut reader = SliceReader::new(&mut buf).unwrap(); + DecodeEncodeCheck::decode_value(&mut reader, header); + } +} + /// Custom derive test cases for the `BitString` macro. #[cfg(feature = "std")] mod bitstring { diff --git a/der_derive/src/choice.rs b/der_derive/src/choice.rs index 4c2b8e28f..6ca474802 100644 --- a/der_derive/src/choice.rs +++ b/der_derive/src/choice.rs @@ -97,6 +97,10 @@ impl DeriveChoice { } } + impl #impl_generics ::der::IsConstructed for #ident #ty_generics #where_clause { + const CONSTRUCTED: bool = true; + } + impl #impl_generics ::der::Decode<#lifetime> for #ident #ty_generics #where_clause { type Error = #error; From 5ee5ef642c1ecb80f41daf5115368916ec6f30fc Mon Sep 17 00:00:00 2001 From: dishmaker <141624503+dishmaker@users.noreply.github.com> Date: Thu, 3 Apr 2025 16:25:03 +0200 Subject: [PATCH 2/2] der: format tests --- der/tests/derive.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/der/tests/derive.rs b/der/tests/derive.rs index 4009144ad..14c579723 100644 --- a/der/tests/derive.rs +++ b/der/tests/derive.rs @@ -112,7 +112,7 @@ mod choice { /// `Choice` with `IMPLICIT` tagging. mod implicit { use der::{ - Choice, Decode, Encode, SliceWriter, + Choice, Decode, Encode, Sequence, SliceWriter, asn1::{BitStringRef, GeneralizedTime}, }; use hex_literal::hex; @@ -179,6 +179,13 @@ mod choice { cs_time.encode(&mut encoder).unwrap(); assert_eq!(TIME_DER, encoder.finish().unwrap()); } + + /// Test case for `CHOICE` inside `[0]` `EXPLICIT` tag in `SEQUENCE`. + #[derive(Sequence, Debug, Eq, PartialEq)] + pub struct ExplicitChoiceInsideSequence<'a> { + #[asn1(tag_mode = "EXPLICIT", context_specific = "0")] + choice_field: ImplicitChoice<'a>, + } } } @@ -709,12 +716,11 @@ mod decode_value { /// Custom derive test cases for the `DecodeValue` + `EncodeValue` macro combo. mod decode_encode_value { use der::{ - Decode, DecodeValue, Encode, EncodeValue, FixedTag, Header, IsConstructed, Length, - Sequence, SliceReader, SliceWriter, Tag, TagNumber, + DecodeValue, EncodeValue, Header, IsConstructed, Length, SliceReader, SliceWriter, Tag, + TagNumber, }; - use hex_literal::hex; - /// Example of a structure, that does not have a tag + /// Example of a structure, that does not have a tag and is not a sequence #[derive(DecodeValue, EncodeValue, Default, Eq, PartialEq, Debug)] #[asn1(tag_mode = "IMPLICIT")] struct DecodeEncodeCheck<'a> { @@ -762,10 +768,9 @@ mod decode_encode_value { let mut buf = [0u8; 100]; let mut writer = SliceWriter::new(&mut buf); obj.encode_value(&mut writer).unwrap(); - let len = obj.value_len().unwrap(); - let mut reader = SliceReader::new(&mut buf).unwrap(); - DecodeEncodeCheck::decode_value(&mut reader, header); + let mut reader = SliceReader::new(&buf).unwrap(); + let _ = DecodeEncodeCheck::decode_value(&mut reader, header); } }