Skip to content

Commit cb96284

Browse files
authored
der: use strict context-specific skipping condition (equal tag numbers only) (#1740)
1 parent 4a63b69 commit cb96284

File tree

2 files changed

+63
-48
lines changed

2 files changed

+63
-48
lines changed

der/src/asn1/context_specific.rs

Lines changed: 61 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Context-specific field.
22
33
use crate::{
4-
Choice, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
4+
Choice, Class, Decode, DecodeValue, DerOrd, Encode, EncodeValue, EncodeValueRef, Error, Header,
55
Length, Reader, Tag, TagMode, TagNumber, Tagged, ValueOrd, Writer, asn1::AnyRef,
66
tag::IsConstructed,
77
};
@@ -47,7 +47,10 @@ impl<T> ContextSpecific<T> {
4747
where
4848
T: Decode<'a>,
4949
{
50-
Self::decode_with(reader, tag_number, |reader| Self::decode(reader))
50+
if !peek_tag_matches(reader, Class::ContextSpecific, tag_number)? {
51+
return Ok(None);
52+
}
53+
Ok(Some(Self::decode(reader)?))
5154
}
5255

5356
/// Attempt to decode an `IMPLICIT` ASN.1 `CONTEXT-SPECIFIC` field with the
@@ -63,53 +66,54 @@ impl<T> ContextSpecific<T> {
6366
where
6467
T: DecodeValue<'a> + IsConstructed,
6568
{
66-
Self::decode_with::<_, _, T::Error>(reader, tag_number, |reader| {
67-
// Decode IMPLICIT header
68-
let header = Header::decode(reader)?;
69-
70-
// read_nested checks if header matches decoded length
71-
let value = reader.read_nested(header.length, |reader| {
72-
// Decode inner IMPLICIT value
73-
T::decode_value(reader, header)
74-
})?;
75-
76-
if header.tag.is_constructed() != T::CONSTRUCTED {
77-
return Err(header.tag.non_canonical_error().into());
78-
}
79-
80-
Ok(Self {
81-
tag_number,
82-
tag_mode: TagMode::Implicit,
83-
value,
84-
})
85-
})
86-
}
69+
// Peek tag number
70+
if !peek_tag_matches::<_, T::Error>(reader, Class::ContextSpecific, tag_number)? {
71+
return Ok(None);
72+
}
73+
// Decode IMPLICIT header
74+
let header = Header::decode(reader)?;
8775

88-
/// Attempt to decode a context-specific field with the given
89-
/// helper callback.
90-
fn decode_with<'a, F, R: Reader<'a>, E>(
91-
reader: &mut R,
92-
tag_number: TagNumber,
93-
f: F,
94-
) -> Result<Option<Self>, E>
95-
where
96-
F: FnOnce(&mut R) -> Result<Self, E>,
97-
E: From<Error>,
98-
{
99-
while let Some(tag) = Tag::peek_optional(reader)? {
100-
if !tag.is_context_specific() || (tag.number() > tag_number) {
101-
break;
102-
} else if tag.number() == tag_number {
103-
return Some(f(reader)).transpose();
104-
} else {
105-
AnyRef::decode(reader)?;
106-
}
76+
// read_nested checks if header matches decoded length
77+
let value = reader.read_nested(header.length, |reader| {
78+
// Decode inner IMPLICIT value
79+
T::decode_value(reader, header)
80+
})?;
81+
82+
// the encoding shall be constructed if the base encoding is constructed
83+
if header.tag.is_constructed() != T::CONSTRUCTED {
84+
return Err(header.tag.non_canonical_error().into());
10785
}
10886

109-
Ok(None)
87+
Ok(Some(Self {
88+
tag_number,
89+
tag_mode: TagMode::Implicit,
90+
value,
91+
}))
11092
}
11193
}
11294

95+
/// Returns true if given context-specific (or any given class) field
96+
/// should be decoded, based on peeked tag.
97+
fn peek_tag_matches<'a, R: Reader<'a>, E>(
98+
reader: &mut R,
99+
expected_class: Class,
100+
expected_tag_number: TagNumber,
101+
) -> Result<bool, E>
102+
where
103+
E: From<Error>,
104+
{
105+
// Peek tag or ignore end of stream
106+
let Some(tag) = Tag::peek_optional(reader)? else {
107+
return Ok(false);
108+
};
109+
// Ignore tags with different numbers
110+
if tag.class() != expected_class || tag.number() != expected_tag_number {
111+
return Ok(false);
112+
}
113+
// Tag matches
114+
Ok(true)
115+
}
116+
113117
impl<'a, T> Choice<'a> for ContextSpecific<T>
114118
where
115119
T: Decode<'a> + Tagged,
@@ -132,6 +136,7 @@ where
132136
match header.tag {
133137
Tag::ContextSpecific {
134138
number,
139+
// encoding shall be constructed
135140
constructed: true,
136141
} => Ok(Self {
137142
tag_number: number,
@@ -171,7 +176,17 @@ where
171176
{
172177
fn tag(&self) -> Tag {
173178
let constructed = match self.tag_mode {
179+
// ISO/IEC 8825-1:2021
180+
// 8.14.3 If implicit tagging (see Rec. ITU-T X.680 | ISO/IEC 8824-1, 31.2.7) was not used in the definition of the type, the
181+
// encoding shall be constructed and the contents octets shall be the complete base encoding [Encode].
174182
TagMode::Explicit => true,
183+
184+
// ISO/IEC 8825-1:2021
185+
// 8.14.4 If implicit tagging was used in the definition of the type, then:
186+
// a) the encoding shall be constructed if the base encoding is constructed, and shall be primitive otherwise; and
187+
// b) the contents octets shall be the same as the contents octets [EncodeValue] of the base encoding.
188+
//
189+
// TODO(dishmaker): use IsConstructed trait for IMPLICIT
175190
TagMode::Implicit => self.value.tag().is_constructed(),
176191
};
177192

@@ -349,13 +364,11 @@ mod tests {
349364
}
350365

351366
#[test]
352-
fn context_specific_skipping_unknown_field() {
367+
fn context_specific_not_skipping_unknown_field() {
353368
let tag = TagNumber(1);
354369
let mut reader = SliceReader::new(&hex!("A003020100A103020101")).unwrap();
355-
let field = ContextSpecific::<u8>::decode_explicit(&mut reader, tag)
356-
.unwrap()
357-
.unwrap();
358-
assert_eq!(field.value, 1);
370+
let field = ContextSpecific::<u8>::decode_explicit(&mut reader, tag).unwrap();
371+
assert_eq!(field, None);
359372
}
360373

361374
#[test]

der/src/tag.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ impl<T: FixedTag + ?Sized> Tagged for T {
3333
}
3434

3535
/// Types which have a constant ASN.1 constructed bit.
36+
///
37+
/// Auto-implemented on all types that implement [`FixedTag`].
3638
pub trait IsConstructed {
3739
/// ASN.1 constructed bit
3840
const CONSTRUCTED: bool;

0 commit comments

Comments
 (0)