Skip to content

Commit 759d6f1

Browse files
committed
der: (re-)add Reader::read_value with EOC support
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 4dff2bb commit 759d6f1

3 files changed

Lines changed: 63 additions & 24 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/length.rs

Lines changed: 43 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,25 @@ 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+
// Indefinite lengths should always have EOC markers
107+
debug_assert!(self.inner >= 2);
108+
Self {
109+
inner: self.inner.saturating_sub(2),
110+
indefinite: true,
111+
}
112+
} else {
113+
self
114+
}
115+
}
116+
95117
/// Get initial octet of the encoded length (if one is required).
96118
///
97119
/// From X.690 Section 8.1.3.5:
@@ -379,22 +401,12 @@ fn decode_indefinite_length<'a, R: Reader<'a>>(reader: &mut R) -> Result<Length>
379401
let start_pos = reader.position();
380402

381403
loop {
382-
let current_pos = reader.position();
383-
384404
// Look for the end-of-contents marker
385405
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-
}
406+
read_eoc(reader)?;
395407

396408
// Compute how much we read and flag the decoded length as indefinite
397-
let mut ret = (current_pos - start_pos)?;
409+
let mut ret = (reader.position() - start_pos)?;
398410
ret.indefinite = true;
399411
return Ok(ret);
400412
}
@@ -404,6 +416,21 @@ fn decode_indefinite_length<'a, R: Reader<'a>>(reader: &mut R) -> Result<Length>
404416
}
405417
}
406418

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

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

535559
// Decode indefinite length
536560
let length = Length::decode(&mut reader).unwrap();
537-
assert!(length.indefinite);
561+
assert!(length.is_indefinite());
538562

539563
// Decoding the length should leave the position at the end of the indefinite length octet
540564
let pos = usize::try_from(reader.position()).unwrap();
541565
assert_eq!(pos, 2);
542566

543567
// The first two bytes are the header and the rest is the length of the message.
544568
// 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-
);
569+
assert_eq!(usize::try_from(length).unwrap(), EXAMPLE_LEN - pos);
549570

550571
// Read OID
551572
reader.tlv_bytes().unwrap();
@@ -564,7 +585,7 @@ mod tests {
564585

565586
// Parse the inner indefinite length
566587
let length = Length::decode(&mut reader).unwrap();
567-
assert!(length.indefinite);
568-
assert_eq!(usize::try_from(length).unwrap(), 18);
588+
assert!(length.is_indefinite());
589+
assert_eq!(usize::try_from(length).unwrap(), 20);
569590
}
570591
}

der/src/reader.rs

Lines changed: 9 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")]
@@ -41,7 +41,14 @@ pub trait Reader<'r>: Clone {
4141
E: From<Error>,
4242
F: FnOnce(&mut Self) -> Result<T, E>,
4343
{
44-
self.read_nested(header.length, f)
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)
4552
}
4653

4754
/// Attempt to read data borrowed directly from the input as a slice,

0 commit comments

Comments
 (0)