Skip to main content

structured_cades/evidence/
cms_object.rs

1//! RFC4998 Appendix A: change only deleted ER attributes and containing lengths.
2
3use super::{View, syntax};
4use crate::{Error, Result, preservation::Kind};
5use structured_asn1::{
6    Element, Encoding, Parser, Reader,
7    length::DerLength,
8    memory::{OwnedVec, Work},
9};
10use structured_cms::{
11    ErrorCode,
12    attribute::{Attribute, Placement},
13    content::ContentInfo,
14    stream::{Receiver, Refused, Source},
15};
16
17const FIELD: &str = "cades.evidence.object";
18
19enum Header<'d> {
20    Original(&'d [u8]),
21    Definite { tag: u8, length: DerLength },
22}
23struct Frame<'d> {
24    header: Header<'d>,
25    prefix: &'d [u8],
26    suffix: &'d [u8],
27    indefinite: bool,
28    length: u64,
29}
30impl<'d> Frame<'d> {
31    fn body(element: Element<'d>, tag: u8, length: u64) -> Result<Self> {
32        let old = element.contents_span();
33        let header_len = usize::try_from(old.start - element.span().start)
34            .map_err(|_| syntax(ErrorCode::LengthOverflow, element.span()))?;
35        let header = if element.is_indefinite() || length == old.length {
36            Header::Original(&element.as_bytes()[..header_len])
37        } else {
38            Header::Definite {
39                tag,
40                length: DerLength::new(length),
41            }
42        };
43        let header_length = match &header {
44            Header::Original(bytes) => bytes.len() as u64,
45            Header::Definite { length, .. } => 1 + length.as_bytes().len() as u64,
46        };
47        let total = header_length
48            .checked_add(length)
49            .and_then(|value| value.checked_add(if element.is_indefinite() { 2 } else { 0 }))
50            .ok_or_else(|| syntax(ErrorCode::LengthOverflow, element.span()))?;
51        Ok(Self {
52            header,
53            prefix: &[],
54            suffix: &[],
55            indefinite: element.is_indefinite(),
56            length: total,
57        })
58    }
59    fn around(element: Element<'d>, tag: u8, child: Element<'d>, replacement: u64) -> Result<Self> {
60        let outer = element.contents_span();
61        let inner = child.span();
62        if inner.start < outer.start || inner.end() > outer.end() {
63            return Err(Error::Foreign { at: inner.start });
64        }
65        let start = usize::try_from(inner.start - outer.start)
66            .map_err(|_| syntax(ErrorCode::LengthOverflow, inner))?;
67        let end = usize::try_from(inner.end() - outer.start)
68            .map_err(|_| syntax(ErrorCode::LengthOverflow, inner))?;
69        let prefix = &element.contents()[..start];
70        let suffix = &element.contents()[end..];
71        let length = (prefix.len() as u64)
72            .checked_add(replacement)
73            .and_then(|value| value.checked_add(suffix.len() as u64))
74            .ok_or_else(|| syntax(ErrorCode::LengthOverflow, outer))?;
75        let mut frame = Self::body(element, tag, length)?;
76        frame.prefix = prefix;
77        frame.suffix = suffix;
78        Ok(frame)
79    }
80    fn begin<W: Work>(
81        &self,
82        out: &mut impl Receiver,
83        work: &W,
84    ) -> core::result::Result<(), ObjectError> {
85        work.charge_work(1).map_err(ObjectError::Resource)?;
86        match &self.header {
87            Header::Original(bytes) => out.write(bytes).map_err(ObjectError::Receiver)?,
88            Header::Definite { tag, length } => {
89                out.write(&[*tag]).map_err(ObjectError::Receiver)?;
90                out.write(length.as_bytes())
91                    .map_err(ObjectError::Receiver)?;
92            }
93        }
94        out.write(self.prefix).map_err(ObjectError::Receiver)
95    }
96    fn end<W: Work>(
97        &self,
98        out: &mut impl Receiver,
99        work: &W,
100    ) -> core::result::Result<(), ObjectError> {
101        work.charge_work(1).map_err(ObjectError::Resource)?;
102        out.write(self.suffix).map_err(ObjectError::Receiver)?;
103        if self.indefinite {
104            out.write(&[0, 0]).map_err(ObjectError::Receiver)?;
105        }
106        Ok(())
107    }
108}
109
110fn enclosing_frames<'d>(
111    message: &structured_cms::SignedData<'d>,
112    content: &ContentInfo<'d>,
113    envelope: Element<'d>,
114    first: Element<'d>,
115    signer: Frame<'d>,
116) -> Result<[Frame<'d>; 5]> {
117    let signer_set = Frame::around(message.signer_set_element(), 0x31, first, signer.length)?;
118    let body = Frame::around(
119        *content.content(),
120        0x30,
121        message.signer_set_element(),
122        signer_set.length,
123    )?;
124    let explicit = Frame::around(
125        content.content_wrapper(),
126        0xa0,
127        *content.content(),
128        body.length,
129    )?;
130    let outer = Frame::around(envelope, 0x30, content.content_wrapper(), explicit.length)?;
131    Ok([outer, explicit, body, signer_set, signer])
132}
133
134/// Actual source/dispatch refusal while streaming an original CMS projection.
135#[derive(Debug)]
136pub enum ObjectError {
137    /// Current operation refused before a source-descriptor traversal.
138    Resource(structured_asn1::memory::MemoryError),
139    /// The common stream guard retains the original provider/sink cause.
140    Receiver(Refused),
141    /// Original snapshot source/depth/current-operation refusal.
142    PriorProcessing(Error),
143}
144
145/// Refusal keeps current and previous document coordinates distinct.
146#[derive(Debug)]
147pub enum SnapshotError {
148    /// Current final-message selection/placement/operation refusal.
149    Current(Error),
150    /// Actual previous source/query refusal, in that source's coordinates.
151    Prior(Error),
152    /// Unchanged fields or exact prior attributes differ between the sources.
153    Changed {
154        /// Current selecting proof.
155        current: structured_asn1::Span,
156        /// Actual earlier whole CMS input.
157        prior: structured_asn1::Span,
158    },
159}
160/// Exact earlier whole CMS coding, captured from its own existing parsed
161/// message. It is not a caller-created sequence of bytes or a claimed date.
162/// Binding checks every unchanged source field and prior attribute before any
163/// hashing; original BER length forms can then be replayed without guessing.
164pub struct CmsSnapshot<'a, 'd> {
165    envelope: Element<'d>,
166    message: &'a structured_cms::SignedData<'d>,
167    frames: [Frame<'d>; 5],
168    parser: &'d Parser,
169    encoding: Encoding,
170    depth: u32,
171}
172impl<'a, 'd> CmsSnapshot<'a, 'd> {
173    /// Capture the actual full original container, without another CMS parse.
174    pub fn capture(
175        message: &'a structured_cms::SignedData<'d>,
176        envelope: Element<'d>,
177        operation: &Reader<'d>,
178    ) -> Result<Self> {
179        let content = container(message, envelope, operation)?;
180        let first = message
181            .signers()
182            .first()
183            .ok_or_else(|| syntax(ErrorCode::MissingField, envelope.span()))?;
184        let signer = if let Some(unsigned) = first.unsigned_attributes() {
185            Frame::around(
186                *first.element(),
187                0x30,
188                *unsigned.element(),
189                unsigned.element().as_bytes().len() as u64,
190            )?
191        } else {
192            let mut frame = Frame::body(
193                *first.element(),
194                0x30,
195                first.element().contents_span().length,
196            )?;
197            frame.prefix = first.element().contents();
198            frame
199        };
200        let frames = enclosing_frames(message, &content, envelope, *first.element(), signer)?;
201        Ok(Self {
202            envelope,
203            message,
204            frames,
205            parser: operation.parser(),
206            encoding: operation.encoding(),
207            depth: operation.depth(),
208        })
209    }
210}
211struct Original<'d> {
212    element: Element<'d>,
213    parser: &'d Parser,
214    encoding: Encoding,
215    depth: u32,
216}
217
218/// Closed whole-`ContentInfo` input for one original internal/external ER
219/// attribute. Only self/later ER attributes and their containing lengths change.
220/// Other fields, signature bytes and BER indefinite framing remain original.
221pub struct CmsObject<'a, 'd> {
222    frames: [Frame<'d>; 5],
223    unsigned: Option<Frame<'d>>,
224    attributes: &'a [Attribute<'d>],
225    keep: OwnedVec<bool>,
226    target: usize,
227    original: Option<Original<'d>>,
228}
229impl<'d> CmsObject<'_, 'd> {
230    /// Wire occurrence among the first signer's actual ER attributes. Full
231    /// record closure still checks their proven chronology; SET order/`genTime`
232    /// alone is never an authenticated ordering receipt.
233    #[must_use]
234    pub fn occurrence(&self) -> usize {
235        self.target
236    }
237    pub(super) fn coverage(
238        &self,
239        view: &View<'_, '_, 'd>,
240        operation: &Reader<'d>,
241    ) -> Result<OwnedVec<structured_asn1::Span>> {
242        let work = operation.execution();
243        let mut ranges = work.vector();
244        let message = view.prepared.document().message();
245        let mut fields = message.fields_source().reader_in(&work)?;
246        while let Some(element) = fields
247            .next_element()
248            .map_err(|cause| Error::encoding(cause, FIELD))?
249        {
250            if element.span() != message.signer_set_element().span() {
251                ranges
252                    .try_push(element.span())
253                    .map_err(|cause| Error::resource(cause, FIELD, element.span().start))?;
254            }
255        }
256        let first = message
257            .signers()
258            .first()
259            .expect("CmsObject admitted first signer");
260        let source = first.fields();
261        ranges
262            .try_push(structured_asn1::Span {
263                start: source.identity[0].span().start,
264                length: source.signature[1].span().end() - source.identity[0].span().start,
265            })
266            .map_err(|cause| Error::resource(cause, FIELD, first.span().start))?;
267        for signer in message.signers().iter().skip(1) {
268            ranges
269                .try_push(signer.span())
270                .map_err(|cause| Error::resource(cause, FIELD, signer.span().start))?;
271        }
272        for (ordinal, attribute) in self.attributes.iter().enumerate() {
273            work.charge_work(1)
274                .map_err(|cause| Error::resource(cause, FIELD, attribute.span().start))?;
275            if self.keep[ordinal] {
276                ranges
277                    .try_push(attribute.span())
278                    .map_err(|cause| Error::resource(cause, FIELD, attribute.span().start))?;
279            }
280        }
281        Ok(ranges.into_owned())
282    }
283}
284impl Source for CmsObject<'_, '_> {
285    type Error = ObjectError;
286    fn stream<W: Work>(
287        &mut self,
288        out: &mut impl Receiver,
289        work: &W,
290    ) -> core::result::Result<(), ObjectError> {
291        if let Some(original) = &self.original {
292            // Obtaining a borrowed source slice is constant work. The common
293            // selected provider pays the actual byte scan during hashing.
294            let origin = work
295                .read_scoped_source(
296                    original.parser,
297                    original.element.as_bytes(),
298                    original.element.span().start,
299                    original.encoding,
300                    original.depth,
301                )
302                .map_err(|cause| ObjectError::PriorProcessing(Error::encoding(cause, FIELD)))?;
303            origin
304                .charge(1)
305                .map_err(|cause| ObjectError::PriorProcessing(Error::encoding(cause, FIELD)))?;
306            return out
307                .write(original.element.as_bytes())
308                .map_err(ObjectError::Receiver);
309        }
310        for frame in &self.frames {
311            frame.begin(out, work)?;
312        }
313        if let Some(unsigned) = &self.unsigned {
314            unsigned.begin(out, work)?;
315            for (index, attribute) in self.attributes.iter().enumerate() {
316                work.charge_work(1).map_err(ObjectError::Resource)?;
317                if self.keep[index] {
318                    out.write(attribute.as_bytes())
319                        .map_err(ObjectError::Receiver)?;
320                }
321            }
322            unsigned.end(out, work)?;
323        }
324        for frame in self.frames.iter().rev() {
325            frame.end(out, work)?;
326        }
327        Ok(())
328    }
329}
330impl<'a, 'd> View<'a, '_, 'd> {
331    /// Selects the actual full CMS object for RFC4998 Appendix A. The supplied
332    /// `ContentInfo` must contain this SAME parsed `SignedData`, not equal bytes
333    /// from another parser/model. No serialization or second CMS parse occurs.
334    pub fn cms_object(
335        &self,
336        envelope: Element<'d>,
337        operation: &Reader<'d>,
338    ) -> Result<CmsObject<'a, 'd>> {
339        let required = self.entry.required;
340        let message = self.prepared.document().message();
341        message.require_session(operation, FIELD)?;
342        operation
343            .charge(12)
344            .map_err(|cause| Error::encoding(cause, FIELD))?;
345        if required.placement != Placement::Unsigned
346            || !matches!(
347                required.kind,
348                Kind::EvidenceRecordInternal | Kind::EvidenceRecordExternal
349            )
350        {
351            return Err(Error::Foreign {
352                at: required.property.span().start,
353            });
354        }
355        let first = message
356            .signers()
357            .first()
358            .ok_or_else(|| syntax(ErrorCode::MissingField, envelope.span()))?;
359        if first.span() != required.signature.span() {
360            return Err(Error::Foreign {
361                at: required.signature.span().start,
362            });
363        }
364        let content = container(message, envelope, operation)?;
365        let unsigned = first
366            .unsigned_attributes()
367            .ok_or_else(|| syntax(ErrorCode::MissingField, first.span()))?;
368        let work = operation.execution();
369        let mut keep = work.vector();
370        keep.try_reserve_exact(unsigned.items().len())
371            .map_err(|cause| Error::resource(cause, FIELD, unsigned.element().span().start))?;
372        let mut target = None;
373        let mut occurrence = 0;
374        let mut retained = 0u64;
375        for attribute in unsigned.items() {
376            work.charge_work(1 + attribute.kind().as_bytes().len().min(11) as u64)
377                .map_err(|cause| Error::resource(cause, FIELD, attribute.span().start))?;
378            let evidence = matches!(
379                Kind::from_oid(attribute.kind()),
380                Some(Kind::EvidenceRecordInternal | Kind::EvidenceRecordExternal)
381            );
382            if evidence && attribute.values().len() != 1 {
383                return Err(syntax(ErrorCode::AttributeCardinality, attribute.span()));
384            }
385            if attribute.span() == required.property.span() {
386                target = Some(occurrence);
387            }
388            let selected = !evidence || target.is_none();
389            if selected {
390                retained = retained
391                    .checked_add(attribute.as_bytes().len() as u64)
392                    .ok_or_else(|| syntax(ErrorCode::LengthOverflow, attribute.span()))?;
393            }
394            keep.try_push(selected)
395                .map_err(|cause| Error::resource(cause, FIELD, attribute.span().start))?;
396            if evidence {
397                occurrence += 1;
398            }
399        }
400        let target = target.ok_or(Error::Foreign {
401            at: required.property.span().start,
402        })?;
403        let modified = if retained == 0 {
404            None
405        } else {
406            Some(Frame::body(*unsigned.element(), 0xa1, retained)?)
407        };
408        let signer = Frame::around(
409            *first.element(),
410            0x30,
411            *unsigned.element(),
412            modified.as_ref().map_or(0, |frame| frame.length),
413        )?;
414        let frames = enclosing_frames(message, &content, envelope, *first.element(), signer)?;
415        Ok(CmsObject {
416            frames,
417            unsigned: modified,
418            attributes: unsigned.items(),
419            keep: keep.into_owned(),
420            target,
421            original: None,
422        })
423    }
424    /// Replay a genuine earlier whole CMS encoding. Only enclosing length
425    /// spellings may differ from the final-message projection; all original
426    /// signed fields, material, parallel signers and retained attributes must
427    /// match exactly before a digest/provider/source callback is started.
428    pub fn cms_object_snapshot(
429        &self,
430        envelope: Element<'d>,
431        snapshot: &CmsSnapshot<'_, 'd>,
432        operation: &Reader<'d>,
433    ) -> core::result::Result<CmsObject<'a, 'd>, SnapshotError> {
434        let mut selected = self
435            .cms_object(envelope, operation)
436            .map_err(SnapshotError::Current)?;
437        let current = operation.execution();
438        let origin = current
439            .read_scoped_source(
440                snapshot.parser,
441                snapshot.envelope.as_bytes(),
442                snapshot.envelope.span().start,
443                snapshot.encoding,
444                snapshot.depth,
445            )
446            .map_err(|cause| SnapshotError::Prior(Error::encoding(cause, FIELD)))?;
447        let work = origin.execution();
448        let current = self.required().property.span();
449        let prior_span = snapshot.envelope.span();
450        let changed = || SnapshotError::Changed {
451            current,
452            prior: prior_span,
453        };
454        let at = prior_span.start;
455        for (actual, prior) in selected.frames.iter().zip(&snapshot.frames) {
456            if !same(actual.prefix, prior.prefix, &work, at).map_err(SnapshotError::Prior)?
457                || !same(actual.suffix, prior.suffix, &work, at).map_err(SnapshotError::Prior)?
458            {
459                return Err(changed());
460            }
461        }
462        let prior = snapshot.message.signers()[0]
463            .unsigned_attributes()
464            .map_or(&[][..], |attributes| attributes.items());
465        let mut next = 0;
466        for (ordinal, attribute) in selected.attributes.iter().enumerate() {
467            work.charge_work(1)
468                .map_err(|cause| SnapshotError::Prior(Error::resource(cause, FIELD, at)))?;
469            if !selected.keep[ordinal] {
470                continue;
471            }
472            let previous = prior.get(next).ok_or_else(changed)?;
473            if !same(attribute.as_bytes(), previous.as_bytes(), &work, at)
474                .map_err(SnapshotError::Prior)?
475            {
476                return Err(changed());
477            }
478            next += 1;
479        }
480        if next != prior.len() {
481            return Err(changed());
482        }
483        selected.original = Some(Original {
484            element: snapshot.envelope,
485            parser: snapshot.parser,
486            encoding: snapshot.encoding,
487            depth: snapshot.depth,
488        });
489        Ok(selected)
490    }
491}
492
493fn same<W: Work>(left: &[u8], right: &[u8], work: &W, at: u64) -> Result<bool> {
494    work.charge_work(1)
495        .map_err(|cause| Error::resource(cause, FIELD, at))?;
496    if left.len() != right.len() {
497        return Ok(false);
498    }
499    work.charge_work(left.len() as u64)
500        .map_err(|cause| Error::resource(cause, FIELD, at))?;
501    Ok(left == right)
502}
503fn container<'d>(
504    message: &structured_cms::SignedData<'d>,
505    envelope: Element<'d>,
506    operation: &Reader<'d>,
507) -> Result<ContentInfo<'d>> {
508    message.require_session(operation, FIELD)?;
509    let (content, _) = ContentInfo::read_element(operation, &envelope)?;
510    operation
511        .charge(1 + content.kind().as_bytes().len() as u64)
512        .map_err(|cause| Error::encoding(cause, FIELD))?;
513    if content.kind() != structured_cms::oid::SIGNED_DATA {
514        return Err(syntax(ErrorCode::UnexpectedContentType, envelope.span()));
515    }
516    if content.content().span() != message.span()
517        || !core::ptr::eq(
518            content.content().as_bytes().as_ptr(),
519            message.as_bytes().as_ptr(),
520        )
521    {
522        return Err(Error::Foreign {
523            at: envelope.span().start,
524        });
525    }
526    Ok(content)
527}