Skip to main content

structured_cms/
evidence.rs

1//! RFC 4998 evidence records over original sources and current execution.
2//!
3//! Reading preserves unprotected hints and unknown timestamp formats. Neither
4//! a readable record nor a matching hash establishes historical TSA validity.
5//! <https://www.rfc-editor.org/rfc/rfc4998#section-3>
6
7use crate::{
8    ContentInfo, Error, ErrorCode, Octets, Result, SignedData, attribute::Attribute, oid,
9    timestamp::Token,
10};
11use structured_asn1::{
12    Element, Encoding, Oid, Parser, Reader, Span, Tag,
13    memory::{OwnedVec, Work},
14    universal,
15};
16use structured_pkix::algorithm::{self, AlgorithmIdentifier};
17
18const FIELD: &str = "evidenceRecord";
19
20mod input;
21pub use input::{InputError, Membership, ObjectInput};
22mod renewal;
23pub use renewal::PriorSequence;
24mod objects;
25pub use objects::{AsyncObjects, AsyncStreams};
26pub mod verify;
27
28/// Original field context, independent of a movable reader cursor.
29#[derive(Debug, Clone, Copy)]
30pub struct Original<'d> {
31    parser: &'d Parser,
32    element: Element<'d>,
33    encoding: Encoding,
34    outer: u32,
35}
36impl<'d> Original<'d> {
37    fn new(parent: &Reader<'d>, element: Element<'d>) -> Self {
38        Self {
39            parser: parent.parser(),
40            element,
41            encoding: parent.encoding(),
42            outer: parent.depth(),
43        }
44    }
45    /// Whole original value, including its original tag and length.
46    #[must_use]
47    pub fn element(&self) -> Element<'d> {
48        self.element
49    }
50    /// Reopens this original field with both source and current query ceilings.
51    pub fn reader_in<'q>(&self, query: &Reader<'q>) -> Result<Reader<'q>>
52    where
53        'd: 'q,
54    {
55        self.parser
56            .read_query_in(
57                query,
58                self.element.as_bytes(),
59                self.element.span().start,
60                self.encoding,
61                self.outer,
62            )
63            .map_err(|cause| Error::encoding(cause, FIELD))
64    }
65}
66
67/// Unprotected re-encryption context, not evidence for plaintext correspondence.
68#[derive(Debug)]
69pub struct EncryptionInfo<'d> {
70    kind: Oid<'d>,
71    value: Original<'d>,
72}
73impl<'d> EncryptionInfo<'d> {
74    /// Original encryption mechanism identifier, without algorithm dispatch.
75    #[must_use]
76    pub fn kind(&self) -> Oid<'d> {
77        self.kind
78    }
79    /// Original open value for the actual encryption mechanism owner.
80    #[must_use]
81    pub fn value(&self) -> Original<'d> {
82        self.value
83    }
84}
85
86/// One reduced-tree level. Logical hashes retain original OCTET STRING pieces.
87#[derive(Debug)]
88pub struct PartialTree<'d> {
89    original: Original<'d>,
90    hashes: OwnedVec<Octets<'d>>,
91}
92impl<'d> PartialTree<'d> {
93    /// Original sequence and its producing context.
94    #[must_use]
95    pub fn original(&self) -> Original<'d> {
96        self.original
97    }
98    /// Hash values as full-width logical octets, without canonicalizing input.
99    #[must_use]
100    pub fn hashes(&self) -> &[Octets<'d>] {
101        &self.hashes
102    }
103}
104
105/// RFC3161 token or an original alternative timestamp requiring its own owner.
106#[derive(Debug)]
107#[expect(
108    clippy::large_enum_variant,
109    reason = "the normal RFC3161 model is retained inline in the quota-backed flat stamp catalogue; boxing would add a separate allocation for every token"
110)]
111pub enum TimeStamp<'d> {
112    /// Original parsed CMS/TST envelope; inspection is not authentication.
113    Rfc3161(Token<'d>),
114    /// Original open timestamp format; never silently omitted from a proof.
115    Other(Original<'d>),
116}
117
118/// One archive timestamp, preserving the exact renewed timestamp encoding.
119#[derive(Debug)]
120pub struct ArchiveTimeStamp<'d> {
121    original: Original<'d>,
122    algorithm: Option<AlgorithmIdentifier<'d>>,
123    attributes: Option<OwnedVec<Attribute<'d>>>,
124    attributes_span: Option<Span>,
125    tree: Option<OwnedVec<PartialTree<'d>>>,
126    timestamp: Original<'d>,
127    token: TimeStamp<'d>,
128}
129impl<'d> ArchiveTimeStamp<'d> {
130    /// Whole original archive-timestamp field.
131    #[must_use]
132    pub fn original(&self) -> Original<'d> {
133        self.original
134    }
135    /// Written tree algorithm. Absence selects the actual token imprint algorithm.
136    #[must_use]
137    pub fn digest_algorithm(&self) -> Option<AlgorithmIdentifier<'d>> {
138        self.algorithm
139    }
140    /// Original optional renewal attributes; absence differs from an empty set.
141    #[must_use]
142    pub fn attributes(&self) -> Option<&[Attribute<'d>]> {
143        self.attributes.as_deref()
144    }
145    /// Original reduced tree. Absence means a direct-object timestamp.
146    #[must_use]
147    pub fn reduced_tree(&self) -> Option<&[PartialTree<'d>]> {
148        self.tree.as_deref()
149    }
150    /// Whole original `ContentInfo` bytes used by timestamp renewal (§5.2).
151    #[must_use]
152    pub fn timestamp_source(&self) -> Original<'d> {
153        self.timestamp
154    }
155    /// Parsed timestamp or explicitly unimplemented alternative format.
156    #[must_use]
157    pub fn timestamp(&self) -> &TimeStamp<'d> {
158        &self.token
159    }
160}
161
162/// One timestamp-renewal chain, in its original sequence order.
163#[derive(Debug)]
164pub struct Chain<'d> {
165    original: Original<'d>,
166    first: usize,
167    end: usize,
168}
169impl<'d> Chain<'d> {
170    /// Whole original chain including its wrapper.
171    #[must_use]
172    pub fn original(&self) -> Original<'d> {
173        self.original
174    }
175    /// Range in this record's flat timestamp catalogue.
176    #[must_use]
177    pub fn timestamps(&self) -> core::ops::Range<usize> {
178        self.first..self.end
179    }
180}
181
182/// Original evidence record. Flat ownership makes traversal and drop stack-safe.
183#[derive(Debug)]
184pub struct EvidenceRecord<'d> {
185    original: Original<'d>,
186    algorithms: OwnedVec<AlgorithmIdentifier<'d>>,
187    crypto: Option<OwnedVec<Attribute<'d>>>,
188    encryption: Option<EncryptionInfo<'d>>,
189    sequence: Original<'d>,
190    chains: OwnedVec<Chain<'d>>,
191    timestamps: OwnedVec<ArchiveTimeStamp<'d>>,
192}
193impl<'d> EvidenceRecord<'d> {
194    /// Reads one original embedded record. Parent encoding/depth are inherited.
195    pub fn read(parent: &Reader<'d>, element: &Element<'d>) -> Result<Self> {
196        let mut inside = enter(parent, element, Tag::constructed(universal::SEQUENCE))?;
197        let version = required(&mut inside)?;
198        if version.tag() != Tag::primitive(universal::INTEGER) {
199            return Err(syntax(ErrorCode::UnexpectedTag, version.span()));
200        }
201        let version = inside.integer(&version).map_err(encoding)?;
202        if version.as_u64() != Some(1) {
203            return Err(syntax(ErrorCode::UnsupportedVersion, element.span()));
204        }
205        let set = required(&mut inside)?;
206        let mut members = enter(&inside, &set, Tag::constructed(universal::SEQUENCE))?;
207        let work = parent.execution();
208        let mut algorithms = work.vector();
209        while let Some(member) = members.next_element().map_err(encoding)? {
210            let value = algorithm::read(&members, &member, FIELD).map_err(Error::pkix)?;
211            algorithms
212                .try_push(value)
213                .map_err(|cause| Error::resource(cause, FIELD, member.span().start))?;
214        }
215        if algorithms.is_empty() {
216            return Err(syntax(ErrorCode::MissingField, set.span()));
217        }
218        let mut next = required(&mut inside)?;
219        let crypto = if next.tag() == Tag::context(true, 0) {
220            let values = attributes(&inside, &next, Tag::context(true, 0), false)?;
221            next = required(&mut inside)?;
222            Some(values)
223        } else {
224            None
225        };
226        let encryption = if next.tag() == Tag::context(true, 1) {
227            let mut fields = enter(&inside, &next, Tag::context(true, 1))?;
228            let kind = fields.read_oid().map_err(encoding)?;
229            let value = required(&mut fields)?;
230            finish(&fields)?;
231            let result = EncryptionInfo {
232                kind,
233                value: Original::new(&fields, value),
234            };
235            next = required(&mut inside)?;
236            Some(result)
237        } else {
238            None
239        };
240        let mut sequence = enter(&inside, &next, Tag::constructed(universal::SEQUENCE))?;
241        let mut chains = work.vector();
242        let mut timestamps = work.vector();
243        while let Some(chain) = sequence.next_element().map_err(encoding)? {
244            let mut entries = enter(&sequence, &chain, Tag::constructed(universal::SEQUENCE))?;
245            let first = timestamps.len();
246            while let Some(entry) = entries.next_element().map_err(encoding)? {
247                let value = read_timestamp(&entries, entry)?;
248                timestamps
249                    .try_push(value)
250                    .map_err(|cause| Error::resource(cause, FIELD, entry.span().start))?;
251            }
252            if first == timestamps.len() {
253                return Err(syntax(ErrorCode::MissingField, chain.span()));
254            }
255            chains
256                .try_push(Chain {
257                    original: Original::new(&sequence, chain),
258                    first,
259                    end: timestamps.len(),
260                })
261                .map_err(|cause| Error::resource(cause, FIELD, chain.span().start))?;
262        }
263        if chains.is_empty() {
264            return Err(syntax(ErrorCode::MissingField, next.span()));
265        }
266        finish(&inside)?;
267        Ok(Self {
268            original: Original::new(parent, *element),
269            algorithms: algorithms.into_owned(),
270            crypto,
271            encryption,
272            sequence: Original::new(&inside, next),
273            chains: chains.into_owned(),
274            timestamps: timestamps.into_owned(),
275        })
276    }
277    /// Whole original record, without serializing its retained views.
278    #[must_use]
279    pub fn original(&self) -> Original<'d> {
280        self.original
281    }
282    /// Declared union; semantic membership is checked with the selected provider.
283    #[must_use]
284    pub fn digest_algorithms(&self) -> &[AlgorithmIdentifier<'d>] {
285        &self.algorithms
286    }
287    /// Unprotected validation hints; no trust is inferred from their presence.
288    #[must_use]
289    pub fn crypto_infos(&self) -> Option<&[Attribute<'d>]> {
290        self.crypto.as_deref()
291    }
292    /// Unprotected exact re-encryption context.
293    #[must_use]
294    pub fn encryption_info(&self) -> Option<&EncryptionInfo<'d>> {
295        self.encryption.as_ref()
296    }
297    /// Whole original sequence of timestamp-renewal chains.
298    #[must_use]
299    pub fn sequence(&self) -> Original<'d> {
300        self.sequence
301    }
302    /// Actual chain order; not a trusted chronology before verification.
303    #[must_use]
304    pub fn chains(&self) -> &[Chain<'d>] {
305        &self.chains
306    }
307    /// Flat original timestamp catalogue; chain ranges select their members.
308    #[must_use]
309    pub fn timestamps(&self) -> &[ArchiveTimeStamp<'d>] {
310        &self.timestamps
311    }
312}
313
314fn read_timestamp<'d>(parent: &Reader<'d>, element: Element<'d>) -> Result<ArchiveTimeStamp<'d>> {
315    let mut inside = enter(parent, &element, Tag::constructed(universal::SEQUENCE))?;
316    let mut next = required(&mut inside)?;
317    let algorithm = if next.tag() == Tag::context(true, 0) {
318        let value = algorithm::read_implicit(&inside, &next, 0, FIELD).map_err(Error::pkix)?;
319        next = required(&mut inside)?;
320        Some(value)
321    } else {
322        None
323    };
324    let attributes_span = (next.tag() == Tag::context(true, 1)).then_some(next.span());
325    let attributes = if next.tag() == Tag::context(true, 1) {
326        let values = attributes(&inside, &next, Tag::context(true, 1), true)?;
327        next = required(&mut inside)?;
328        Some(values)
329    } else {
330        None
331    };
332    let tree = if next.tag() == Tag::context(true, 2) {
333        let mut levels = enter(&inside, &next, Tag::context(true, 2))?;
334        let work = parent.execution();
335        let mut tree = work.vector();
336        while let Some(level) = levels.next_element().map_err(encoding)? {
337            let mut entries = enter(&levels, &level, Tag::constructed(universal::SEQUENCE))?;
338            let mut hashes = work.vector();
339            while let Some(hash) = entries.next_element().map_err(encoding)? {
340                let value = Octets::read(&entries, &hash, FIELD)?;
341                hashes
342                    .try_push(value)
343                    .map_err(|cause| Error::resource(cause, FIELD, hash.span().start))?;
344            }
345            // RFC4998 §4.2 permits arbitrary hash-list lengths. Inspection
346            // preserves an empty list; proof execution still needs membership.
347            tree.try_push(PartialTree {
348                original: Original::new(&levels, level),
349                hashes: hashes.into_owned(),
350            })
351            .map_err(|cause| Error::resource(cause, FIELD, level.span().start))?;
352        }
353        next = required(&mut inside)?;
354        Some(tree.into_owned())
355    } else {
356        None
357    };
358    let (envelope, source) = ContentInfo::read_element(&inside, &next)?;
359    inside
360        .charge(1 + envelope.kind().as_bytes().len() as u64)
361        .map_err(encoding)?;
362    let token = if envelope.kind() == oid::SIGNED_DATA {
363        TimeStamp::Rfc3161(Token::from_message(
364            SignedData::read_element(&source, envelope.content())?,
365            &source,
366        )?)
367    } else {
368        TimeStamp::Other(Original::new(&inside, next))
369    };
370    finish(&inside)?;
371    Ok(ArchiveTimeStamp {
372        original: Original::new(parent, element),
373        algorithm,
374        attributes,
375        attributes_span,
376        tree,
377        timestamp: Original::new(&inside, next),
378        token,
379    })
380}
381
382fn attributes<'d>(
383    parent: &Reader<'d>,
384    element: &Element<'d>,
385    tag: Tag,
386    sorted: bool,
387) -> Result<OwnedVec<Attribute<'d>>> {
388    let mut inside = enter(parent, element, tag)?;
389    let work = parent.execution();
390    let mut values = work.vector();
391    let mut previous = None;
392    while let Some(entry) = inside.next_element().map_err(encoding)? {
393        // RFC4998 §§3.1/4.1: CryptoInfos is a SEQUENCE, Attributes a SET.
394        // Only the latter receives DER SET ordering, never the former.
395        if sorted && inside.encoding().is_der() {
396            if let Some(previous) = previous
397                && !structured_pkix::der::set_ordered(&inside, previous, &entry, FIELD)
398                    .map_err(Error::pkix)?
399            {
400                return Err(syntax(ErrorCode::SetOrder, entry.span()));
401            }
402            previous = Some(entry.as_bytes());
403        }
404        values
405            .try_push(Attribute::read(&inside, &entry, FIELD)?)
406            .map_err(|cause| Error::resource(cause, FIELD, entry.span().start))?;
407    }
408    if values.is_empty() {
409        return Err(syntax(ErrorCode::EmptyAttributes, element.span()));
410    }
411    Ok(values.into_owned())
412}
413fn enter<'d>(parent: &Reader<'d>, element: &Element<'d>, tag: Tag) -> Result<Reader<'d>> {
414    if element.tag() != tag {
415        return Err(syntax(ErrorCode::UnexpectedTag, element.span()));
416    }
417    parent.enter(element).map_err(encoding)
418}
419fn required<'d>(reader: &mut Reader<'d>) -> Result<Element<'d>> {
420    reader
421        .next_element()
422        .map_err(encoding)?
423        .ok_or_else(|| Error::schema(ErrorCode::MissingField, FIELD, reader.offset()))
424}
425fn finish(reader: &Reader<'_>) -> Result<()> {
426    if reader.is_empty() {
427        Ok(())
428    } else {
429        Err(Error::schema(
430            ErrorCode::TrailingData,
431            FIELD,
432            reader.offset(),
433        ))
434    }
435}
436fn syntax(code: ErrorCode, span: Span) -> Error {
437    Error::schema(code, FIELD, span.start)
438}
439fn encoding(cause: structured_asn1::Error) -> Error {
440    Error::encoding(cause, FIELD)
441}