Skip to main content

structured_pades/material/
checks.rs

1//! Complete actual X.509 material, independent of status usability and trust.
2
3use super::{Placement, Unified};
4use crate::{
5    query::Query,
6    range::RangeSource,
7    timestamp::{Node, Pades},
8};
9use structured_ltv::{
10    baseline::X509Material,
11    material::{self, Incorporation, IncorporationError, IndexError},
12    pki::{self, ParticipantClosure, ParticipantRequirements, UsesError},
13    source::Query as _,
14    timestamp::TimestampScope,
15};
16
17/// All actual actor/credential/status-authority artifacts and their original
18/// incorporated copies. A late copy establishes presence, never earlier `PoE`.
19pub struct Checks<'cl, 'r, 'v, 'c, 'd, D, G> {
20    inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, D, G, Pades>,
21    presence: Incorporation<'c, 'd, Node, D, G, Placement>,
22    source_conforming: bool,
23    revision: usize,
24}
25impl<'c, 'd, D: Copy, G: Copy> Checks<'_, '_, '_, 'c, 'd, D, G> {
26    /// Every actual required artifact, preserving each missing value.
27    #[must_use]
28    pub fn presence(&self) -> &Incorporation<'c, 'd, Node, D, G, Placement> {
29        &self.presence
30    }
31    /// Whole-source syntax and required presence only, not Good status,
32    /// historical coverage, credential-format completeness or application trust.
33    #[must_use]
34    pub fn all_x509_requirements_met(&self) -> bool {
35        self.source_conforming && self.presence.all_present()
36    }
37    /// Original revision whose available material was assessed. Later DSS
38    /// windows cannot repair this report's presence or source findings.
39    #[must_use]
40    pub fn revision(&self) -> usize {
41        self.revision
42    }
43}
44impl<'v, 'd, D: Copy, G: Copy> X509Material<'v, 'd> for Checks<'_, '_, 'v, '_, 'd, D, G> {
45    type Id = Node;
46    type Document = D;
47    type Generation = G;
48    type Issuer = Pades;
49    fn requirements(&self) -> &ParticipantRequirements<'v, 'd, Node, D, G, Pades> {
50        self.inputs.requirements()
51    }
52    fn all_x509_requirements_met(&self) -> bool {
53        Self::all_x509_requirements_met(self)
54    }
55}
56
57/// Complete actor attribution or original source/current admission refusal.
58#[derive(Debug)]
59pub enum Error {
60    /// An earlier-archive subset cannot establish whole-signature material.
61    Scope,
62    /// Original source/index refusal.
63    Index(IndexError<Node>),
64    /// Complete actual authority/status input census refused.
65    Uses(UsesError<Node>),
66    /// Original exact-artifact incorporation query refused.
67    Presence(IncorporationError<Node>),
68}
69impl<D: Copy + Eq, G: Copy + Eq> Unified<'_, '_, D, G> {
70    /// Uses all real root/counter/credential/TSA paths and their reached status
71    /// authorities, without replaying a parser, signature or status evaluation.
72    /// EN319142-1 ยง6.3(r,t) includes every used certificate/status artifact;
73    /// status usability and historical protection remain separate obligations.
74    /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31914201/01.02.01_60/en_31914201v010201p.pdf>
75    pub fn check_participants<'cl, 'r, 'v, 'c, 'd, S: RangeSource<Document = D, Generation = G>>(
76        &self,
77        inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, D, G, Pades>,
78        paths: &structured_ltv::timestamp::TimestampPaths<'_, '_, '_, Node, D, G, Pades>,
79        query: &Query<'_, '_, S>,
80    ) -> Result<Checks<'cl, 'r, 'v, 'c, 'd, D, G>, Error> {
81        let required = inputs.requirements();
82        if !matches!(required.scope(), TimestampScope::All(_)) {
83            return Err(Error::Scope);
84        }
85        if !query.belongs(self.session)
86            || required.signature() != self.root
87            || required.scope().location().snapshot != query.snapshot()
88        {
89            return Err(Error::Index(IndexError::Foreign {
90                signature: self.root,
91            }));
92        }
93        query
94            .admit(self.root, 2)
95            .map_err(|cause| Error::Index(IndexError::Scope(cause)))?;
96        if !core::ptr::eq(paths.required(), self.required) || !required.same_timestamps(paths) {
97            return Err(Error::Scope);
98        }
99        self.index
100            .require_signature(self.root, query)
101            .map_err(Error::Index)?;
102        let uses = pki::authority_uses(inputs, query).map_err(Error::Uses)?;
103        let artifacts = material::artifact_uses(&uses, query).map_err(Error::Uses)?;
104        let presence =
105            material::incorporation(&artifacts, &self.index, query).map_err(Error::Presence)?;
106        Ok(Checks {
107            inputs,
108            presence,
109            source_conforming: self.complete,
110            revision: self.revision,
111        })
112    }
113
114    /// Requires each actual preceding actor's X.509/status artifacts inside
115    /// this exact checked original PDF input. The archive's own contents and
116    /// later DSS copies do not count as prior protection.
117    #[expect(
118        clippy::result_large_err,
119        reason = "the common participant archive error preserves original typed artifact failures and cannot allocate an envelope after refusal"
120    )]
121    pub fn archived_participants<'cl, 'r, 'v, 'c, 'd, S, W>(
122        &self,
123        inputs: &'cl ParticipantClosure<'r, 'v, 'c, 'd, Node, D, G, Pades>,
124        coverage: &material::Coverage<Node, D, G, W, Pades>,
125        actor: &structured_ltv::timestamp::CheckedActor<'_, '_, Node, D, G, Pades>,
126        query: &Query<'_, '_, S>,
127    ) -> Result<
128        material::ParticipantMaterial<'cl, 'r, 'v, 'c, 'd, Node, D, G, Placement, Pades>,
129        material::ParticipantArchiveError<Node>,
130    >
131    where
132        S: RangeSource<Document = D, Generation = G>,
133        W: structured_asn1::memory::Work,
134    {
135        if !query.belongs(self.session) || inputs.requirements().signature() != self.root {
136            return Err(material::ParticipantArchiveError::Foreign {
137                expected: self.root,
138                actual: inputs.requirements().signature(),
139            });
140        }
141        material::archived_participants(inputs, &self.index, coverage, actor.verified(), query)
142    }
143}