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