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    // RFC3161 §2.4.2 orders the INTEGER roles. Here all three roles are
241    // populated, so another INTEGER can only repeat nonce. Standard-silent
242    // recovery must not rescue a conflicting nonce by matching version/serial.
243    // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
244    let applicable = if element.tag() == Tag::primitive(universal::INTEGER) {
245        &seen[Slot::Nonce as usize..=Slot::Nonce as usize]
246    } else {
247        &seen[..]
248    };
249    for prior in applicable.iter().flatten() {
250        execution.charge_work(1).map_err(refusal)?;
251        if prior.as_bytes().len() != element.as_bytes().len() {
252            continue;
253        }
254        execution
255            .charge_work(element.as_bytes().len() as u64)
256            .map_err(refusal)?;
257        if prior.as_bytes() == element.as_bytes() {
258            return Ok(true);
259        }
260    }
261    Ok(false)
262}
263
264#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
265#[repr(usize)]
266enum Slot {
267    Version,
268    Policy,
269    Imprint,
270    Serial,
271    GenTime,
272    Accuracy,
273    Ordering,
274    Nonce,
275    Tsa,
276    Extensions,
277}
278
279#[derive(Default)]
280struct Fields<'a> {
281    version: bool,
282    policy: Option<Oid<'a>>,
283    imprint: Option<MessageImprint<'a>>,
284    serial: Option<IntegerField<'a>>,
285    serial_displaced: bool,
286    gen_time: Option<Reading>,
287    accuracy: Option<AccuracyReading<'a>>,
288    ordering: Option<bool>,
289    nonce: Option<IntegerField<'a>>,
290    tsa: Option<TsaReading<'a>>,
291    extensions: Option<TokenExtensions<'a>>,
292}
293
294impl<'a> Fields<'a> {
295    fn slot(&self, parent: &Reader<'a>, element: &Element<'a>) -> Result<Option<Slot>> {
296        let tag = element.tag();
297        let slot = if tag == Tag::primitive(universal::INTEGER) {
298            if !self.version {
299                // RFC3161 §2.4.2 orders version before policy and imprint.
300                // Recovery is not specified: once those anchors occurred,
301                // this INTEGER also admits serial with missing version. Never
302                // assign one of the competing readings merely by first slot.
303                // https://www.rfc-editor.org/rfc/rfc3161#section-2.4.2
304                if self.policy.is_some() || self.imprint.is_some() || self.gen_time.is_some() {
305                    return Err(Error::schema(
306                        ErrorCode::DuplicateField,
307                        FIELD,
308                        element.span().start,
309                    ));
310                }
311                Slot::Version
312            } else if self.serial.is_none() {
313                Slot::Serial
314            } else if self.nonce.is_none() {
315                Slot::Nonce
316            } else {
317                return Ok(None);
318            }
319        } else if tag == Tag::primitive(universal::OID) {
320            Slot::Policy
321        } else if tag == Tag::constructed(universal::SEQUENCE) {
322            let mut probe = parent
323                .enter(element)
324                .map_err(|error| Error::encoding(error, FIELD))?;
325            let first = probe
326                .next_element()
327                .map_err(|error| Error::encoding(error, FIELD))?;
328            // A bare OID + hash has one malformed imprint reading; it must not
329            // occupy Accuracy and erase required serial/genTime at finish.
330            if first.is_some_and(|first| {
331                first.tag() == Tag::constructed(universal::SEQUENCE)
332                    || first.tag() == Tag::primitive(universal::OID)
333            }) {
334                Slot::Imprint
335            } else {
336                Slot::Accuracy
337            }
338        } else if tag == Tag::primitive(universal::GENERALIZED_TIME)
339            || tag == Tag::constructed(universal::GENERALIZED_TIME)
340        {
341            Slot::GenTime
342        } else if tag == Tag::primitive(universal::BOOLEAN) {
343            Slot::Ordering
344        } else if tag == Tag::context(true, 0) {
345            Slot::Tsa
346        } else if tag == Tag::context(true, 1) {
347            Slot::Extensions
348        } else {
349            return Ok(None);
350        };
351        let found = match slot {
352            Slot::Version => self.version,
353            Slot::Policy => self.policy.is_some(),
354            Slot::Imprint => self.imprint.is_some(),
355            Slot::Serial => self.serial.is_some(),
356            Slot::GenTime => self.gen_time.is_some(),
357            Slot::Accuracy => self.accuracy.is_some(),
358            Slot::Ordering => self.ordering.is_some(),
359            Slot::Nonce => self.nonce.is_some(),
360            Slot::Tsa => self.tsa.is_some(),
361            Slot::Extensions => self.extensions.is_some(),
362        };
363        Ok(if found { None } else { Some(slot) })
364    }
365
366    fn read<W: Work>(
367        &mut self,
368        slot: Slot,
369        parent: &Reader<'a>,
370        element: &Element<'a>,
371        faults: &mut OwnedVec<Error, W>,
372    ) -> Result<()> {
373        match slot {
374            Slot::Version => {
375                let version = IntegerField::read(parent, element, "tstInfo.version", faults)?;
376                if version.value.as_u64() != Some(1) {
377                    return Err(Error::schema(
378                        ErrorCode::UnsupportedVersion,
379                        "tstInfo.version",
380                        element.span().start,
381                    ));
382                }
383                self.version = true;
384            }
385            Slot::Policy => {
386                self.policy = Some(
387                    parent
388                        .oid(element)
389                        .map_err(|error| Error::encoding(error, "tstInfo.policy"))?,
390                );
391            }
392            Slot::Imprint => self.imprint = Some(MessageImprint::read(parent, element)?),
393            Slot::Serial => {
394                self.serial_displaced = self.imprint.is_none() || self.gen_time.is_some();
395                self.serial = Some(IntegerField::read(
396                    parent,
397                    element,
398                    "tstInfo.serialNumber",
399                    faults,
400                )?);
401            }
402            Slot::GenTime => {
403                self.gen_time = Some(
404                    structured_pkix::temporal::read_generalized(parent, element, "tstInfo.genTime")
405                        .map_err(Error::pkix)?,
406                );
407            }
408            Slot::Accuracy => self.accuracy = Some(AccuracyReading::read(parent, element)?),
409            Slot::Ordering => {
410                let ordering = parent
411                    .boolean(element)
412                    .map_err(|error| Error::encoding(error, "tstInfo.ordering"))?;
413                if !ordering {
414                    record(
415                        faults,
416                        Error::schema(
417                            ErrorCode::DefaultWritten,
418                            "tstInfo.ordering",
419                            element.span().start,
420                        ),
421                    )?;
422                }
423                self.ordering = Some(ordering);
424            }
425            Slot::Nonce => {
426                self.nonce = Some(IntegerField::read(
427                    parent,
428                    element,
429                    "tstInfo.nonce",
430                    faults,
431                )?);
432            }
433            Slot::Tsa => self.tsa = Some(TsaReading::read(parent, element, faults)?),
434            Slot::Extensions => self.extensions = Some(TokenExtensions::read(parent, element)?),
435        }
436        Ok(())
437    }
438
439    fn finish(self, span: Span, faults: OwnedVec<Error>) -> Result<TokenInfo<'a>> {
440        let missing = |field| Error::schema(ErrorCode::MissingField, field, span.start);
441        if !self.version {
442            return Err(missing("tstInfo.version"));
443        }
444        Ok(TokenInfo {
445            policy: self.policy.ok_or_else(|| missing("tstInfo.policy"))?,
446            imprint: self
447                .imprint
448                .ok_or_else(|| missing("tstInfo.messageImprint"))?,
449            serial: self.serial.ok_or_else(|| missing("tstInfo.serialNumber"))?,
450            gen_time: self.gen_time.ok_or_else(|| missing("tstInfo.genTime"))?,
451            accuracy: self.accuracy,
452            ordering: self.ordering.unwrap_or(false),
453            nonce: self.nonce,
454            tsa: self.tsa,
455            extensions: self.extensions,
456            faults,
457            span,
458        })
459    }
460}
461
462fn carries(tag: Tag) -> bool {
463    tag == Tag::primitive(universal::INTEGER)
464        || tag == Tag::primitive(universal::OID)
465        || tag == Tag::constructed(universal::SEQUENCE)
466        || tag == Tag::primitive(universal::GENERALIZED_TIME)
467        || tag == Tag::constructed(universal::GENERALIZED_TIME)
468        || tag == Tag::primitive(universal::BOOLEAN)
469        || tag == Tag::context(true, 0)
470        || tag == Tag::context(true, 1)
471}
472
473fn distinguished<W: Work>(
474    parent: &Reader<'_>,
475    element: &Element<'_>,
476    faults: &mut OwnedVec<Error, W>,
477) -> Result<()> {
478    let mut strict = parent
479        .reread(element, Encoding::Der)
480        .map_err(|error| Error::encoding(error, FIELD))?;
481    let result = strict
482        .expect(element.tag())
483        .map_err(|error| Error::encoding(error, FIELD))
484        .and_then(|again| {
485            structured_pkix::der::conforms(&strict, &again, FIELD).map_err(Error::pkix)
486        });
487    match result {
488        Ok(()) => Ok(()),
489        Err(error) => record(faults, error),
490    }
491}