Skip to content

Commit 544e397

Browse files
committed
Negative test case for constructed, definite-length octet string
Ensure octet strings with constructed, definite-length method return an error, rather than an incorrect decoding.
1 parent d877d76 commit 544e397

File tree

5 files changed

+99
-19
lines changed

5 files changed

+99
-19
lines changed

der/src/asn1/any.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ impl<'a> AnyRef<'a> {
7777
return Err(self.tag.unexpected_error(None).to_error().into());
7878
}
7979

80-
let header = Header {
81-
tag: self.tag,
82-
length: self.value.len(),
83-
};
80+
let header = Header::new(self.tag, self.value.len())?;
8481

8582
let mut decoder = SliceReader::new_with_encoding_rules(self.value(), encoding)?;
8683
let result = T::decode_value(&mut decoder, header)?;

der/src/asn1/bit_string.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,7 @@ impl<'a> DecodeValue<'a> for BitStringRef<'a> {
139139
type Error = Error;
140140

141141
fn decode_value<R: Reader<'a>>(reader: &mut R, header: Header) -> Result<Self> {
142-
let header = Header {
143-
tag: header.tag,
144-
length: (header.length - Length::ONE)?,
145-
};
142+
let header = header.copy_with_length((header.length - Length::ONE)?);
146143

147144
let unused_bits = reader.read_byte()?;
148145
let inner = <&'a BytesRef>::decode_value(reader, header)?;

der/src/asn1/octet_string.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,25 @@ mod tests {
375375

376376
#[test]
377377
#[cfg(feature = "alloc")]
378-
fn decode_ber() {
378+
fn decode_ber_primitive_definite() {
379+
use crate::{Decode, asn1::OctetString};
380+
use hex_literal::hex;
381+
382+
const EXAMPLE: &[u8] = &hex!(
383+
"040c" // primitive definite length OCTET STRING
384+
"48656c6c6f2c20776f726c64" // "Hello, world"
385+
);
386+
387+
let decoded = OctetString::from_ber(EXAMPLE).unwrap();
388+
assert_eq!(decoded.as_bytes(), b"Hello, world");
389+
390+
let decoded = OctetString::from_der(EXAMPLE).unwrap();
391+
assert_eq!(decoded.as_bytes(), b"Hello, world");
392+
}
393+
394+
#[test]
395+
#[cfg(feature = "alloc")]
396+
fn decode_ber_constructed_indefinite() {
379397
use crate::{Decode, asn1::OctetString};
380398
use hex_literal::hex;
381399

@@ -390,6 +408,28 @@ mod tests {
390408
assert_eq!(decoded.as_bytes(), b"Hello, world");
391409
}
392410

411+
#[test]
412+
#[cfg(feature = "alloc")]
413+
fn decode_ber_constructed_definite() {
414+
use crate::{Decode, Error, ErrorKind, Length, Tag, asn1::OctetString};
415+
use hex_literal::hex;
416+
417+
const EXAMPLE_BER: &[u8] = &hex!(
418+
"2410" // Constructed definite length OCTET STRING
419+
"040648656c6c6f2c" // Segment containing "Hello,"
420+
"040620776f726c64" // Segment containing " world"
421+
);
422+
423+
let err = OctetString::from_ber(EXAMPLE_BER).err().unwrap();
424+
let expected = Error::new(
425+
ErrorKind::Noncanonical {
426+
tag: Tag::OctetString,
427+
},
428+
Length::new(1),
429+
);
430+
assert_eq!(expected, err);
431+
}
432+
393433
#[test]
394434
#[cfg(feature = "alloc")]
395435
fn decode_context_specific_ber_explicit() {

der/src/bytes.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ impl<'a> arbitrary::Arbitrary<'a> for &'a BytesRef {
118118
pub(crate) mod allocating {
119119
use super::BytesRef;
120120
use crate::{
121-
DecodeValue, DerOrd, EncodeValue, EncodingRules, Error, Header, Length, Reader, Result,
122-
Tag, Writer, length::indefinite::read_constructed_vec,
121+
DecodeValue, DerOrd, EncodeValue, EncodingRules, Error, ErrorKind, Header, Length, Reader,
122+
Result, Tag, Writer, length::indefinite::read_constructed_vec,
123123
};
124124
use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
125125
use core::{borrow::Borrow, cmp::Ordering, ops::Deref};
@@ -151,12 +151,23 @@ pub(crate) mod allocating {
151151
header: Header,
152152
inner_tag: Tag,
153153
) -> Result<Self> {
154-
// Reassemble indefinite length string types
155-
if reader.encoding_rules() == EncodingRules::Ber
156-
&& header.length.is_indefinite()
157-
&& !inner_tag.is_constructed()
158-
{
159-
return Self::new(read_constructed_vec(reader, header.length, inner_tag)?);
154+
if header.is_constructed() {
155+
if header.length.is_indefinite() && reader.encoding_rules() == EncodingRules::Ber {
156+
// Reassemble indefinite length string types
157+
return Self::new(read_constructed_vec(reader, header.length, inner_tag)?);
158+
} else {
159+
// NOTE:
160+
// constructed strings with definite length unsupported
161+
// See discussion
162+
// - https://github.com/RustCrypto/formats/issues/779#issuecomment-3049869721
163+
//
164+
// NOTE: this repositions the error to be at the end of the header
165+
// rather than at the beginning of the value
166+
return Err(Error::new(
167+
ErrorKind::Noncanonical { tag: header.tag },
168+
reader.position().saturating_sub(Length::ONE),
169+
));
170+
}
160171
}
161172

162173
Self::decode_value(reader, header)

der/src/header.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub struct Header {
1313

1414
/// Length of the encoded value
1515
pub length: Length,
16+
17+
/// True if value is constructed, rather than primitive
18+
pub constructed: bool,
1619
}
1720

1821
impl Header {
@@ -21,7 +24,35 @@ impl Header {
2124
/// Returns an error if the length exceeds the limits of [`Length`].
2225
pub fn new(tag: Tag, length: impl TryInto<Length>) -> Result<Self> {
2326
let length = length.try_into().map_err(|_| ErrorKind::Overflow)?;
24-
Ok(Self { tag, length })
27+
Ok(Self {
28+
tag,
29+
length,
30+
constructed: length.is_indefinite(),
31+
})
32+
}
33+
34+
/// [`Tag`] of this header.
35+
pub fn tag(&self) -> Tag {
36+
self.tag
37+
}
38+
39+
/// [`Length`] of this header.
40+
pub fn length(&self) -> Length {
41+
self.length
42+
}
43+
44+
/// True if the [`Tag`] of this header has its constructed bit set.
45+
pub fn is_constructed(&self) -> bool {
46+
self.constructed
47+
}
48+
49+
/// Copy of header with adjusted length.
50+
pub fn copy_with_length(&self, length: Length) -> Self {
51+
Self {
52+
tag: self.tag,
53+
length,
54+
constructed: self.constructed,
55+
}
2556
}
2657

2758
/// Peek forward in the reader, attempting to decode a [`Header`] at the current position.
@@ -51,7 +82,11 @@ impl<'a> Decode<'a> for Header {
5182
return Err(reader.error(ErrorKind::IndefiniteLength));
5283
}
5384

54-
Ok(Self { tag, length })
85+
Ok(Self {
86+
tag,
87+
length,
88+
constructed: is_constructed,
89+
})
5590
}
5691
}
5792

0 commit comments

Comments
 (0)