Skip to main content

structured_cms/timestamp/
info.rs

1//! `TSTInfo` schema views, original source fields and explicit recovery faults.
2
3use super::{AccuracyReading, MessageImprint, TokenExtensions, TsaReading, record};
4use crate::{Error, ErrorCode, Result};
5use structured_asn1::memory::{OwnedVec, Work};
6use structured_asn1::{Element, Encoding, Integer, Oid, Reader, Span, Tag, universal};
7use structured_pkix::temporal::Reading;
8
9const FIELD: &str = "tstInfo";
10
11/// Original integer spelling and the common decoder's minimal numeric view.
12#[derive(Debug, Clone, Copy)]
13pub struct IntegerField<'a> {
14    element: Element<'a>,
15    value: Integer<'a>,
16}
17impl<'a> IntegerField<'a> {
18    fn read<W: Work>(
19        parent: &Reader<'a>,
20        element: &Element<'a>,
21        field: &'static str,
22        faults: &mut OwnedVec<Error, W>,
23    ) -> Result<Self> {
24        let (value, fault) = parent
25            .integer_reading(element)
26            .map_err(|error| Error::encoding(error, field))?
27            .into_parts();
28        if let Some(fault) = fault {
29            record(faults, Error::encoding(fault, field))?;
30        }
31        Ok(Self {
32            element: *element,
33            value,
34        })
35    }
36    /// Whole original INTEGER TLV; no minimal re-encoding is substituted.
37    #[must_use]
38    pub fn element(self) -> Element<'a> {
39        self.element
40    }
41    /// Shared minimal numeric view, still borrowing the original contents.
42    #[must_use]
43    pub fn value(self) -> Integer<'a> {
44        self.value
45    }
46}
47
48/// Required timestamp facts and optional values, independently of acceptance.
49/// Original bytes are borrowed; diagnostics/catalogues own their accounted capacity.
50#[derive(Debug)]
51pub struct TokenInfo<'a> {
52    policy: Oid<'a>,
53    imprint: MessageImprint<'a>,
54    serial: IntegerField<'a>,
55    gen_time: structured_pkix::temporal::ExactReading,
56    accuracy: Option<AccuracyReading<'a>>,
57    ordering: bool,
58    nonce: Option<IntegerField<'a>>,
59    tsa: Option<TsaReading<'a>>,
60    extensions: Option<TokenExtensions<'a>>,
61    faults: OwnedVec<Error>,
62    span: Span,
63}
64
65impl<'a> TokenInfo<'a> {
66    pub(super) fn read_carried(parent: &mut Reader<'a>) -> Result<Self> {
67        let element = parent
68            .expect(Tag::constructed(universal::SEQUENCE))
69            .map_err(|error| Error::encoding(error, FIELD))?;
70        let info = Self::read(parent, &element)?;
71        if parent.is_empty() {
72            return Ok(info);
73        }
74        info.with_tail(parent)
75    }
76
77    fn with_tail(mut self, parent: &Reader<'_>) -> Result<Self> {
78        let at = parent.offset();
79        let work = parent.execution();
80        // Recovery retains the uniquely bounded first TSTInfo and its source
81        // map. RFC3161 §2.4.2 does not permit trailing content: this fault must
82        // still prevent complete checking/evidence. It is inspection recovery,
83        // not a second TSTInfo or silently consumed bytes.
84        // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
85        let mut faults = self.faults.with_execution(&work).map_err(|_| {
86            Error::pkix(structured_pkix::Error::new(
87                structured_pkix::ErrorCode::ForeignReader,
88                FIELD,
89                at,
90            ))
91        })?;
92        record(
93            &mut faults,
94            Error::schema(ErrorCode::TrailingData, FIELD, at),
95        )?;
96        self.faults = faults.into_owned();
97        Ok(self)
98    }
99
100    /// Reads RFC3161 §2.4.2 fields under the original producing reader.
101    ///
102    /// A BER inspection reader retains DER/schema faults instead of repairing
103    /// source bytes. This is not token acceptance: every nested fault remains
104    /// visible and must be considered before successful proof extraction.
105    /// INTEGER roles follow required/optional schema order; competing serial/
106    /// nonce assignments refuse. Unknown bounded fields remain diagnostics.
107    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
108    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
109        if element.tag() != Tag::constructed(universal::SEQUENCE) {
110            return Err(Error::schema(
111                ErrorCode::UnexpectedTag,
112                FIELD,
113                element.span().start,
114            ));
115        }
116        let execution = parent.execution();
117        let mut faults = execution.vector();
118        distinguished(parent, element, &mut faults)?;
119        let mut inside = parent
120            .enter(element)
121            .map_err(|error| Error::encoding(error, FIELD))?;
122        let mut fields = Fields::default();
123        // Only first successful schema fields require duplicate records: ten
124        // fixed slots, never an unaccounted input-driven Vec of encodings.
125        let mut seen: [Option<Element<'a>>; 10] = [None; 10];
126        let mut last = None;
127        while let Some(element) = inside
128            .next_element()
129            .map_err(|error| Error::encoding(error, FIELD))?
130        {
131            execution
132                .charge_work(1)
133                .map_err(|reason| Error::resource(reason, FIELD, element.span().start))?;
134            let Some(slot) = fields.slot(&inside, &element)? else {
135                if carries(element.tag()) {
136                    if !same_duplicate(&inside, &fields, &seen, &element)? {
137                        return Err(Error::schema(
138                            ErrorCode::DuplicateField,
139                            FIELD,
140                            element.span().start,
141                        ));
142                    }
143                    record(
144                        &mut faults,
145                        Error::schema(ErrorCode::DuplicateField, FIELD, element.span().start),
146                    )?;
147                } else {
148                    record(
149                        &mut faults,
150                        Error::schema(ErrorCode::UnknownField, FIELD, element.span().start),
151                    )?;
152                }
153                continue;
154            };
155            if (slot == Slot::Serial && fields.gen_time.is_some())
156                || (slot == Slot::Nonce && (fields.serial_displaced || fields.gen_time.is_none()))
157            {
158                return Err(Error::schema(
159                    ErrorCode::DuplicateField,
160                    FIELD,
161                    element.span().start,
162                ));
163            }
164            if last.is_some_and(|last| slot < last) {
165                record(
166                    &mut faults,
167                    Error::schema(ErrorCode::FieldOrder, FIELD, element.span().start),
168                )?;
169            }
170            last = Some(last.map_or(slot, |last| last.max(slot)));
171            fields.read(slot, &inside, &element, &mut faults)?;
172            seen[slot as usize] = Some(element);
173        }
174        fields.finish(element.span(), faults.into_owned())
175    }
176
177    /// Original policy OID; acceptable policy selection belongs to the caller.
178    #[must_use]
179    pub fn policy(&self) -> Oid<'a> {
180        self.policy
181    }
182    /// Original imprint fields, including explicit malformed algorithm syntax.
183    #[must_use]
184    pub fn imprint(&self) -> &MessageImprint<'a> {
185        &self.imprint
186    }
187    /// Original serial; RFC3161 imposes no positivity or 160-bit maximum.
188    #[must_use]
189    pub fn serial(&self) -> IntegerField<'a> {
190        self.serial
191    }
192    /// Shared UTC label/precision reading; supplied UTC facts remain independent.
193    #[must_use]
194    pub fn gen_time(&self) -> Reading {
195        self.gen_time.reading()
196    }
197    pub(super) fn precise_gen_time(&self) -> &structured_pkix::temporal::ExactReading {
198        &self.gen_time
199    }
200    /// Absent complete Accuracy differs from a present empty/invalid reading.
201    #[must_use]
202    pub fn accuracy(&self) -> Option<&AccuracyReading<'a>> {
203        self.accuracy.as_ref()
204    }
205    /// Original ordering BOOLEAN, with FALSE only for its normative absent default.
206    #[must_use]
207    pub fn ordering(&self) -> bool {
208        self.ordering
209    }
210    /// Original optional nonce, without a fixed width or sign policy.
211    #[must_use]
212    pub fn nonce(&self) -> Option<IntegerField<'a>> {
213        self.nonce
214    }
215    /// Original explicit wrapper and common `GeneralName` reading or malformation.
216    #[must_use]
217    pub fn tsa(&self) -> Option<&TsaReading<'a>> {
218        self.tsa.as_ref()
219    }
220    /// Original optional extension catalogue and its own recovery diagnostics.
221    #[must_use]
222    pub fn extensions(&self) -> Option<&TokenExtensions<'a>> {
223        self.extensions.as_ref()
224    }
225    /// Root schema/DER faults; nested imprint/accuracy/extension faults stay there.
226    #[must_use]
227    pub fn faults(&self) -> &[Error] {
228        &self.faults
229    }
230    /// Any retained root or nested conformance fault, without scanning collections.
231    #[must_use]
232    pub fn has_faults(&self) -> bool {
233        !self.faults.is_empty()
234            || !self.imprint.faults().is_empty()
235            || self
236                .accuracy
237                .as_ref()
238                .is_some_and(|reading| !reading.faults().is_empty())
239            || self
240                .extensions
241                .as_ref()
242                .is_some_and(|reading| !reading.faults().is_empty())
243    }
244    /// Whole original `TSTInfo` range, never reconstructed from scalar fields.
245    #[must_use]
246    pub fn span(&self) -> Span {
247        self.span
248    }
249}
250
251fn same_duplicate(
252    parent: &Reader<'_>,
253    fields: &Fields<'_>,
254    seen: &[Option<Element<'_>>; 10],
255    element: &Element<'_>,
256) -> Result<bool> {
257    let execution = parent.execution();
258    let refusal = |reason| Error::resource(reason, FIELD, element.span().start);
259    if element.tag() == Tag::primitive(universal::OID)
260        && let Some(policy) = fields.policy
261    {
262        // RFC3161 §2.4.2 requires one DER policy; duplicate recovery is our
263        // inspection contract, not conformance. X.690 §8.1.3 length spelling
264        // does not change the decoded OID. Shared decoding validates both.
265        let other = parent
266            .oid(element)
267            .map_err(|error| Error::encoding(error, "tstInfo.policy"))?;
268        execution.charge_work(1).map_err(refusal)?;
269        if policy.as_bytes().len() != other.as_bytes().len() {
270            return Ok(false);
271        }
272        execution
273            .charge_work(policy.as_bytes().len() as u64)
274            .map_err(refusal)?;
275        return Ok(policy.as_bytes() == other.as_bytes());
276    }
277    // RFC3161 §2.4.2 orders the INTEGER roles. Here all three roles are
278    // populated, so another INTEGER can only repeat nonce. Standard-silent
279    // recovery must not rescue a conflicting nonce by matching version/serial.
280    // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
281    let applicable = if element.tag() == Tag::primitive(universal::INTEGER) {
282        &seen[Slot::Nonce as usize..=Slot::Nonce as usize]
283    } else {
284        &seen[..]
285    };
286    for prior in applicable.iter().flatten() {
287        execution.charge_work(1).map_err(refusal)?;
288        if prior.as_bytes().len() != element.as_bytes().len() {
289            continue;
290        }
291        execution
292            .charge_work(element.as_bytes().len() as u64)
293            .map_err(refusal)?;
294        if prior.as_bytes() == element.as_bytes() {
295            return Ok(true);
296        }
297    }
298    Ok(false)
299}
300
301#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
302#[repr(usize)]
303enum Slot {
304    Version,
305    Policy,
306    Imprint,
307    Serial,
308    GenTime,
309    Accuracy,
310    Ordering,
311    Nonce,
312    Tsa,
313    Extensions,
314}
315
316#[derive(Default)]
317struct Fields<'a> {
318    version: bool,
319    policy: Option<Oid<'a>>,
320    imprint: Option<MessageImprint<'a>>,
321    serial: Option<IntegerField<'a>>,
322    serial_displaced: bool,
323    gen_time: Option<structured_pkix::temporal::ExactReading>,
324    accuracy: Option<AccuracyReading<'a>>,
325    ordering: Option<bool>,
326    nonce: Option<IntegerField<'a>>,
327    tsa: Option<TsaReading<'a>>,
328    extensions: Option<TokenExtensions<'a>>,
329}
330
331impl<'a> Fields<'a> {
332    fn slot(&self, parent: &Reader<'a>, element: &Element<'a>) -> Result<Option<Slot>> {
333        let tag = element.tag();
334        let slot = if tag == Tag::primitive(universal::INTEGER) {
335            if !self.version {
336                // RFC3161 §2.4.2 orders version before policy and imprint.
337                // Recovery is not specified: once those anchors occurred,
338                // this INTEGER also admits serial with missing version. Never
339                // assign one of the competing readings merely by first slot.
340                // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
341                if self.policy.is_some() || self.imprint.is_some() || self.gen_time.is_some() {
342                    return Err(Error::schema(
343                        ErrorCode::DuplicateField,
344                        FIELD,
345                        element.span().start,
346                    ));
347                }
348                Slot::Version
349            } else if self.serial.is_none() {
350                Slot::Serial
351            } else if self.nonce.is_none() {
352                Slot::Nonce
353            } else {
354                return Ok(None);
355            }
356        } else if tag == Tag::primitive(universal::OID) {
357            Slot::Policy
358        } else if tag == Tag::constructed(universal::SEQUENCE) {
359            let mut probe = parent
360                .enter(element)
361                .map_err(|error| Error::encoding(error, FIELD))?;
362            let first = probe
363                .next_element()
364                .map_err(|error| Error::encoding(error, FIELD))?;
365            // A bare OID + hash has one malformed imprint reading; it must not
366            // occupy Accuracy and erase required serial/genTime at finish.
367            if first.is_some_and(|first| {
368                first.tag() == Tag::constructed(universal::SEQUENCE)
369                    || first.tag() == Tag::primitive(universal::OID)
370            }) {
371                Slot::Imprint
372            } else {
373                Slot::Accuracy
374            }
375        } else if tag == Tag::primitive(universal::GENERALIZED_TIME)
376            || tag == Tag::constructed(universal::GENERALIZED_TIME)
377        {
378            Slot::GenTime
379        } else if tag == Tag::primitive(universal::BOOLEAN) {
380            Slot::Ordering
381        } else if tag == Tag::context(true, 0) {
382            Slot::Tsa
383        } else if tag == Tag::context(true, 1) {
384            Slot::Extensions
385        } else {
386            return Ok(None);
387        };
388        let found = match slot {
389            Slot::Version => self.version,
390            Slot::Policy => self.policy.is_some(),
391            Slot::Imprint => self.imprint.is_some(),
392            Slot::Serial => self.serial.is_some(),
393            Slot::GenTime => self.gen_time.is_some(),
394            Slot::Accuracy => self.accuracy.is_some(),
395            Slot::Ordering => self.ordering.is_some(),
396            Slot::Nonce => self.nonce.is_some(),
397            Slot::Tsa => self.tsa.is_some(),
398            Slot::Extensions => self.extensions.is_some(),
399        };
400        Ok(if found { None } else { Some(slot) })
401    }
402
403    fn read<W: Work>(
404        &mut self,
405        slot: Slot,
406        parent: &Reader<'a>,
407        element: &Element<'a>,
408        faults: &mut OwnedVec<Error, W>,
409    ) -> Result<()> {
410        match slot {
411            Slot::Version => {
412                let version = IntegerField::read(parent, element, "tstInfo.version", faults)?;
413                if version.value.as_u64() != Some(1) {
414                    return Err(Error::schema(
415                        ErrorCode::UnsupportedVersion,
416                        "tstInfo.version",
417                        element.span().start,
418                    ));
419                }
420                self.version = true;
421            }
422            Slot::Policy => {
423                self.policy = Some(
424                    parent
425                        .oid(element)
426                        .map_err(|error| Error::encoding(error, "tstInfo.policy"))?,
427                );
428            }
429            Slot::Imprint => self.imprint = Some(MessageImprint::read(parent, element)?),
430            Slot::Serial => {
431                let serial = IntegerField::read(parent, element, "tstInfo.serialNumber", faults)?;
432                // RFC3161 §2.4.2 places serial after messageImprint. Before
433                // that anchor, value 1 also admits a repeated version with
434                // serial absent. Recovery must not select a competing reading.
435                // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
436                if self.imprint.is_none() && serial.value.as_u64() == Some(1) {
437                    return Err(Error::schema(
438                        ErrorCode::DuplicateField,
439                        FIELD,
440                        element.span().start,
441                    ));
442                }
443                self.serial_displaced = self.imprint.is_none() || self.gen_time.is_some();
444                self.serial = Some(serial);
445            }
446            Slot::GenTime => {
447                self.gen_time = Some(
448                    structured_pkix::temporal::read_generalized_exact(
449                        parent,
450                        element,
451                        "tstInfo.genTime",
452                    )
453                    .map_err(Error::pkix)?,
454                );
455            }
456            Slot::Accuracy => self.accuracy = Some(AccuracyReading::read(parent, element)?),
457            Slot::Ordering => {
458                let ordering = parent
459                    .boolean(element)
460                    .map_err(|error| Error::encoding(error, "tstInfo.ordering"))?;
461                if !ordering {
462                    record(
463                        faults,
464                        Error::schema(
465                            ErrorCode::DefaultWritten,
466                            "tstInfo.ordering",
467                            element.span().start,
468                        ),
469                    )?;
470                }
471                self.ordering = Some(ordering);
472            }
473            Slot::Nonce => {
474                self.nonce = Some(IntegerField::read(
475                    parent,
476                    element,
477                    "tstInfo.nonce",
478                    faults,
479                )?);
480            }
481            Slot::Tsa => self.tsa = Some(TsaReading::read(parent, element, faults)?),
482            Slot::Extensions => self.extensions = Some(TokenExtensions::read(parent, element)?),
483        }
484        Ok(())
485    }
486
487    fn finish(self, span: Span, faults: OwnedVec<Error>) -> Result<TokenInfo<'a>> {
488        let missing = |field| Error::schema(ErrorCode::MissingField, field, span.start);
489        if !self.version {
490            return Err(missing("tstInfo.version"));
491        }
492        Ok(TokenInfo {
493            policy: self.policy.ok_or_else(|| missing("tstInfo.policy"))?,
494            imprint: self
495                .imprint
496                .ok_or_else(|| missing("tstInfo.messageImprint"))?,
497            serial: self.serial.ok_or_else(|| missing("tstInfo.serialNumber"))?,
498            gen_time: self.gen_time.ok_or_else(|| missing("tstInfo.genTime"))?,
499            accuracy: self.accuracy,
500            ordering: self.ordering.unwrap_or(false),
501            nonce: self.nonce,
502            tsa: self.tsa,
503            extensions: self.extensions,
504            faults,
505            span,
506        })
507    }
508}
509
510fn carries(tag: Tag) -> bool {
511    tag == Tag::primitive(universal::INTEGER)
512        || tag == Tag::primitive(universal::OID)
513        || tag == Tag::constructed(universal::SEQUENCE)
514        || tag == Tag::primitive(universal::GENERALIZED_TIME)
515        || tag == Tag::constructed(universal::GENERALIZED_TIME)
516        || tag == Tag::primitive(universal::BOOLEAN)
517        || tag == Tag::context(true, 0)
518        || tag == Tag::context(true, 1)
519}
520
521fn distinguished<W: Work>(
522    parent: &Reader<'_>,
523    element: &Element<'_>,
524    faults: &mut OwnedVec<Error, W>,
525) -> Result<()> {
526    let mut strict = parent
527        .reread(element, Encoding::Der)
528        .map_err(|error| Error::encoding(error, FIELD))?;
529    let result = strict
530        .expect(element.tag())
531        .map_err(|error| Error::encoding(error, FIELD))
532        .and_then(|again| {
533            structured_pkix::der::conforms(&strict, &again, FIELD).map_err(Error::pkix)
534        });
535    match result {
536        Ok(()) => Ok(()),
537        Err(error) => record(faults, error),
538    }
539}
540
541#[cfg(test)]
542mod tail_tests {
543    use super::*;
544    use alloc::sync::Arc;
545    use structured_asn1::{
546        Parser,
547        memory::{Memory, MemoryAccount, MemoryError},
548    };
549
550    fn carried_bytes() -> [u8; 42] {
551        [
552            0x30, 37, 2, 1, 1, 6, 1, 43, 0x30, 9, 0x30, 3, 6, 1, 42, 4, 2, b'a', b'b', 2, 1, 7,
553            0x18, 15, b'2', b'0', b'2', b'6', b'0', b'1', b'0', b'1', b'0', b'0', b'0', b'0', b'0',
554            b'0', b'Z', 0xff, 0xfe, 0xfd,
555        ]
556    }
557
558    #[test]
559    fn tail_capacity_refusal_rolls_back_without_funding_a_successful_reading() {
560        // Fill capacity only after schema reading, isolating the fault admission
561        // from temporary DER-walk frames and proving refusal at the tail itself.
562        let bytes = carried_bytes();
563        let memory = Memory::new(Arc::new(MemoryAccount::new(65_536, 1_000_000)));
564        let parser = Parser::ber(memory.clone());
565        let mut parent = parser.read(&bytes, 70).unwrap();
566        let element = parent
567            .expect(Tag::constructed(universal::SEQUENCE))
568            .unwrap();
569        let info = TokenInfo::read(&parent, &element).unwrap();
570        assert_eq!(info.faults().len(), 0);
571        let mut held = memory.vector::<u8>();
572        held.try_reserve_exact(memory.limit() - memory.used())
573            .unwrap();
574        assert_eq!(memory.used(), memory.limit());
575        let error = info.with_tail(&parent).unwrap_err();
576        assert!(error.is_terminal());
577        assert_eq!(error.offset(), 109);
578        assert_eq!(error.field(), "tstInfo");
579        assert_eq!(
580            error.code(),
581            ErrorCode::Encoding(structured_asn1::ErrorCode::from(MemoryError::LimitExceeded))
582        );
583        drop(error);
584        drop(held);
585        assert_eq!(memory.used(), 0);
586    }
587
588    #[test]
589    fn retained_tail_diagnostic_keeps_charge_after_original_parser_drops() {
590        // Moving the independently owned catalogue preserves its allocation and
591        // source location; releasing the parser is not a storage refund.
592        let bytes = carried_bytes();
593        let memory = Memory::new(Arc::new(MemoryAccount::new(65_536, 1_000_000)));
594        let faults = {
595            let parser = Parser::ber(memory.clone());
596            let mut reader = parser.read(&bytes, 70).unwrap();
597            let TokenInfo { faults, .. } = TokenInfo::read_carried(&mut reader).unwrap();
598            faults
599        };
600        assert_eq!(faults.len(), 1);
601        assert_eq!(faults[0].code(), ErrorCode::TrailingData);
602        assert_eq!(faults[0].offset(), 109);
603        assert_eq!(faults[0].field(), "tstInfo");
604        assert!(memory.used() >= core::mem::size_of::<Error>());
605        drop(faults);
606        assert_eq!(memory.used(), 0);
607    }
608}