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: Reading,
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    /// Reads RFC3161 §2.4.2 fields under the original producing reader.
67    ///
68    /// A BER inspection reader retains DER/schema faults instead of repairing
69    /// source bytes. This is not token acceptance: every nested fault remains
70    /// visible and must be considered before successful proof extraction.
71    /// INTEGER roles follow required/optional schema order; competing serial/
72    /// nonce assignments refuse. Unknown bounded fields remain diagnostics.
73    /// <https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2>
74    pub fn read(parent: &Reader<'a>, element: &Element<'a>) -> Result<Self> {
75        if element.tag() != Tag::constructed(universal::SEQUENCE) {
76            return Err(Error::schema(
77                ErrorCode::UnexpectedTag,
78                FIELD,
79                element.span().start,
80            ));
81        }
82        let execution = parent.execution();
83        let mut faults = execution.vector();
84        distinguished(parent, element, &mut faults)?;
85        let mut inside = parent
86            .enter(element)
87            .map_err(|error| Error::encoding(error, FIELD))?;
88        let mut fields = Fields::default();
89        // Only first successful schema fields require duplicate records: ten
90        // fixed slots, never an unaccounted input-driven Vec of encodings.
91        let mut seen: [Option<Element<'a>>; 10] = [None; 10];
92        let mut last = None;
93        while let Some(element) = inside
94            .next_element()
95            .map_err(|error| Error::encoding(error, FIELD))?
96        {
97            execution
98                .charge_work(1)
99                .map_err(|reason| Error::resource(reason, FIELD, element.span().start))?;
100            let Some(slot) = fields.slot(&inside, &element)? else {
101                if carries(element.tag()) {
102                    if !same_duplicate(&inside, &fields, &seen, &element)? {
103                        return Err(Error::schema(
104                            ErrorCode::DuplicateField,
105                            FIELD,
106                            element.span().start,
107                        ));
108                    }
109                    record(
110                        &mut faults,
111                        Error::schema(ErrorCode::DuplicateField, FIELD, element.span().start),
112                    )?;
113                } else {
114                    record(
115                        &mut faults,
116                        Error::schema(ErrorCode::UnknownField, FIELD, element.span().start),
117                    )?;
118                }
119                continue;
120            };
121            if (slot == Slot::Serial && fields.gen_time.is_some())
122                || (slot == Slot::Nonce && (fields.serial_displaced || fields.gen_time.is_none()))
123            {
124                return Err(Error::schema(
125                    ErrorCode::DuplicateField,
126                    FIELD,
127                    element.span().start,
128                ));
129            }
130            if last.is_some_and(|last| slot < last) {
131                record(
132                    &mut faults,
133                    Error::schema(ErrorCode::FieldOrder, FIELD, element.span().start),
134                )?;
135            }
136            last = Some(last.map_or(slot, |last| last.max(slot)));
137            fields.read(slot, &inside, &element, &mut faults)?;
138            seen[slot as usize] = Some(element);
139        }
140        fields.finish(element.span(), faults.into_owned())
141    }
142
143    /// Original policy OID; acceptable policy selection belongs to the caller.
144    #[must_use]
145    pub fn policy(&self) -> Oid<'a> {
146        self.policy
147    }
148    /// Original imprint fields, including explicit malformed algorithm syntax.
149    #[must_use]
150    pub fn imprint(&self) -> &MessageImprint<'a> {
151        &self.imprint
152    }
153    /// Original serial; RFC3161 imposes no positivity or 160-bit maximum.
154    #[must_use]
155    pub fn serial(&self) -> IntegerField<'a> {
156        self.serial
157    }
158    /// Shared UTC label/precision reading; supplied UTC facts remain independent.
159    #[must_use]
160    pub fn gen_time(&self) -> Reading {
161        self.gen_time
162    }
163    /// Absent complete Accuracy differs from a present empty/invalid reading.
164    #[must_use]
165    pub fn accuracy(&self) -> Option<&AccuracyReading<'a>> {
166        self.accuracy.as_ref()
167    }
168    /// Original ordering BOOLEAN, with FALSE only for its normative absent default.
169    #[must_use]
170    pub fn ordering(&self) -> bool {
171        self.ordering
172    }
173    /// Original optional nonce, without a fixed width or sign policy.
174    #[must_use]
175    pub fn nonce(&self) -> Option<IntegerField<'a>> {
176        self.nonce
177    }
178    /// Original explicit wrapper and common `GeneralName` reading or malformation.
179    #[must_use]
180    pub fn tsa(&self) -> Option<&TsaReading<'a>> {
181        self.tsa.as_ref()
182    }
183    /// Original optional extension catalogue and its own recovery diagnostics.
184    #[must_use]
185    pub fn extensions(&self) -> Option<&TokenExtensions<'a>> {
186        self.extensions.as_ref()
187    }
188    /// Root schema/DER faults; nested imprint/accuracy/extension faults stay there.
189    #[must_use]
190    pub fn faults(&self) -> &[Error] {
191        &self.faults
192    }
193    /// Any retained root or nested conformance fault, without scanning collections.
194    #[must_use]
195    pub fn has_faults(&self) -> bool {
196        !self.faults.is_empty()
197            || !self.imprint.faults().is_empty()
198            || self
199                .accuracy
200                .as_ref()
201                .is_some_and(|reading| !reading.faults().is_empty())
202            || self
203                .extensions
204                .as_ref()
205                .is_some_and(|reading| !reading.faults().is_empty())
206    }
207    /// Whole original `TSTInfo` range, never reconstructed from scalar fields.
208    #[must_use]
209    pub fn span(&self) -> Span {
210        self.span
211    }
212}
213
214fn same_duplicate(
215    parent: &Reader<'_>,
216    fields: &Fields<'_>,
217    seen: &[Option<Element<'_>>; 10],
218    element: &Element<'_>,
219) -> Result<bool> {
220    let execution = parent.execution();
221    let refusal = |reason| Error::resource(reason, FIELD, element.span().start);
222    if element.tag() == Tag::primitive(universal::OID)
223        && let Some(policy) = fields.policy
224    {
225        // RFC3161 §2.4.2 requires one DER policy; duplicate recovery is our
226        // inspection contract, not conformance. X.690 §8.1.3 length spelling
227        // does not change the decoded OID. Shared decoding validates both.
228        let other = parent
229            .oid(element)
230            .map_err(|error| Error::encoding(error, "tstInfo.policy"))?;
231        execution.charge_work(1).map_err(refusal)?;
232        if policy.as_bytes().len() != other.as_bytes().len() {
233            return Ok(false);
234        }
235        execution
236            .charge_work(policy.as_bytes().len() as u64)
237            .map_err(refusal)?;
238        return Ok(policy.as_bytes() == other.as_bytes());
239    }
240    for prior in seen.iter().flatten() {
241        execution.charge_work(1).map_err(refusal)?;
242        if prior.as_bytes().len() != element.as_bytes().len() {
243            continue;
244        }
245        execution
246            .charge_work(element.as_bytes().len() as u64)
247            .map_err(refusal)?;
248        if prior.as_bytes() == element.as_bytes() {
249            return Ok(true);
250        }
251    }
252    Ok(false)
253}
254
255#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
256#[repr(usize)]
257enum Slot {
258    Version,
259    Policy,
260    Imprint,
261    Serial,
262    GenTime,
263    Accuracy,
264    Ordering,
265    Nonce,
266    Tsa,
267    Extensions,
268}
269
270#[derive(Default)]
271struct Fields<'a> {
272    version: bool,
273    policy: Option<Oid<'a>>,
274    imprint: Option<MessageImprint<'a>>,
275    serial: Option<IntegerField<'a>>,
276    serial_displaced: bool,
277    gen_time: Option<Reading>,
278    accuracy: Option<AccuracyReading<'a>>,
279    ordering: Option<bool>,
280    nonce: Option<IntegerField<'a>>,
281    tsa: Option<TsaReading<'a>>,
282    extensions: Option<TokenExtensions<'a>>,
283}
284
285impl<'a> Fields<'a> {
286    fn slot(&self, parent: &Reader<'a>, element: &Element<'a>) -> Result<Option<Slot>> {
287        let tag = element.tag();
288        let slot = if tag == Tag::primitive(universal::INTEGER) {
289            if !self.version {
290                // RFC3161 §2.4.2 orders version before policy and imprint.
291                // Recovery is not specified: once those anchors occurred,
292                // this INTEGER also admits serial with missing version. Never
293                // assign one of the competing readings merely by first slot.
294                // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
295                if self.policy.is_some() || self.imprint.is_some() || self.gen_time.is_some() {
296                    return Err(Error::schema(
297                        ErrorCode::DuplicateField,
298                        FIELD,
299                        element.span().start,
300                    ));
301                }
302                Slot::Version
303            } else if self.serial.is_none() {
304                Slot::Serial
305            } else if self.nonce.is_none() {
306                Slot::Nonce
307            } else {
308                return Ok(None);
309            }
310        } else if tag == Tag::primitive(universal::OID) {
311            Slot::Policy
312        } else if tag == Tag::constructed(universal::SEQUENCE) {
313            let mut probe = parent
314                .enter(element)
315                .map_err(|error| Error::encoding(error, FIELD))?;
316            let first = probe
317                .next_element()
318                .map_err(|error| Error::encoding(error, FIELD))?;
319            // A bare OID + hash has one malformed imprint reading; it must not
320            // occupy Accuracy and erase required serial/genTime at finish.
321            if first.is_some_and(|first| {
322                first.tag() == Tag::constructed(universal::SEQUENCE)
323                    || first.tag() == Tag::primitive(universal::OID)
324            }) {
325                Slot::Imprint
326            } else {
327                Slot::Accuracy
328            }
329        } else if tag == Tag::primitive(universal::GENERALIZED_TIME)
330            || tag == Tag::constructed(universal::GENERALIZED_TIME)
331        {
332            Slot::GenTime
333        } else if tag == Tag::primitive(universal::BOOLEAN) {
334            Slot::Ordering
335        } else if tag == Tag::context(true, 0) {
336            Slot::Tsa
337        } else if tag == Tag::context(true, 1) {
338            Slot::Extensions
339        } else {
340            return Ok(None);
341        };
342        let found = match slot {
343            Slot::Version => self.version,
344            Slot::Policy => self.policy.is_some(),
345            Slot::Imprint => self.imprint.is_some(),
346            Slot::Serial => self.serial.is_some(),
347            Slot::GenTime => self.gen_time.is_some(),
348            Slot::Accuracy => self.accuracy.is_some(),
349            Slot::Ordering => self.ordering.is_some(),
350            Slot::Nonce => self.nonce.is_some(),
351            Slot::Tsa => self.tsa.is_some(),
352            Slot::Extensions => self.extensions.is_some(),
353        };
354        Ok(if found { None } else { Some(slot) })
355    }
356
357    fn read<W: Work>(
358        &mut self,
359        slot: Slot,
360        parent: &Reader<'a>,
361        element: &Element<'a>,
362        faults: &mut OwnedVec<Error, W>,
363    ) -> Result<()> {
364        match slot {
365            Slot::Version => {
366                let version = IntegerField::read(parent, element, "tstInfo.version", faults)?;
367                if version.value.as_u64() != Some(1) {
368                    return Err(Error::schema(
369                        ErrorCode::UnsupportedVersion,
370                        "tstInfo.version",
371                        element.span().start,
372                    ));
373                }
374                self.version = true;
375            }
376            Slot::Policy => {
377                self.policy = Some(
378                    parent
379                        .oid(element)
380                        .map_err(|error| Error::encoding(error, "tstInfo.policy"))?,
381                );
382            }
383            Slot::Imprint => self.imprint = Some(MessageImprint::read(parent, element)?),
384            Slot::Serial => {
385                self.serial_displaced = self.imprint.is_none() || self.gen_time.is_some();
386                self.serial = Some(IntegerField::read(
387                    parent,
388                    element,
389                    "tstInfo.serialNumber",
390                    faults,
391                )?);
392            }
393            Slot::GenTime => {
394                self.gen_time = Some(
395                    structured_pkix::temporal::read_generalized(parent, element, "tstInfo.genTime")
396                        .map_err(Error::pkix)?,
397                );
398            }
399            Slot::Accuracy => self.accuracy = Some(AccuracyReading::read(parent, element)?),
400            Slot::Ordering => {
401                let ordering = parent
402                    .boolean(element)
403                    .map_err(|error| Error::encoding(error, "tstInfo.ordering"))?;
404                if !ordering {
405                    record(
406                        faults,
407                        Error::schema(
408                            ErrorCode::DefaultWritten,
409                            "tstInfo.ordering",
410                            element.span().start,
411                        ),
412                    )?;
413                }
414                self.ordering = Some(ordering);
415            }
416            Slot::Nonce => {
417                self.nonce = Some(IntegerField::read(
418                    parent,
419                    element,
420                    "tstInfo.nonce",
421                    faults,
422                )?);
423            }
424            Slot::Tsa => self.tsa = Some(TsaReading::read(parent, element, faults)?),
425            Slot::Extensions => self.extensions = Some(TokenExtensions::read(parent, element)?),
426        }
427        Ok(())
428    }
429
430    fn finish(self, span: Span, faults: OwnedVec<Error>) -> Result<TokenInfo<'a>> {
431        let missing = |field| Error::schema(ErrorCode::MissingField, field, span.start);
432        if !self.version {
433            return Err(missing("tstInfo.version"));
434        }
435        Ok(TokenInfo {
436            policy: self.policy.ok_or_else(|| missing("tstInfo.policy"))?,
437            imprint: self
438                .imprint
439                .ok_or_else(|| missing("tstInfo.messageImprint"))?,
440            serial: self.serial.ok_or_else(|| missing("tstInfo.serialNumber"))?,
441            gen_time: self.gen_time.ok_or_else(|| missing("tstInfo.genTime"))?,
442            accuracy: self.accuracy,
443            ordering: self.ordering.unwrap_or(false),
444            nonce: self.nonce,
445            tsa: self.tsa,
446            extensions: self.extensions,
447            faults,
448            span,
449        })
450    }
451}
452
453fn carries(tag: Tag) -> bool {
454    tag == Tag::primitive(universal::INTEGER)
455        || tag == Tag::primitive(universal::OID)
456        || tag == Tag::constructed(universal::SEQUENCE)
457        || tag == Tag::primitive(universal::GENERALIZED_TIME)
458        || tag == Tag::constructed(universal::GENERALIZED_TIME)
459        || tag == Tag::primitive(universal::BOOLEAN)
460        || tag == Tag::context(true, 0)
461        || tag == Tag::context(true, 1)
462}
463
464fn distinguished<W: Work>(
465    parent: &Reader<'_>,
466    element: &Element<'_>,
467    faults: &mut OwnedVec<Error, W>,
468) -> Result<()> {
469    let mut strict = parent
470        .reread(element, Encoding::Der)
471        .map_err(|error| Error::encoding(error, FIELD))?;
472    let result = strict
473        .expect(element.tag())
474        .map_err(|error| Error::encoding(error, FIELD))
475        .and_then(|again| {
476            structured_pkix::der::conforms(&strict, &again, FIELD).map_err(Error::pkix)
477        });
478    match result {
479        Ok(()) => Ok(()),
480        Err(error) => record(faults, error),
481    }
482}