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 from its format-defined retained container. CMS
159    /// RFC5940 uses a whole `OCSPResponse`; legacy `CAdES` Annex A.1.4 explicitly
160    /// carries `BasicOCSPResponse`. The format issuer admits the REAL placement;
161    /// an arbitrary caller body cannot mint an indexed occurrence or evidence.
162    Ocsp(&'view BasicResponse<'source>),
163}
164
165impl<'source> ArtifactRef<'_, 'source> {
166    /// Exact identity of the referenced artifact, borrowing its original DER.
167    #[must_use]
168    pub fn identity(self) -> ArtifactKey<'source> {
169        match self {
170            Self::Certificate(value) => ArtifactKey {
171                kind: Kind::Certificate,
172                bytes: value.as_bytes(),
173            },
174            Self::Crl(value) => ArtifactKey {
175                kind: Kind::Crl,
176                bytes: value.as_bytes(),
177            },
178            Self::Ocsp(value) => ArtifactKey {
179                kind: Kind::Basic,
180                bytes: value.as_bytes(),
181            },
182        }
183    }
184}
185
186/// Exact original artifact availability under one checked archive input.
187#[derive(Debug)]
188pub enum Archived<N, D, G, P> {
189    /// No exact artifact was incorporated in either original source pool.
190    Missing,
191    /// Exact copies exist, but none is covered by this particular archive input.
192    /// A later property or the archive token's own material cannot repair it.
193    Uncovered,
194    /// At least one exact incorporated copy is mathematically covered.
195    Present(Occurrence<N, D, G, P>),
196}
197
198/// Exact position of a certificate in a supplied, actually checked path.
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub enum PathCertificate {
201    /// Explicitly supplied trust-anchor certificate, if one exists. This is
202    /// material availability only, never a requirement to revoke an anchor.
203    Anchor,
204    /// Issuer-to-subject ordinal in the checked non-anchor member catalogue.
205    Member(usize),
206}
207
208/// One checked-path certificate's mathematical availability before an archive.
209#[derive(Debug)]
210pub struct ArchivedCertificate<N, D, G, P> {
211    /// Original role; no caller-supplied certificate count is used.
212    pub role: PathCertificate,
213    /// Exact copies are searched using the same archive coverage receipt.
214    pub availability: Archived<N, D, G, P>,
215}
216
217/// Complete certificate census for one actual checked path and archive input.
218/// It deliberately retains its path: ordinals cannot be relabelled as another
219/// actor after this check. Status artifacts and actor association are separate.
220pub struct PathCertificates<'path, 'document, N, D, G, P> {
221    path: &'path CheckedPath<'document>,
222    archive: Location<N, D, G>,
223    entries: OwnedVec<ArchivedCertificate<N, D, G, P>>,
224    all_covered: bool,
225}
226
227impl<'path, 'document, N: Copy, D: Copy, G: Copy, P>
228    PathCertificates<'path, 'document, N, D, G, P>
229{
230    /// Same actual common-validator path, not an independent artifact list.
231    #[must_use]
232    pub fn path(&self) -> &'path CheckedPath<'document> {
233        self.path
234    }
235    /// Original archive token whose actual input coverage was used.
236    #[must_use]
237    pub fn archive(&self) -> Location<N, D, G> {
238        self.archive
239    }
240    /// Every existing anchor/member certificate, including missing late copies.
241    #[must_use]
242    pub fn entries(&self) -> &[ArchivedCertificate<N, D, G, P>] {
243        &self.entries
244    }
245    /// Whether every certificate in this path has an exact covered copy.
246    /// This alone proves neither status completeness nor any baseline level.
247    #[must_use]
248    pub fn all_certificates_covered(&self) -> bool {
249        self.all_covered
250    }
251}
252
253/// Original source/resource failure, separately from missing archive material.
254#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub enum ArchiveError<N> {
256    /// Exact artifact hashing, lookup or source traversal refused.
257    Index(IndexError<N>),
258    /// The mathematical input coverage/source binding could not be established.
259    Coverage(CoverageError<N>),
260    /// The supplied successful token covers a different kind of input.
261    NotArchive {
262        /// Actual original timestamp property.
263        property: N,
264    },
265}
266
267impl<'source, N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, I>
268    Index<'source, N, D, G, P, O, W, I>
269{
270    /// An empty index for this signature in the query's source generation.
271    ///
272    /// `open` is the format's schema-open material catalogue, retained only so
273    /// that format checks find it beside the index. Requiring the issuer value
274    /// keeps a caller of the format from building an index of its own.
275    pub fn new<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
276        _issuer: I,
277        signature: N,
278        open: &'source [O],
279        query: &Q,
280    ) -> Result<Self, IndexError<N>> {
281        query.admit(signature, 1).map_err(IndexError::Scope)?;
282        let execution = query.execution();
283        Ok(Self {
284            snapshot: query.snapshot(),
285            signature,
286            keys: execution.map(),
287            entries: execution.vector(),
288            open,
289            issuer: PhantomData,
290        })
291    }
292
293    /// Records one occurrence of an exact original artifact. For an OCSP value
294    /// every certificate embedded in its basic response is recorded too, with
295    /// the same location and placement. Hashing, probes and storage are
296    /// admitted first; a refusal leaves the index for the caller to drop. The
297    /// builder pays its own traversal of the format's pools with [`Self::admit`].
298    /// Requiring the issuer value keeps a caller of the format, which may own
299    /// the index, from adding material the document does not carry.
300    pub fn insert<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
301        &mut self,
302        _issuer: I,
303        artifact: ArtifactRef<'_, 'source>,
304        occurrence: Occurrence<N, D, G, P>,
305        query: &Q,
306    ) -> Result<(), IndexError<N>> {
307        // Coverage lookups compare only the occurrence's node, so its source
308        // generation must already be this index's.
309        if occurrence.location.snapshot != self.snapshot || query.snapshot() != self.snapshot {
310            return Err(IndexError::Foreign {
311                signature: self.signature,
312            });
313        }
314        self.insert_key(artifact.identity(), occurrence, query)?;
315        if let ArtifactRef::Ocsp(response) = artifact {
316            for certificate in 0..response.certificates().len() {
317                self.admit(query)?;
318                self.insert_key(
319                    ArtifactKey {
320                        kind: Kind::Certificate,
321                        bytes: response.certificates()[certificate].as_bytes(),
322                    },
323                    Occurrence {
324                        embedded_certificate: Some(certificate),
325                        ..occurrence
326                    },
327                    query,
328                )?;
329            }
330        }
331        Ok(())
332    }
333
334    /// The format's schema-open catalogue, as supplied.
335    #[must_use]
336    pub fn open(&self) -> &'source [O] {
337        self.open
338    }
339
340    /// Original selected signature of this index.
341    #[must_use]
342    pub fn signature(&self) -> N {
343        self.signature
344    }
345
346    /// Source generation of every occurrence.
347    #[must_use]
348    pub fn snapshot(&self) -> Snapshot<D, G> {
349        self.snapshot
350    }
351
352    /// Finds all copies of this exact original certificate, including copies
353    /// inside OCSP. A match is availability only, never authentication/trust.
354    pub fn certificate<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
355        &'view self,
356        value: &Certificate<'_>,
357        query: &'query Q,
358    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
359        self.find(Kind::Certificate, value.as_bytes(), query)
360    }
361    /// Finds all whole CRL copies. An authenticated delta still needs its
362    /// authenticated/bound complete source; finding either alone proves no pair.
363    pub fn crl<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
364        &'view self,
365        value: &CertificateList<'_>,
366        query: &'query Q,
367    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
368        self.find(Kind::Crl, value.as_bytes(), query)
369    }
370    /// Finds the exact basic response in format-issued retained occurrences.
371    /// Good-looking status or a successful wire code does not mint evidence.
372    pub fn ocsp<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
373        &'view self,
374        value: &BasicResponse<'_>,
375        query: &'query Q,
376    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
377        self.find(Kind::Basic, value.as_bytes(), query)
378    }
379
380    /// Finds all copies of this exact artifact, in source catalogue order.
381    pub fn artifact<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
382        &'view self,
383        artifact: ArtifactRef<'_, '_>,
384        query: &'query Q,
385    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
386        let key = artifact.identity();
387        self.find(key.kind, key.bytes, query)
388    }
389
390    /// Requires that this index was built for `signature`.
391    pub fn require_signature<Q: Query<Id = N, Document = D, Generation = G>>(
392        &self,
393        signature: N,
394        query: &Q,
395    ) -> Result<(), IndexError<N>> {
396        self.admit(query)?;
397        let same =
398            query
399                .equal(self.signature, signature)
400                .map_err(|cause| IndexError::Resource {
401                    signature: self.signature,
402                    cause,
403                })?;
404        if !same {
405            return Err(IndexError::Foreign { signature });
406        }
407        Ok(())
408    }
409
410    /// Locates a whole original artifact in this exact checked archive input.
411    ///
412    /// EN319132-1 §6.3(aa)/§5.5.2.2 requires the necessary material to have
413    /// been incorporated before generating each archive timestamp. This uses
414    /// the same selection the format used for actual timestamp input, so merely
415    /// finding a late or self-carried copy cannot count as covered. All copies
416    /// remain available; an early uncovered duplicate does not hide a covered
417    /// one. This proves mathematical availability only. The caller must derive
418    /// required artifacts from actual actor/path/status checks and must
419    /// authenticate the TSA separately before claiming a baseline level.
420    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
421    pub fn archived<Q: Query<Id = N, Document = D, Generation = G>, C: Work>(
422        &self,
423        artifact: ArtifactRef<'_, '_>,
424        coverage: &Coverage<N, D, G, C, I>,
425        evidence: &Verified<N, D, G, I>,
426        query: &Q,
427    ) -> Result<Archived<N, D, G, P>, ArchiveError<N>>
428    where
429        N: HashWork,
430    {
431        if evidence.purpose() != Purpose::Archive {
432            return Err(ArchiveError::NotArchive {
433                property: evidence.property(),
434            });
435        }
436        // Validate even empty/missing catalogues before deriving an absence.
437        if evidence.token().snapshot != self.snapshot {
438            return Err(ArchiveError::Index(IndexError::Foreign {
439                signature: self.signature,
440            }));
441        }
442        self.require_signature(evidence.signature(), query)
443            .map_err(ArchiveError::Index)?;
444        coverage
445            .require_evidence(evidence, evidence.property(), query)
446            .map_err(ArchiveError::Coverage)?;
447        let key = artifact.identity();
448        let copies = self
449            .find(key.kind, key.bytes, query)
450            .map_err(ArchiveError::Index)?;
451        let mut found = false;
452        for copy in copies {
453            let copy = copy.map_err(ArchiveError::Index)?;
454            found = true;
455            // The immutable coverage/evidence pair was bound once above;
456            // each occurrence still checks the current source and lookup.
457            if coverage
458                .selects(copy.location.node, query)
459                .map_err(ArchiveError::Coverage)?
460            {
461                return Ok(Archived::Present(copy));
462            }
463        }
464        Ok(if found {
465            Archived::Uncovered
466        } else {
467            Archived::Missing
468        })
469    }
470
471    /// Derives certificate requirements from the actual common-validator path,
472    /// including an existing anchor certificate, then checks each exact artifact
473    /// against this archive's mathematical input. Missing/late copies remain
474    /// facts; resource/source failure returns no successful partial census.
475    ///
476    /// EN319132-1 V1.3.1 §§5.4.2/5.5.2.2/6.3(aa) require the material of the
477    /// actual actors before each archive. This certificate component does not
478    /// assert that status, credential, countersigner or previous-TSA processing
479    /// completed. The caller must supply their separately bound paths.
480    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
481    pub fn archived_path<'path, 'document, Q, C: Work>(
482        &self,
483        path: &'path CheckedPath<'document>,
484        coverage: &Coverage<N, D, G, C, I>,
485        evidence: &Verified<N, D, G, I>,
486        query: &Q,
487    ) -> Result<PathCertificates<'path, 'document, N, D, G, P>, ArchiveError<N>>
488    where
489        N: HashWork,
490        Q: Query<Id = N, Document = D, Generation = G>,
491    {
492        query
493            .admit(self.signature, 1)
494            .map_err(|cause| ArchiveError::Index(IndexError::Scope(cause)))?;
495        let mut entries = query.execution().vector();
496        let mut all_covered = true;
497        let mut record = |certificate, role| {
498            let availability = self.archived(
499                ArtifactRef::Certificate(certificate),
500                coverage,
501                evidence,
502                query,
503            )?;
504            if !matches!(availability, Archived::Present(_)) {
505                all_covered = false;
506            }
507            entries
508                .try_push(ArchivedCertificate { role, availability })
509                .map_err(|cause| {
510                    ArchiveError::Index(IndexError::Resource {
511                        signature: self.signature,
512                        cause,
513                    })
514                })
515        };
516        if let Some(certificate) = path.anchor().certificate() {
517            record(certificate, PathCertificate::Anchor)?;
518        }
519        for (ordinal, member) in path.members().iter().enumerate() {
520            self.admit(query).map_err(ArchiveError::Index)?;
521            record(member.certificate(), PathCertificate::Member(ordinal))?;
522        }
523        Ok(PathCertificates {
524            path,
525            archive: evidence.token(),
526            entries: entries.into_owned(),
527            all_covered,
528        })
529    }
530
531    /// Admits one traversal step over this index's source generation.
532    pub fn admit<Q: Query<Id = N, Document = D, Generation = G>>(
533        &self,
534        query: &Q,
535    ) -> Result<(), IndexError<N>> {
536        if self.snapshot != query.snapshot() {
537            return Err(IndexError::Foreign {
538                signature: self.signature,
539            });
540        }
541        query.admit(self.signature, 2).map_err(IndexError::Scope)
542    }
543
544    fn insert_key<Q: Query<Id = N, Document = D, Generation = G, Work = W>>(
545        &mut self,
546        key: ArtifactKey<'source>,
547        value: Occurrence<N, D, G, P>,
548        query: &Q,
549    ) -> Result<(), IndexError<N>> {
550        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
551        let resource = |cause| IndexError::Resource {
552            signature: self.signature,
553            cause,
554        };
555        let previous = self
556            .keys
557            .get(&key, query.execution())
558            .map(Option::<&(usize, usize)>::copied)
559            .map_err(resource)?;
560        let next = self.entries.len();
561        self.entries
562            .try_push(Entry { value, next: None })
563            .map_err(resource)?;
564        let head = previous.map_or(next, |(head, _)| head);
565        self.keys.try_insert(key, (head, next)).map_err(resource)?;
566        if let Some((_, tail)) = previous {
567            self.entries[tail].next = Some(next);
568        }
569        Ok(())
570    }
571
572    fn find<'view, 'query, Q: Query<Id = N, Document = D, Generation = G>>(
573        &'view self,
574        kind: Kind,
575        bytes: &[u8],
576        query: &'query Q,
577    ) -> Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> {
578        self.admit(query)?;
579        query.admit(self.signature, 1).map_err(IndexError::Scope)?;
580        let next = self
581            .keys
582            .get(&ArtifactKey { kind, bytes }, query.execution())
583            .map(|value| value.map(|&(head, _)| head))
584            .map_err(|cause| IndexError::Resource {
585                signature: self.signature,
586                cause,
587            })?;
588        Ok(Occurrences {
589            index: self,
590            query,
591            next,
592        })
593    }
594}
595
596/// Every copy of one artifact, or the refusal to look it up.
597pub type Lookup<'view, 'query, 'source, N, D, G, P, O, W, Q, I> =
598    Result<Occurrences<'view, 'query, 'source, N, D, G, P, O, W, Q, I>, IndexError<N>>;
599
600/// Source-catalogue-order traversal of every copy; current Work pays metadata
601/// access even when index storage belongs to an earlier execution. The first
602/// source/resource refusal is returned once and fuses the iterator.
603pub struct Occurrences<'view, 'query, 'source, N, D, G, P, O, W: Work, Q, I> {
604    index: &'view Index<'source, N, D, G, P, O, W, I>,
605    query: &'query Q,
606    next: Option<usize>,
607}
608
609impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> Iterator
610    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
611where
612    Q: Query<Id = N, Document = D, Generation = G>,
613{
614    type Item = Result<Occurrence<N, D, G, P>, IndexError<N>>;
615    fn next(&mut self) -> Option<Self::Item> {
616        let next = self.next.take()?;
617        if let Err(error) = self.index.admit(self.query) {
618            return Some(Err(error));
619        }
620        let entry = &self.index.entries[next];
621        self.next = entry.next;
622        Some(Ok(entry.value))
623    }
624}
625
626impl<N: Copy + Eq, D: Copy + Eq, G: Copy + Eq, P: Copy, O, W: Work, Q, I> core::iter::FusedIterator
627    for Occurrences<'_, '_, '_, N, D, G, P, O, W, Q, I>
628where
629    Q: Query<Id = N, Document = D, Generation = G>,
630{
631}