Skip to content

Commit ae36061

Browse files
authored
der: (re-)add Reader::read_value with EOC support (#1895)
One of the blockers for addressing indefinite length handling (#779) has been where to consume the EOC tag. This commit (re)introduces `Reader::read_value` (added in #1877, removed in #1887) and handles decoding the EOC there. This gives us a single place where EOC can be handled for all constructed messages. With these changes, the decoder is able to parse `cms_ber.bin` from `cms/tests`, from which the `cms_der.bin` file has been translated. This file provides a real-world example of nested indefinite lengths from CMS. Note, however, that the example contains a constructed `Any` which isn't yet being correctly handled. A `TODO` for ensuring the BER and DER decode identically has been added.
1 parent b288a61 commit ae36061

5 files changed

Lines changed: 80 additions & 28 deletions

File tree

cms/tests/tests_from_pkcs7_crate.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,14 @@ fn cms_decode_signed_der() {
141141
// should match the original
142142
assert_eq!(reencoded_der_signed_data_in_ci, der_signed_data_in_ci)
143143
}
144+
145+
#[test]
146+
fn cms_decode_signed_ber() {
147+
let cms_ber = include_bytes!("../tests/examples/cms_ber.bin");
148+
let _ci_ber = ContentInfo::from_ber(cms_ber).unwrap();
149+
150+
// TODO(tarcieri): ensure BER and DER decode identically
151+
// let cms_der = include_bytes!("../tests/examples/cms_der.bin");
152+
// let ci_der = ContentInfo::from_der(cms_der).unwrap();
153+
// assert_eq!(ci_ber, ci_der);
154+
}

der/src/asn1/internal_macros.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ macro_rules! impl_custom_class {
151151
return Err(reader.error(header.tag.non_canonical_error()).into());
152152
}
153153

154-
// read_nested checks if header matches decoded length
155-
let value = reader.read_nested(header.length, |reader| {
154+
// read_value checks if header matches decoded length
155+
let value = reader.read_value(header, |reader| {
156156
// Decode inner IMPLICIT value
157157
T::decode_value(reader, header)
158158
})?;
@@ -192,7 +192,7 @@ macro_rules! impl_custom_class {
192192
Tag::$class_enum_name { number, .. } => Ok(Self {
193193
tag_number: number,
194194
tag_mode: TagMode::default(),
195-
value: reader.read_nested(header.length, |reader| {
195+
value: reader.read_value(header, |reader| {
196196
// Decode inner tag-length-value of EXPLICIT
197197
T::decode(reader)
198198
})?,

der/src/decode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ where
6868
fn decode<R: Reader<'a>>(reader: &mut R) -> Result<T, <T as DecodeValue<'a>>::Error> {
6969
let header = Header::decode(reader)?;
7070
header.tag.assert_eq(T::TAG)?;
71-
reader.read_nested(header.length, |r| T::decode_value(r, header))
71+
reader.read_value(header, |r| T::decode_value(r, header))
7272
}
7373
}
7474

der/src/length.rs

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ impl Length {
4040
/// Maximum length (`u32::MAX`).
4141
pub const MAX: Self = Self::new(u32::MAX);
4242

43+
/// Length of end-of-content octets (i.e. `00 00`).
44+
pub(crate) const EOC_LEN: Self = Self::new(2);
45+
4346
/// Maximum number of octets in a DER encoding of a [`Length`] using the
4447
/// rules implemented by this crate.
4548
pub(crate) const MAX_SIZE: usize = 5;
@@ -92,6 +95,26 @@ impl Length {
9295
Self::new(self.inner.saturating_sub(rhs.inner))
9396
}
9497

98+
/// If the length is indefinite, compute a length with the EOC marker removed
99+
/// (i.e. the final two bytes `00 00`).
100+
///
101+
/// Otherwise (as should always be the case with DER), the length is unchanged.
102+
///
103+
/// This method notably preserves the `indefinite` flag when performing arithmetic.
104+
pub(crate) fn sans_eoc(self) -> Self {
105+
if self.indefinite {
106+
// We expect EOC to be present when this is called.
107+
debug_assert!(self >= Self::EOC_LEN);
108+
109+
Self {
110+
inner: self.saturating_sub(Self::EOC_LEN).inner,
111+
indefinite: true,
112+
}
113+
} else {
114+
self
115+
}
116+
}
117+
95118
/// Get initial octet of the encoded length (if one is required).
96119
///
97120
/// From X.690 Section 8.1.3.5:
@@ -379,22 +402,12 @@ fn decode_indefinite_length<'a, R: Reader<'a>>(reader: &mut R) -> Result<Length>
379402
let start_pos = reader.position();
380403

381404
loop {
382-
let current_pos = reader.position();
383-
384405
// Look for the end-of-contents marker
385406
if reader.peek_byte() == Some(EOC_TAG) {
386-
// Drain the end-of-contents tag
387-
reader.drain(Length::ONE)?;
388-
389-
// Read the length byte and ensure it's zero (i.e. the full EOC is `00 00`)
390-
let length_byte = reader.read_byte()?;
391-
392-
if length_byte != 0 {
393-
return Err(reader.error(ErrorKind::IndefiniteLength));
394-
}
407+
read_eoc(reader)?;
395408

396409
// Compute how much we read and flag the decoded length as indefinite
397-
let mut ret = (current_pos - start_pos)?;
410+
let mut ret = (reader.position() - start_pos)?;
398411
ret.indefinite = true;
399412
return Ok(ret);
400413
}
@@ -404,6 +417,21 @@ fn decode_indefinite_length<'a, R: Reader<'a>>(reader: &mut R) -> Result<Length>
404417
}
405418
}
406419

420+
/// Read an expected end-of-contents (EOC) marker: `00 00`.
421+
///
422+
/// # Errors
423+
///
424+
/// - Returns `ErrorKind::IndefiniteLength` if the EOC marker isn't present as expected.
425+
pub(crate) fn read_eoc<'a>(reader: &mut impl Reader<'a>) -> Result<()> {
426+
for _ in 0..Length::EOC_LEN.inner as usize {
427+
if reader.read_byte()? != 0 {
428+
return Err(reader.error(ErrorKind::IndefiniteLength));
429+
}
430+
}
431+
432+
Ok(())
433+
}
434+
407435
#[cfg(test)]
408436
#[allow(clippy::unwrap_used)]
409437
mod tests {
@@ -507,9 +535,6 @@ mod tests {
507535
/// Length of example in octets.
508536
const EXAMPLE_LEN: usize = 68;
509537

510-
/// Length of end-of-content octets (i.e. `00 00`).
511-
const EOC_LEN: usize = 2;
512-
513538
/// Test vector from: <https://github.com/RustCrypto/formats/issues/779#issuecomment-2902948789>
514539
///
515540
/// Notably this example contains nested indefinite lengths to ensure the decoder handles
@@ -534,18 +559,15 @@ mod tests {
534559

535560
// Decode indefinite length
536561
let length = Length::decode(&mut reader).unwrap();
537-
assert!(length.indefinite);
562+
assert!(length.is_indefinite());
538563

539564
// Decoding the length should leave the position at the end of the indefinite length octet
540565
let pos = usize::try_from(reader.position()).unwrap();
541566
assert_eq!(pos, 2);
542567

543568
// The first two bytes are the header and the rest is the length of the message.
544569
// The last four are two end-of-content markers (2 * 2 bytes).
545-
assert_eq!(
546-
usize::try_from(length).unwrap(),
547-
EXAMPLE_LEN - pos - (EOC_LEN * 2)
548-
);
570+
assert_eq!(usize::try_from(length).unwrap(), EXAMPLE_LEN - pos);
549571

550572
// Read OID
551573
reader.tlv_bytes().unwrap();
@@ -564,7 +586,7 @@ mod tests {
564586

565587
// Parse the inner indefinite length
566588
let length = Length::decode(&mut reader).unwrap();
567-
assert!(length.indefinite);
568-
assert_eq!(usize::try_from(length).unwrap(), 18);
589+
assert!(length.is_indefinite());
590+
assert_eq!(usize::try_from(length).unwrap(), 20);
569591
}
570592
}

der/src/reader.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod position;
99

1010
use crate::{
1111
Decode, DecodeValue, Encode, EncodingRules, Error, ErrorKind, FixedTag, Header, Length, Tag,
12-
TagMode, TagNumber, asn1::ContextSpecific,
12+
TagMode, TagNumber, asn1::ContextSpecific, length::read_eoc,
1313
};
1414

1515
#[cfg(feature = "alloc")]
@@ -32,6 +32,25 @@ pub trait Reader<'r>: Clone {
3232
E: From<Error>,
3333
F: FnOnce(&mut Self) -> Result<T, E>;
3434

35+
/// Read a value (i.e. the "V" part of a "TLV" field) using the provided header.
36+
///
37+
/// This calls the provided function `f` with a nested reader created using
38+
/// [`Reader::read_nested`].
39+
fn read_value<T, F, E>(&mut self, header: Header, f: F) -> Result<T, E>
40+
where
41+
E: From<Error>,
42+
F: FnOnce(&mut Self) -> Result<T, E>,
43+
{
44+
let ret = self.read_nested(header.length.sans_eoc(), f)?;
45+
46+
// Consume EOC marker if the length is indefinite.
47+
if header.length.is_indefinite() {
48+
read_eoc(self)?;
49+
}
50+
51+
Ok(ret)
52+
}
53+
3554
/// Attempt to read data borrowed directly from the input as a slice,
3655
/// updating the internal cursor position.
3756
///
@@ -192,7 +211,7 @@ pub trait Reader<'r>: Clone {
192211
{
193212
let header = Header::decode(self)?;
194213
header.tag.assert_eq(Tag::Sequence)?;
195-
self.read_nested(header.length, f)
214+
self.read_value(header, f)
196215
}
197216

198217
/// Obtain a slice of bytes containing a complete TLV production suitable for parsing later.

0 commit comments

Comments
 (0)