Skip to main content

structured_ltv/material/
index.rs

1//! Exact original artifact availability, distinct from validation and trust.
2
3use super::{Coverage, CoverageError};
4use crate::pki::CheckedPath;
5use crate::source::{Location, Query, ScopeError, Snapshot};
6use crate::timestamp::{Purpose, Verified};
7use core::hash::Hash;
8use core::marker::PhantomData;
9use structured_asn1::memory::{HashWork, MemoryError, OwnedMap, OwnedVec, Work};
10use structured_pkix::{Certificate, crl::CertificateList, ocsp::BasicResponse};
11
12#[derive(Clone, Copy, PartialEq, Eq, Hash)]
13pub(super) enum Kind {
14    Certificate,
15    Crl,
16    Basic,
17}
18
19/// Exact identity of one original artifact: its kind and original DER. Two
20/// separately parsed copies of the same value have the same identity.
21#[derive(Clone, Copy, PartialEq, Eq, Hash)]
22pub struct ArtifactKey<'source> {
23    pub(super) kind: Kind,
24    pub(super) bytes: &'source [u8],
25}
26
27impl HashWork for ArtifactKey<'_> {
28    fn hash_work(&self) -> Result<u64, MemoryError> {
29        u64::try_from(self.bytes.len())
30            .map_err(|_| MemoryError::WorkExceeded)?
31            .checked_add(2)
32            .ok_or(MemoryError::WorkExceeded)
33    }
34}
35
36/// Exact identity of a revocation choice carried in a timestamp token. These
37/// ordinals do not address the format's own material pool.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub struct CarriedOrigin {
40    /// Ordinal in the frozen timestamp token-source set.
41    pub token: usize,
42    /// Original choice ordinal inside that token's `SignedData` revocation set.
43    pub choice: usize,
44}
45
46/// One actual location of an original certificate or status artifact. No path,
47/// authorization, freshness, reason coverage or attained level is asserted.
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum ArtifactSource {
50    /// Ordinal in the format's frozen material sources/values pool.
51    Material(usize),
52    /// Certificate already parsed in an original timestamp `SignedData`.
53    TokenCertificate {
54        /// Ordinal in the frozen timestamp source pool.
55        token: usize,
56        /// Ordinal in that message's parsed X.509 certificate catalogue.
57        certificate: usize,
58    },
59    /// Original revocation choice in the separate timestamp source pool.
60    TokenRevocation(CarriedOrigin),
61}
62
63/// One original occurrence, with an explicit source-pool discriminator and the
64/// format's own placement of the value.
65#[derive(Debug, Clone, Copy)]
66pub struct Occurrence<N, D, G, P> {
67    source: ArtifactSource,
68    location: Location<N, D, G>,
69    placement: P,
70    embedded_certificate: Option<usize>,
71}
72
73impl<N: Copy, D: Copy, G: Copy, P: Copy> Occurrence<N, D, G, P> {
74    /// A direct occurrence of a certificate, CRL or whole OCSP value.
75    #[must_use]
76    pub fn new(source: ArtifactSource, location: Location<N, D, G>, placement: P) -> Self {
77        Self {
78            source,
79            location,
80            placement,
81            embedded_certificate: None,
82        }
83    }
84    /// Actual original source pool and its ordinal, never a shared ambiguous
85    /// integer for format material and timestamp token sources.
86    #[must_use]
87    pub fn source(&self) -> ArtifactSource {
88        self.source
89    }
90    /// Original enclosure of the whole material value.
91    #[must_use]
92    pub fn location(&self) -> Location<N, D, G> {
93        self.location
94    }
95    /// Actual container role, not a permission to use it for every actor.
96    #[must_use]
97    pub fn placement(&self) -> P {
98        self.placement
99    }
100    /// Certificate ordinal inside an OCSP basic response, if the artifact is
101    /// embedded there. None means a direct certificate/status value.
102    #[must_use]
103    pub fn embedded_certificate(&self) -> Option<usize> {
104        self.embedded_certificate
105    }
106}
107
108struct Entry<N, D, G, P> {
109    value: Occurrence<N, D, G, P>,
110    next: Option<usize>,
111}
112
113/// Accounted scoped index of every original occurrence. Keys borrow original
114/// DER, not copied/normalized certificate names or unqualified fingerprints.
115/// The same artifact in several containers keeps ALL locations, so later
116/// archive/actor checks cannot mistake a late or disallowed copy for coverage.
117///
118/// `P` is the format's placement of an occurrence and `O` an entry of the
119/// format's schema-open catalogue; the index stores and returns both unread.
120/// Only the holder of issuer `I` builds it from the format's own pools, and it
121/// locates material only under coverage and evidence of the same issuer.
122pub struct Index<'source, N, D, G, P, O, W: Work, I> {
123    snapshot: Snapshot<D, G>,
124    signature: N,
125    keys: OwnedMap<ArtifactKey<'source>, (usize, usize), W>,
126    entries: OwnedVec<Entry<N, D, G, P>, W>,
127    open: &'source [O],
128    issuer: PhantomData<I>,
129}
130
131/// Original source refusal is independent of index capacity/work refusal.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum IndexError<N> {
134    /// Source generation or current-operation failure.
135    Scope(ScopeError<N>),
136    /// A supplied view/index belongs to another original source generation.
137    Foreign {
138        /// Original selected signature.
139        signature: N,
140    },
141    /// Index hashing/probes/storage or occurrence traversal was not admitted.
142    Resource {
143        /// Original selected signature.
144        signature: N,
145        /// Original resource cause.
146        cause: MemoryError,
147    },
148}
149
150/// One original parsed artifact to locate. This is a source reference, not a
151/// receipt of authentication, applicability, freshness or actor membership.
152#[derive(Debug, Clone, Copy)]
153pub enum ArtifactRef<'view, 'source> {
154    /// Exact original certificate, including a separately supplied anchor.
155    Certificate(&'view Certificate<'source>),
156    /// Exact whole complete or delta CRL. A bound pair requires both artifacts.
157    Crl(&'view CertificateList<'source>),
158    /// Exact basic response; only a retained whole `OCSPResponse` containing it
159    /// can satisfy lookup, not an unwrapped basic response injected by a caller.
160    Ocsp(&'view BasicResponse<'source>),
161}
162
163impl<'source> ArtifactRef<'_, 'source> {
164    /// Exact identity of the referenced artifact, borrowing its original DER.
165    #[must_use]
166    pub fn identity(self) -> ArtifactKey<'source> {
167        match self {
168            Self::Certificate(value) => ArtifactKey {
169                kind: Kind::Certificate,
170                bytes: value.as_bytes(),
171            },
172            Self::Crl(value) => ArtifactKey {
173                kind: Kind::Crl,
174                bytes: value.as_bytes(),
175            },
176            Self::Ocsp(value) => ArtifactKey {
177                kind: Kind::Basic,
178                bytes: value.as_bytes(),
179            },
180        }
181    }
182}
183
184/// Exact original artifact availability under one checked archive input.
185#[derive(Debug)]
186pub enum Archived<N, D, G, P> {
187    /// No exact artifact was incorporated in either original source pool.
188    Missing,
189    /// Exact copies exist, but none is covered by this particular archive input.
190    /// A later property or the archive token's own material cannot repair it.
191    Uncovered,
192    /// At least one exact incorporated copy is mathematically covered.
193    Present(Occurrence<N, D, G, P>),
194}
195
196/// Exact position of a certificate in a supplied, actually checked path.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum PathCertificate {
199    /// Explicitly supplied trust-anchor certificate, if one exists. This is
200    /// material availability only, never a requirement to revoke an anchor.
201    Anchor,
202    /// Issuer-to-subject ordinal in the checked non-anchor member catalogue.
203    Member(usize),
204}
205
206/// One checked-path certificate's mathematical availability before an archive.
207#[derive(Debug)]
208pub struct ArchivedCertificate<N, D, G, P> {
209    /// Original role; no caller-supplied certificate count is used.
210    pub role: PathCertificate,
211    /// Exact copies are searched using the same archive coverage receipt.
212    pub availability: Archived<N, D, G, P>,
213}
214
215/// Complete certificate census for one actual checked path and archive input.
216/// It deliberately retains its path: ordinals cannot be relabelled as another
217/// actor after this check. Status artifacts and actor association are separate.
218pub struct PathCertificates<'path, 'document, N, D, G, P> {
219    path: &'path CheckedPath<'document>,
220    archive: Location<N, D, G>,
221    entries: OwnedVec<ArchivedCertificate<N, D, G, P>>,
222    all_covered: bool,
223}
224
225impl<'path, 'document, N: Copy, D: Copy, G: Copy, P>
226    PathCertificates<'path, 'document, N, D, G, P>
227{
228    /// Same actual common-validator path, not an independent artifact list.
229    #[must_use]
230    pub fn path(&self) -> &'path CheckedPath<'document> {
231        self.path
232    }
233    /// Original archive token whose actual input coverage was used.
234    #[must_use]
235    pub fn archive(&self) -> Location<N, D, G> {
236        self.archive
237    }
238    /// Every existing anchor/member certificate, including missing late copies.
239    #[must_use]
240    pub fn entries(&self) -> &[ArchivedCertificate<N, D, G, P>] {
241        &self.entries
242    }
243    /// Whether every certificate in this path has an exact covered copy.
244    /// This alone proves neither status completeness nor any baseline level.
245    #[must_use]
246    pub fn all_certificates_covered(&self) -> bool {
247        self.all_covered
248    }
249}
250
251/// Original source/resource failure, separately from missing archive material.
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum ArchiveError<N> {
254    /// Exact artifact hashing, lookup or source traversal refused.
255    Index(IndexError<N>),
256    /// The mathematical input coverage/source binding could not be established.
257    Coverage(CoverageError<N>),
258    /// The supplied successful token covers a different kind of input.
259    NotArchive {
260        /// Actual original timestamp property.
261        property: N,
262    },
263}
264
265impl<'source, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, I>
266    Index<'source, N, D, G, P, O, W, I>
267{
268    /// An empty index for this signature in the query's source generation.
269    ///
270    /// `open` is the format's schema-open material catalogue, retained only so
271    /// that format checks find it beside the index. Requiring the issuer value
272    /// keeps a caller of the format from building an index of its own.
273    pub fn new<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
274        _issuer: I,
275        signature: N,
276        open: &'source [O],
277        query: &Q,
278    ) -> Result<Self, IndexError<N>> {
279        query.admit(signature, 1).map_err(IndexError::Scope)?;
280        let execution = query.execution();
281        Ok(Self {
282            snapshot: query.snapshot(),
283            signature,
284            keys: execution.map(),
285            entries: execution.vector(),
286            open,
287            issuer: PhantomData,
288        })
289    }
290
291    /// Records one occurrence of an exact original artifact. For an OCSP value
292    /// every certificate embedded in its basic response is recorded too, with
293    /// the same location and placement. Hashing, probes and storage are
294    /// admitted first; a refusal leaves the index for the caller to drop. The
295    /// builder pays its own traversal of the format's pools with [`Self::admit`].
296    /// Requiring the issuer value keeps a caller of the format, which may own
297    /// the index, from adding material the document does not carry.
298    pub fn insert<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
299        &mut self,
300        _issuer: I,
301        artifact: ArtifactRef<'_, 'source>,
302        occurrence: Occurrence<N, D, G, P>,
303        query: &Q,
304    ) -> Result<(), IndexError<N>> {
305        // Coverage lookups compare only the occurrence's node, so its source
306        // generation must already be this index's.
307        if occurrence.location.snapshot != self.snapshot || query.snapshot() != self.snapshot {
308            return Err(IndexError::Foreign {
309                signature: self.signature,
310            });
311        }
312        self.insert_key(artifact.identity(), occurrence, query)?;
313        if let ArtifactRef::Ocsp(response) = artifact {
314            for certificate in 0..response.certificates().len() {
315                self.admit(query)?;
316                self.insert_key(
317                    ArtifactKey {
318                        kind: Kind::Certificate,
319                        bytes: response.certificates()[certificate].as_bytes(),
320                    },
321                    Occurrence {
322                        embedded_certificate: Some(certificate),
323                        ..occurrence
324                    },
325                    query,
326                )?;
327            }
328        }
329        Ok(())
330    }
331
332    /// The format's schema-open catalogue, as supplied.
333    #[must_use]
334    pub fn open(&self) -> &'source [O] {
335        self.open
336    }
337
338    /// Original selected signature of this index.
339    #[must_use]
340    pub fn signature(&self) -> N {
341        self.signature
342    }
343
344    /// Source generation of every occurrence.
345    #[must_use]
346    pub fn snapshot(&self) -> Snapshot<D, G> {
347        self.snapshot
348    }
349
350    /// Finds all copies of this exact original certificate, including copies
351    /// inside OCSP. A match is availability only, never authentication/trust.
352    pub fn certificate<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
353        &'view self,
354        value: &Certificate<'_>,
355        query: &'query Q,
356    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
357        self.find(Kind::Certificate, value.as_bytes(), query)
358    }
359    /// Finds all whole CRL copies. An authenticated delta still needs its
360    /// authenticated/bound complete source; finding either alone proves no pair.
361    pub fn crl<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
362        &'view self,
363        value: &CertificateList<'_>,
364        query: &'query Q,
365    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
366        self.find(Kind::Crl, value.as_bytes(), query)
367    }
368    /// Finds the exact basic response only in retained whole OCSP envelopes.
369    /// Good-looking status or a successful wire code does not mint evidence.
370    pub fn ocsp<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
371        &'view self,
372        value: &BasicResponse<'_>,
373        query: &'query Q,
374    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
375        self.find(Kind::Basic, value.as_bytes(), query)
376    }
377
378    /// Finds all copies of this exact artifact, in source catalogue order.
379    pub fn artifact<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
380        &'view self,
381        artifact: ArtifactRef<'_, '_>,
382        query: &'query Q,
383    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
384        let key = artifact.identity();
385        self.find(key.kind, key.bytes, query)
386    }
387
388    /// Requires that this index was built for `signature`.
389    pub fn require_signature<Q: Query<Id = N, Document = D, Generation = G>>(
390        &self,
391        signature: N,
392        query: &Q,
393    ) -> Result<(), IndexError<N>> {
394        self.admit(query)?;
395        let same =
396            query
397                .equal(self.signature, signature)
398                .map_err(|cause| IndexError::Resource {
399                    signature: self.signature,
400                    cause,
401                })?;
402        if !same {
403            return Err(IndexError::Foreign { signature });
404        }
405        Ok(())
406    }
407
408    /// Locates a whole original artifact in this exact checked archive input.
409    ///
410    /// EN319132-1 §6.3(aa)/§5.5.2.2 requires the necessary material to have
411    /// been incorporated before generating each archive timestamp. This uses
412    /// the same selection the format used for actual timestamp input, so merely
413    /// finding a late or self-carried copy cannot count as covered. All copies
414    /// remain available; an early uncovered duplicate does not hide a covered
415    /// one. This proves mathematical availability only. The caller must derive
416    /// required artifacts from actual actor/path/status checks and must
417    /// authenticate the TSA separately before claiming a baseline level.
418    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
419    pub fn archived<Q: Query<Id = N, Document = D, Generation = G>, C: Work>(
420        &self,
421        artifact: ArtifactRef<'_, '_>,
422        coverage: &Coverage<N, D, G, C, I>,
423        evidence: &Verified<N, D, G, I>,
424        query: &Q,
425    ) -> Result<Archived<N, D, G, P>, ArchiveError<N>>
426    where
427        N: HashWork,
428    {
429        if evidence.purpose() != Purpose::Archive {
430            return Err(ArchiveError::NotArchive {
431                property: evidence.property(),
432            });
433        }
434        // Validate even empty/missing catalogues before deriving an absence.
435        if evidence.token().snapshot != self.snapshot {
436            return Err(ArchiveError::Index(IndexError::Foreign {
437                signature: self.signature,
438            }));
439        }
440        self.require_signature(evidence.signature(), query)
441            .map_err(ArchiveError::Index)?;
442        coverage
443            .require_evidence(evidence, evidence.property(), query)
444            .map_err(ArchiveError::Coverage)?;
445        let key = artifact.identity();
446        let copies = self
447            .find(key.kind, key.bytes, query)
448            .map_err(ArchiveError::Index)?;
449        let mut found = false;
450        for copy in copies {
451            let copy = copy.map_err(ArchiveError::Index)?;
452            found = true;
453            // The immutable coverage/evidence pair was bound once above;
454            // each occurrence still checks the current source and lookup.
455            if coverage
456                .selects(copy.location.node, query)
457                .map_err(ArchiveError::Coverage)?
458            {
459                return Ok(Archived::Present(copy));
460            }
461        }
462        Ok(if found {
463            Archived::Uncovered
464        } else {
465            Archived::Missing
466        })
467    }
468
469    /// Derives certificate requirements from the actual common-validator path,
470    /// including an existing anchor certificate, then checks each exact artifact
471    /// against this archive's mathematical input. Missing/late copies remain
472    /// facts; resource/source failure returns no successful partial census.
473    ///
474    /// EN319132-1 V1.3.1 §§5.4.2/5.5.2.2/6.3(aa) require the material of the
475    /// actual actors before each archive. This certificate component does not
476    /// assert that status, credential, countersigner or previous-TSA processing
477    /// completed. The caller must supply their separately bound paths.
478    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
479    pub fn archived_path<'path, 'document, Q, C: Work>(
480        &self,
481        path: &'path CheckedPath<'document>,
482        coverage: &Coverage<N, D, G, C, I>,
483        evidence: &Verified<N, D, G, I>,
484        query: &Q,
485    ) -> Result<PathCertificates<'path, 'document, N, D, G, P>, ArchiveError<N>>
486    where
487        N: HashWork,
488        Q: Query<Id = N, Document = D, Generation = G>,
489    {
490        query
491            .admit(self.signature, 1)
492            .map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
493        let mut entries = query.execution().vector();
494        let mut all_covered = true;
495        let mut record = |certificate, role| {
496            let availability = self.archived(
497                ArtifactRef::Certificate(certificate),
498                coverage,
499                evidence,
500                query,
501            )?;
502            if !matches!(availability, Archived::Present(_)) {
503                all_covered = false;
504            }
505            entries
506                .try_push(ArchivedCertificate { role, availability })
507                .map_err(|cause| {
508                    ArchiveError::Index(IndexError::Resource {
509                        signature: self.signature,
510                        cause,
511                    })
512                })
513        };
514        if let Some(certificate) = path.anchor().certificate() {
515            record(certificate, PathCertificate::Anchor)?;
516        }
517        for (ordinal, member) in path.members().iter().enumerate() {
518            self.admit(query).map_err(ArchiveError::Index)?;
519            record(member.certificate(), PathCertificate::Member(ordinal))?;
520        }
521        Ok(PathCertificates {
522            path,
523            archive: evidence.token(),
524            entries: entries.into_owned(),
525            all_covered,
526        })
527    }
528
529    /// Admits one traversal step over this index's source generation.
530    pub fn admit<Q: Query<Id = N, Document = D, Generation = G>>(
531        &self,
532        query: &Q,
533    ) -> Result<(), IndexError<N>> {
534        if self.snapshot != query.snapshot() {
535            return Err(IndexError::Foreign {
536                signature: self.signature,
537            });
538        }
539        query.admit(self.signature, 2).map_err(IndexError::Scope)
540    }
541
542    fn insert_key<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
543        &mut self,
544        key: ArtifactKey<'source>,
545        value: Occurrence<N, D, G, P>,
546        query: &Q,
547    ) -> Result<(), IndexError<N>> {
548        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
549        let resource = |cause| IndexError::Resource {
550            signature: self.signature,
551            cause,
552        };
553        let previous = self
554            .keys
555            .get(&key, query.execution())
556            .map(Option::<&(usize, usize)>::copied)
557            .map_err(resource)?;
558        let next = self.entries.len();
559        self.entries
560            .try_push(Entry { value, next: None })
561            .map_err(resource)?;
562        let head = previous.map_or(next, |(head, _)| head);
563        self.keys.try_insert(key, (head, next)).map_err(resource)?;
564        if let Some((_, tail)) = previous {
565            self.entries[tail].next = Some(next);
566        }
567        Ok(())
568    }
569
570    fn find<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
571        &'view self,
572        kind: Kind,
573        bytes: &[u8],
574        query: &'query Q,
575    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
576        self.admit(query)?;
577        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
578        let next = self
579            .keys
580            .get(&ArtifactKey { kind, bytes }, query.execution())
581            .map(|value| value.map(|&(head, _)| head))
582            .map_err(|cause| IndexError::Resource {
583                signature: self.signature,
584                cause,
585            })?;
586        Ok(Occurrences {
587            index: self,
588            query,
589            next,
590        })
591    }
592}
593
594/// Every copy of one artifact, or the refusal to look it up.
595pub type Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> =
596    Result<Occurrences<'view, 'query, 'source, N, D, G, P, O, W, Q, I>, IndexError<N>>;
597
598/// Source-catalogue-order traversal of every copy; current Work pays metadata
599/// access even when index storage belongs to an earlier execution. The first
600/// source/resource refusal is returned once and fuses the iterator.
601pub struct Occurrences<'view, 'query, 'source, N, D, G, P, O, W: Work, Q, I> {
602    index: &'view Index<'source, N, D, G, P, O, W, I>,
603    query: &'query Q,
604    next: Option<usize>,
605}
606
607impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> Iterator
608    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
609where
610    Q: Query<Id = N, Document = D, Generation = G>,
611{
612    type Item = Result<Occurrence<N, D, G, P>, IndexError<N>>;
613    fn next(&mut self) -> Option<Self::Item> {
614        let next = self.next.take()?;
615        if let Err(error) = self.index.admit(self.query) {
616            return Some(Err(error));
617        }
618        let entry = &self.index.entries[next];
619        self.next = entry.next;
620        Some(Ok(entry.value))
621    }
622}
623
624impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> core::iter::FusedIterator
625    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
626where
627    Q: Query<Id = N, Document = D, Generation = G>,
628{
629}