Skip to content

Commit 94e1198

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 94e1198

File tree

5 files changed

+81
-19
lines changed

5 files changed

+81
-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: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ pub use self::allocating::OctetString;
159159
#[cfg(feature = "alloc")]
160160
mod allocating {
161161
use super::*;
162-
use crate::{BytesOwned, referenced::*};
162+
use crate::{referenced::*, BytesOwned};
163163
use alloc::{borrow::Cow, boxed::Box, vec::Vec};
164164

165165
/// ASN.1 `OCTET STRING` type: owned form.
@@ -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: 18 additions & 7 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};
@@ -152,11 +152,22 @@ pub(crate) mod allocating {
152152
inner_tag: Tag,
153153
) -> Result<Self> {
154154
// 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)?);
155+
if header.is_constructed() {
156+
if header.length.is_indefinite() && reader.encoding_rules() == EncodingRules::Ber {
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: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct Header {
1313

1414
/// Length of the encoded value
1515
pub length: Length,
16+
17+
constructed: bool,
1618
}
1719

1820
impl Header {
@@ -21,7 +23,22 @@ impl Header {
2123
/// Returns an error if the length exceeds the limits of [`Length`].
2224
pub fn new(tag: Tag, length: impl TryInto<Length>) -> Result<Self> {
2325
let length = length.try_into().map_err(|_| ErrorKind::Overflow)?;
24-
Ok(Self { tag, length })
26+
Ok(Self { tag, length , constructed: length.is_indefinite() })
27+
}
28+
29+
/// TODO.
30+
pub fn copy_with_length(&self, length: impl TryInto<Length>) -> Result<Self> {
31+
let length = length.try_into().map_err(|_| ErrorKind::Overflow)?;
32+
Ok(Self {
33+
tag: self.tag,
34+
length,
35+
constructed: self.constructed
36+
})
37+
}
38+
39+
/// TODO.
40+
pub fn is_constructed(&self) -> bool {
41+
self.constructed
2542
}
2643

2744
/// Peek forward in the reader, attempting to decode a [`Header`] at the current position.
@@ -51,7 +68,7 @@ impl<'a> Decode<'a> for Header {
5168
return Err(reader.error(ErrorKind::IndefiniteLength));
5269
}
5370

54-
Ok(Self { tag, length })
71+
Ok(Self { tag, length, constructed: is_constructed })
5572
}
5673
}
5774

0 commit comments

Comments
 (0)