From 4678193abf24f17f9c239295ad08336cfa694147 Mon Sep 17 00:00:00 2001 From: Daniel James Date: Sat, 8 Feb 2025 21:24:26 -0800 Subject: [PATCH] Helper function for BER to DER transcoding Issue #779 has had some progress (PR #1321). However, this helper function is intended to provide an escape hatch for limited support of some cases of BER, while not waiting for general support or needing API changes. Specifically, the transcoding of occurrences of the constructed, indefinite-length method into the constructed, definite-length method. This is likely sufficient to address the examples in the wild, reported in Issue #779 and elsewhere. The goal is not to support all possible violations of DER that are still following valid BER. Examples are non-canonical encodings of lengths (which would require an alternative `Length`), or constructed string types (which could be handled by further work on this function, but usage may not be well motivated). --- der/src/berder.rs | 505 ++++++++++++++++++++++ der/src/lib.rs | 5 + der/tests/berder.rs | 163 +++++++ der/tests/examples/cms_enveloped_data.ber | Bin 0 -> 461 bytes der/tests/examples/cms_enveloped_data.der | Bin 0 -> 457 bytes der/tests/examples/root_cert.der | Bin 0 -> 533 bytes 6 files changed, 673 insertions(+) create mode 100644 der/src/berder.rs create mode 100644 der/tests/berder.rs create mode 100644 der/tests/examples/cms_enveloped_data.ber create mode 100644 der/tests/examples/cms_enveloped_data.der create mode 100644 der/tests/examples/root_cert.der diff --git a/der/src/berder.rs b/der/src/berder.rs new file mode 100644 index 000000000..bda9df64e --- /dev/null +++ b/der/src/berder.rs @@ -0,0 +1,505 @@ +use alloc::vec::Vec; +use core::mem; + +use crate::{ + Decode as _, Encode as _, ErrorKind, Header, IndefiniteLength, Length, Reader, Result, + SliceReader, Tag, +}; + +type Chunk = Vec; +type Stack = Vec; + +#[derive(Clone)] +struct ChunkStack { + vec: Vec, + size: usize, +} + +impl ChunkStack { + fn new() -> Self { + Self { + vec: Vec::new(), + size: 0, + } + } + + fn push(&mut self, chunk: Chunk) { + self.size += chunk.len(); + self.vec.push(chunk) + } + + fn extend(&mut self, chunks: Self) { + self.size += chunks.size; + self.vec.extend(chunks.vec) + } + + fn size(&self) -> usize { + self.size + } + + fn chunks(self) -> Vec { + self.vec + } +} + +fn push_header(s: &mut ChunkStack, header: Header) -> Result<()> { + let header_len = header.encoded_len()?; + let mut header_bytes = Vec::with_capacity(usize::try_from(header_len)?); + header.encode_to_vec(&mut header_bytes)?; + s.push(header_bytes); + Ok(()) +} +fn push_tag_and_len(s: &mut ChunkStack, tag: Tag, length: Length) -> Result<()> { + push_header(s, Header::new(tag, length)?) +} +fn push_tag_empty(s: &mut ChunkStack, tag: Tag) -> Result<()> { + push_tag_and_len(s, tag, Length::ZERO) +} + +/// Length of end-of-content (eoc) markers +const EOC_LENGTH: Length = Length::new(2); +/// end-of-content (eoc) marker +const EOC_MARKER: &[u8; 2] = &[0u8; 2]; + +fn read_eoc<'i, 'r, R: Reader<'r>>(r: &'i mut R) -> Result<()> { + // consume the last two bytes + if r.peek_byte() == Some(0) { + let eoc = r.read_slice(EOC_LENGTH)?; + if eoc.ne(EOC_MARKER) { + Err(ErrorKind::Failed.at(r.position().saturating_sub(Length::ONE))) + } else { + Ok(()) + } + } else { + // first of reserved two bytes are non zero + Err(ErrorKind::Failed.at(r.position())) + } +} + +enum RecurStage { + Start, + ResumeDefSeqSet { + header: Header, + accum: Stack, + limit: Length, + }, + ResumeDefTagging { + header: Header, + }, + ResumeIndefSeqSet { + tag: Tag, + accum: Stack, + }, + ResumeIndefTagging { + tag: Tag, + }, +} + +struct Recur { + // return value of recursion + sub_result: ChunkStack, + stage: RecurStage, +} + +impl Recur { + fn start_stage() -> Self { + Self { + sub_result: ChunkStack::new(), + stage: RecurStage::Start, + } + } + fn savepoint_def_seqset(header: Header, accum: Vec, limit: Length) -> Self { + Self { + sub_result: ChunkStack::new(), + stage: RecurStage::ResumeDefSeqSet { + header, + accum, + limit, + }, + } + } + fn first_savepoint_def_seqset(header: Header, limit: Length) -> Self { + Self::savepoint_def_seqset(header, Vec::new(), limit) + } + fn savepoint_def_tagging(header: Header) -> Self { + Self { + sub_result: ChunkStack::new(), + stage: RecurStage::ResumeDefTagging { header }, + } + } + fn savepoint_indef_seqset(tag: Tag, accum: Vec) -> Self { + Self { + sub_result: ChunkStack::new(), + stage: RecurStage::ResumeIndefSeqSet { tag, accum }, + } + } + fn first_savepoint_indef_seqset(tag: Tag) -> Self { + Self::savepoint_indef_seqset(tag, Vec::new()) + } + fn savepoint_indef_tagging(tag: Tag) -> Self { + Self { + sub_result: ChunkStack::new(), + stage: RecurStage::ResumeIndefTagging { tag }, + } + } +} + +fn berder_loop<'i, 'r, R: Reader<'r>>(r: &'i mut R) -> Result { + let mut result = ChunkStack::new(); + + let mut recur_stack: Stack = Vec::new(); + + // initial state + recur_stack.push(Recur::start_stage()); + + // NOTE: + // In the following large loop, there are multiple occurrences of `continue;`. + // These statements are superfluous as there are no remaining reachable code paths in the loop + // beyond that immediate lexical scope; however, the `continue;` is kept to make it clear + // to the reader that the loop _must_ restart at that point as the prior pushes to the stack + // are simulating recursion. + // Moreover, the statement + // ```rs + // mem::swap(&mut result, &mut recur.sub_result); + // ``` + // appears at the end of all other ‘sub loop’ code paths (except for `return Err`). + // It is an intentional choice to keep this duplication, over loss of ‘locality’ by + // refactoring to a single occurrence at the very end of the loop body. + + while let Some(mut recur) = recur_stack.pop() { + match recur.stage { + RecurStage::Start => { + let tag = Tag::decode(r) + .map_err(|e| e.kind().at(r.position().saturating_sub(Length::ONE)))?; + + // NOTE: as `IndefiniteLength` is defined in terms of `Length`, + // this will not allow non-canonical encoding of lengths (which are allowed under BER) + // so while this function can transform ‘constructed, indefinite-length’ encodings + // to ‘constructed, definite-length’ encodings, it will still error on lengths + // that are not encoded ‘minimally’. + let length = IndefiniteLength::decode(r)?; + + if let Some(length) = length.into() { + let header = Header::new(tag, length)?; + + match tag { + Tag::Sequence | Tag::Set => { + let limit = (r.position() + length)?; + + if r.position() < limit { + recur_stack.push(Recur::first_savepoint_def_seqset(header, limit)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + // empty seq/set + push_header(&mut recur.sub_result, header)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + Tag::ContextSpecific { + constructed: true, .. + } + | Tag::Application { + constructed: true, .. + } + | Tag::Private { + constructed: true, .. + } => { + if length > Length::ZERO { + recur_stack.push(Recur::savepoint_def_tagging(header)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + // empty tag + push_header(&mut recur.sub_result, header)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + _ => { + recur.sub_result.push(r.read_slice(length)?.to_vec()); + push_header(&mut recur.sub_result, header)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + } else { + match tag { + Tag::Sequence | Tag::Set => { + if r.peek_byte() != Some(0) { + recur_stack.push(Recur::first_savepoint_indef_seqset(tag)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + // empty seq/set + read_eoc(r)?; + push_tag_empty(&mut recur.sub_result, tag)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + Tag::ContextSpecific { + constructed: true, .. + } + | Tag::Application { + constructed: true, .. + } + | Tag::Private { + constructed: true, .. + } => { + if r.peek_byte() != Some(0) { + recur_stack.push(Recur::savepoint_indef_tagging(tag)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + // empty tag + read_eoc(r)?; + push_tag_empty(&mut recur.sub_result, tag)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + _ => { + return Err(ErrorKind::IndefiniteLength.at(r.position())); + } + } + } + } + RecurStage::ResumeDefSeqSet { + header, + mut accum, + limit, + } => { + // resume branch of definite-length seq/set + accum.push(mem::replace(&mut result, ChunkStack::new())); + + if r.position() < limit { + recur_stack.push(Recur::savepoint_def_seqset(header, accum, limit)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + for chunks in accum.into_iter().rev() { + recur.sub_result.extend(chunks); + } + + push_header(&mut recur.sub_result, header)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + RecurStage::ResumeDefTagging { header } => { + // resume branch of constructed, definite-length Application/ContextSpecific/Private + recur + .sub_result + .extend(mem::replace(&mut result, ChunkStack::new())); + + push_header(&mut recur.sub_result, header)?; + mem::swap(&mut result, &mut recur.sub_result); + } + RecurStage::ResumeIndefSeqSet { tag, mut accum } => { + // resume branch of indefinite-length seq/set + accum.push(mem::replace(&mut result, ChunkStack::new())); + + if r.peek_byte() != Some(0) { + recur_stack.push(Recur::savepoint_indef_seqset(tag, accum)); + recur_stack.push(Recur::start_stage()); + continue; + } else { + read_eoc(r)?; + + for chunks in accum.into_iter().rev() { + recur.sub_result.extend(chunks); + } + + let length = Length::try_from(recur.sub_result.size())?; + push_tag_and_len(&mut recur.sub_result, tag, length)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + RecurStage::ResumeIndefTagging { tag } => { + // resume branch of indefinite length constructed Application/ContextSpecific/Private + recur + .sub_result + .extend(mem::replace(&mut result, ChunkStack::new())); + + read_eoc(r)?; + + let length = Length::try_from(recur.sub_result.size())?; + push_tag_and_len(&mut recur.sub_result, tag, length)?; + mem::swap(&mut result, &mut recur.sub_result); + } + } + } + + Ok(result) +} + +/// Convert from Basic Encoding Rules (BER) to Distinguished Encoding Rules (DER) +/// +/// This function transforms an ASN.1 encoding that has occurrences of using the +/// constructed, indefinite-length method. The result has only the +/// definite-length method, thus complying with DER. +/// This function does not handle all possible encodings that would comply with +/// BER but not DER; an example is non-canonical encodings of lengths. In this +/// particular case, the existing decoding features of this crate do not support this. +/// +/// The primary motivation of this function is to handle ASN.1 messages in the +/// wild that have been produced by system that have taken advantage of the +/// stream-like modality that the constructed, indefinite-length method affords. +pub fn ber_to_der(bytes: &[u8]) -> Result> { + let mut r = SliceReader::new(bytes)?; + let chunk_stack = berder_loop(&mut r)?; + + let mut result = Vec::with_capacity(chunk_stack.size()); + for chunk in chunk_stack.chunks().into_iter().rev() { + result.extend(chunk); + } + r.finish(result) +} + +#[cfg(all(test, feature = "alloc"))] +#[allow(clippy::panic, clippy::panic_in_result_fn, clippy::unwrap_used)] +mod tests { + use super::*; + + const EMPTY_SEQ_BER: &[u8; 4] = &[0x30, 0x80, 0x00, 0x00]; + const EMPTY_SEQ_DER: &[u8; 2] = &[0x30, 0x00]; + const EMPTY_SET_BER: &[u8; 4] = &[0x31, 0x80, 0x00, 0x00]; + const EMPTY_SET_DER: &[u8; 2] = &[0x31, 0x00]; + + const EMPTY_CONTEXT_SPECIFIC_BER: &[u8; 4] = &[0xA0, 0x80, 0x00, 0x00]; + const EMPTY_CONTEXT_SPECIFIC_DER: &[u8; 2] = &[0xA0, 0x00]; + const EMPTY_APPLICATION_BER: &[u8; 4] = &[0x60, 0x80, 0x00, 0x00]; + const EMPTY_APPLICATION_DER: &[u8; 2] = &[0x60, 0x00]; + const EMPTY_PRIVATE_BER: &[u8; 4] = &[0xE0, 0x80, 0x00, 0x00]; + const EMPTY_PRIVATE_DER: &[u8; 2] = &[0xE0, 0x00]; + + const EMPTY_OCTET_STR: &[u8; 2] = &[0x04, 0x00]; + // NOTE: primitive, indefinite-length disallowed in BER/DER + + #[test] + fn empty_cases_der() -> Result<()> { + assert_eq!(EMPTY_SEQ_DER, &ber_to_der(EMPTY_SEQ_DER)?[..]); + assert_eq!(EMPTY_SET_DER, &ber_to_der(EMPTY_SET_DER)?[..]); + assert_eq!( + EMPTY_CONTEXT_SPECIFIC_DER, + &ber_to_der(EMPTY_CONTEXT_SPECIFIC_DER)?[..] + ); + assert_eq!( + EMPTY_APPLICATION_DER, + &ber_to_der(EMPTY_APPLICATION_DER)?[..] + ); + assert_eq!(EMPTY_PRIVATE_DER, &ber_to_der(EMPTY_PRIVATE_DER)?[..]); + assert_eq!(EMPTY_OCTET_STR, &ber_to_der(EMPTY_OCTET_STR)?[..]); + Ok(()) + } + + #[test] + fn empty_cases_ber() -> Result<()> { + assert_eq!(EMPTY_SEQ_DER, &ber_to_der(EMPTY_SEQ_BER)?[..]); + assert_eq!(EMPTY_SET_DER, &ber_to_der(EMPTY_SET_BER)?[..]); + assert_eq!( + EMPTY_CONTEXT_SPECIFIC_DER, + &ber_to_der(EMPTY_CONTEXT_SPECIFIC_BER)?[..] + ); + assert_eq!( + EMPTY_APPLICATION_DER, + &ber_to_der(EMPTY_APPLICATION_BER)?[..] + ); + assert_eq!(EMPTY_PRIVATE_DER, &ber_to_der(EMPTY_PRIVATE_BER)?[..]); + Ok(()) + } + + #[test] + fn primitive_indef_len_err() { + if let Err(err) = ber_to_der(&[0x04, 0x80, 0x01, 0x00, 0x00]) { + assert_eq!(ErrorKind::IndefiniteLength, err.kind()); + assert_eq!(Some(Length::from(2u8)), err.position()); + } else { + panic!("Expected error!") + } + } + + const DEGEN_NESTED_SEQ_BER: &[u8; 8] = &[0x30, 0x80, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00]; + const DEGEN_NESTED_SEQ_DER: &[u8; 4] = &[0x30, 0x02, 0x30, 0x00]; + const DEGEN_NESTED_CONTEXT_SPECIFIC_BER: &[u8; 8] = + &[0xA0, 0x80, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00]; + const DEGEN_NESTED_CONTEXT_SPECIFIC_DER: &[u8; 4] = &[0xA0, 0x02, 0x30, 0x00]; + + #[test] + fn degenerate_nested_cases_der() -> Result<()> { + assert_eq!(DEGEN_NESTED_SEQ_DER, &ber_to_der(DEGEN_NESTED_SEQ_DER)?[..]); + assert_eq!( + DEGEN_NESTED_CONTEXT_SPECIFIC_DER, + &ber_to_der(DEGEN_NESTED_CONTEXT_SPECIFIC_DER)?[..] + ); + Ok(()) + } + + #[test] + fn degenerate_nested_cases_ber() -> Result<()> { + assert_eq!(DEGEN_NESTED_SEQ_DER, &ber_to_der(DEGEN_NESTED_SEQ_BER)?[..]); + assert_eq!( + DEGEN_NESTED_CONTEXT_SPECIFIC_DER, + &ber_to_der(DEGEN_NESTED_CONTEXT_SPECIFIC_BER)?[..] + ); + Ok(()) + } + + // [0, 1] + const TWO_ELEM_SEQ_BER: &[u8; 10] = + &[0x30, 0x80, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x00, 0x00]; + const TWO_ELEM_SEQ_DER: &[u8; 8] = &[0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01]; + + #[test] + fn basic_seqs() -> Result<()> { + assert_eq!(TWO_ELEM_SEQ_DER, &ber_to_der(TWO_ELEM_SEQ_DER)?[..]); + assert_eq!(TWO_ELEM_SEQ_DER, &ber_to_der(TWO_ELEM_SEQ_BER)?[..]); + Ok(()) + } + + // SEQUENCE [1,] + const INVALID_EOC2_ONE_ELEM_SEQ_BER: &[u8; 7] = &[0x30, 0x80, 0x02, 0x01, 0x01, 0x00, 0xFF]; + + // context-specific 1 + const INVALID_EOC1_CONTEXT_SPECIFIC_BER: &[u8; 7] = &[0xA0, 0x80, 0x02, 0x01, 0x01, 0xFF, 0x00]; + const INVALID_EOC2_CONTEXT_SPECIFIC_BER: &[u8; 7] = &[0xA0, 0x80, 0x02, 0x01, 0x01, 0x00, 0xFF]; + + #[test] + fn invalid_eoc() { + if let Err(err) = ber_to_der(INVALID_EOC2_ONE_ELEM_SEQ_BER) { + assert_eq!(ErrorKind::Failed, err.kind()); + assert_eq!(Some(Length::from(6u8)), err.position()); + } else { + panic!("Expected error!") + } + + if let Err(err) = ber_to_der(INVALID_EOC1_CONTEXT_SPECIFIC_BER) { + assert_eq!(ErrorKind::Failed, err.kind()); + assert_eq!(Some(Length::from(5u8)), err.position()); + } else { + panic!("Expected error!") + } + + if let Err(err) = ber_to_der(INVALID_EOC2_CONTEXT_SPECIFIC_BER) { + assert_eq!(ErrorKind::Failed, err.kind()); + assert_eq!(Some(Length::from(6u8)), err.position()); + } else { + panic!("Expected error!") + } + } + + #[test] + fn non_canonical_lengths() { + assert!(Length::from_der(&[0x81, 0x00]).is_err()); + assert!(Length::from_der(&[0x81, 0x01]).is_err()); + assert!(Length::from_der(&[0x81, 0x7F]).is_err()); + + assert!(Length::from_der(&[0x82, 0x00, 0x00]).is_err()); + assert!(Length::from_der(&[0x82, 0x00, 0x01]).is_err()); + assert!(Length::from_der(&[0x82, 0x00, 0x7F]).is_err()); + + assert!(IndefiniteLength::from_der(&[0x81, 0x00]).is_err()); + assert!(IndefiniteLength::from_der(&[0x81, 0x01]).is_err()); + assert!(IndefiniteLength::from_der(&[0x81, 0x7F]).is_err()); + + assert!(IndefiniteLength::from_der(&[0x82, 0x00, 0x00]).is_err()); + assert!(IndefiniteLength::from_der(&[0x82, 0x00, 0x01]).is_err()); + assert!(IndefiniteLength::from_der(&[0x82, 0x00, 0x7F]).is_err()); + } +} diff --git a/der/src/lib.rs b/der/src/lib.rs index c7b7c05b9..8aa7f1411 100644 --- a/der/src/lib.rs +++ b/der/src/lib.rs @@ -357,6 +357,8 @@ mod str_ref; mod tag; mod writer; +#[cfg(feature = "alloc")] +mod berder; #[cfg(feature = "alloc")] mod bytes_owned; #[cfg(feature = "alloc")] @@ -383,6 +385,9 @@ pub use crate::{ #[cfg(feature = "alloc")] pub use crate::{asn1::Any, document::Document}; +#[cfg(feature = "alloc")] +pub use crate::berder::ber_to_der; + #[cfg(feature = "derive")] pub use der_derive::{Choice, Enumerated, Sequence, ValueOrd}; diff --git a/der/tests/berder.rs b/der/tests/berder.rs new file mode 100644 index 000000000..c67c4c9a5 --- /dev/null +++ b/der/tests/berder.rs @@ -0,0 +1,163 @@ +//! BER to DER tests. +#![cfg(feature = "alloc")] + +use der::{ber_to_der, Encode, Length, Result, SliceWriter, Tag, Writer}; +use proptest::prelude::*; + +const ROOT_CA: &[u8] = include_bytes!("examples/root_cert.der"); +const ENCLAVE_CMS: &[u8] = include_bytes!("examples/cms_enveloped_data.ber"); +const ENCLAVE_CMS_AS_DER: &[u8] = include_bytes!("examples/cms_enveloped_data.der"); + +#[test] +fn der_test_vectors() -> Result<()> { + assert_eq!(ROOT_CA, &ber_to_der(ROOT_CA)?); + assert_eq!(ENCLAVE_CMS_AS_DER, &ber_to_der(ENCLAVE_CMS_AS_DER)?); + Ok(()) +} + +#[test] +fn ber_to_der_test_vectors() -> Result<()> { + assert_eq!(ENCLAVE_CMS_AS_DER, &ber_to_der(ENCLAVE_CMS)?); + Ok(()) +} + +#[derive(Clone, Debug)] +enum Doc { + Null, + Bool(bool), + Int(i32), + Seq(Vec), +} + +impl Doc { + fn arb() -> impl Strategy { + let leaf = prop_oneof![ + Just(Doc::Null), + any::().prop_map(Doc::Bool), + any::().prop_map(Doc::Int), + ]; + leaf.prop_recursive(5, 64, 3, |inner| { + prop_oneof![prop::collection::vec(inner.clone(), 0..3).prop_map(Doc::Seq)] + }) + } + + fn encoded_len_der(&self) -> der::Result { + match self { + Doc::Null => der::asn1::Null.encoded_len(), + Doc::Bool(b) => b.encoded_len(), + Doc::Int(i) => i.encoded_len(), + Doc::Seq(docs) => { + let mut len = Length::ZERO; + for d in docs { + len = (len + d.encoded_len_der()?)? + } + len.for_tlv() + } + } + } + + fn encoded_len_ber(&self) -> der::Result { + match self { + Doc::Null => der::asn1::Null.encoded_len(), + Doc::Bool(b) => b.encoded_len(), + Doc::Int(i) => i.encoded_len(), + Doc::Seq(docs) => { + let mut len = Length::ZERO; + for d in docs { + len = (len + d.encoded_len_ber()?)? + } + len + Length::from(4u8) // 1 tag, 1 0x80, 2 0x0000 + } + } + } + + fn to_der1(&self, w: &mut SliceWriter) -> der::Result<()> { + match self { + Doc::Null => der::asn1::Null.encode(w), + Doc::Bool(b) => b.encode(w), + Doc::Int(i) => i.encode(w), + Doc::Seq(docs) => { + let len = { + let mut len = Length::ZERO; + for d in docs { + len = (len + d.encoded_len_der()?)? + } + len + }; + w.sequence(len, |w| { + for d in docs { + d.to_der1(w)? + } + Ok(()) + }) + } + } + } + + fn to_ber1(&self, w: &mut SliceWriter) -> der::Result<()> { + match self { + Doc::Null => der::asn1::Null.encode(w), + Doc::Bool(b) => b.encode(w), + Doc::Int(i) => i.encode(w), + Doc::Seq(docs) => { + Tag::Sequence.encode(w)?; + w.write_byte(0x80)?; + for d in docs { + d.to_ber1(w)? + } + w.write(&[0x00, 0x00]) + } + } + } + + fn to_der(&self) -> der::Result> { + let len = usize::try_from(self.encoded_len_der()?)?; + let mut buf = vec![0u8; len]; + let mut w = SliceWriter::new(&mut buf); + self.to_der1(&mut w)?; + Ok(w.finish()?.to_vec()) + } + + fn to_ber(&self) -> der::Result> { + let len = usize::try_from(self.encoded_len_ber()?)?; + let mut buf = vec![0u8; len]; + let mut w = SliceWriter::new(&mut buf); + self.to_ber1(&mut w)?; + Ok(w.finish()?.to_vec()) + } +} + +fn ber_to_der_round_trip(doc: Doc) -> der::Result<(Vec, Vec)> { + let der = doc.to_der()?; + + let der1 = ber_to_der(&der)?; + + Ok((der, der1)) +} + +fn ber_to_der_compare(doc: Doc) -> der::Result<(Vec, Vec)> { + let ber = doc.to_ber()?; + let der = doc.to_der()?; + + let der1 = ber_to_der(&ber)?; + + Ok((der, der1)) +} + +proptest! { + #[test] + fn prop_ber_to_der_round_trip(doc in Doc::arb()) { + match ber_to_der_round_trip(doc) { + Ok((expected, actual)) => prop_assert_eq!(expected, actual), + Err(err) => panic!("Unexpected error: {}", err), + } + } + + #[test] + fn prop_ber_to_der_compare(doc in Doc::arb()) { + match ber_to_der_compare(doc) { + Ok((expected, actual)) => prop_assert_eq!(expected, actual), + Err(err) => panic!("Unexpected error: {}", err), + } + } +} diff --git a/der/tests/examples/cms_enveloped_data.ber b/der/tests/examples/cms_enveloped_data.ber new file mode 100644 index 0000000000000000000000000000000000000000..0eb82f0aa9433104004e357ca5a50a6c521e6d74 GIT binary patch literal 461 zcmXqLVB^$k^Jx3d%gD~WpuwPliIK^$i80%ti7_3>Y)~lVHI9GNSCUf0*`u-FFPwAZ z)v15x`ZvDlHDY@G{CDPb0~@#jjO+&b3-}Fq**Fv0JQ!1%S(q4E85YVINFh{kpvkf{ zF*0bL|F_Xd-@j>x#PL<{^&jy+^*wfXf9a#x125;^JsS5-sm=H8j-3iS7-c3qKbH+V zv!QF3(jC)X68HZ7esOJPfx7q-`(xYcx5RI7;Rw0Q=e+Z?P>cQB9n2SN61~b8*G=R3 zQsw#gp9beFZ~Z7G9fcJ;F8^Z@?rre@!hSL6Rubogjk;zB++5yXI-qT*5?6nJ>6V^w zt=VC%U)kK>a3r+e@$h7^Wxp zw<~;^Q2Tc8+M7y(%mzqNz-S-~4?RXL76Ee;+mG(N-&Egi`?CMi$2Er+G_VMqDOvk1 TcDhY>(b>R|4%Lm1f&^cV0O@Un3xw0SV5GP5u-vN9}`F_1#2 z;6RgQX<}s1JpXT_k-mS^4vFKd-s?Z&f9iYe?*7t8u?Jqxy?Zq7n^K$a+Z{Uy|G)+BnB zF|M1&^QFr3?>`OBS>F0lN;(QFc3l3)BHY{H|AqZx(5)oS2^)3I4!F6zy>vj^P9?7X z{?aWy;aao9YPbAZ@@x+ODebkak&kPyWEtp6Jf7Ua>ahLr9S&jdC5lQLS9UJc>2J-; ztl-q!707LV?a=zSbN0n;`*~C+ literal 0 HcmV?d00001 diff --git a/der/tests/examples/root_cert.der b/der/tests/examples/root_cert.der new file mode 100644 index 0000000000000000000000000000000000000000..994d128b7b49970854fa25e6ee96dd1fb408e632 GIT binary patch literal 533 zcmXqLViGiHVw|>snTe5!Ns!^EVQGf+g!K<)-Og-z^881b0T&yGR-4B;TNY+!15ZP4 z15P&PP!={}rqEzRegj?*hl_{JF*mU)KhKZ{s2V81&BN>%9&9LWAPM3z^9UuD7whF^ zmK5ddrsgH*B$lNX8_0?C8d@3}7+Dw^8(A2bMv3#9K)5DgE;X$xGZ0{72m67Ek&RWm zk%d8tIf;Sg4^znNWgG`TXB0e|b!);tHw&Xl#d)3H|GyR8>~!%C2z+X?WaGEnj}PpL zkTTl7v5cMXp3t>f_OBGB)1C*Nuf2GeGd^Fq;EOni^UXT~jEYarCo^hrrn$7AtW0V# z@I7k(WwDcig8@G<1Z0I78UM4e8ZZMX16hy&ABz}^$OP4`yf<0hpHDkH?N`y<^D|f8 z_nv0J2a@Iosb>L3NE>qKGiNdwq%j#XEUwp|xbWoOxaT{PYQ$z-3T5t-XLkM>oxC!s z$JVl8s_R0zJ^S^nLvO5mSGeWywY8stS{8ktEylX3Kx64$?**A#8S_%N{Jz)!fK4_l cSh9Z3t*