Skip to main content

structured_cades/
baseline.rs

1//! Explicit requested `CAdES` level over the same complete original stage reports.
2//!
3//! The LTV stage constructors are reused directly: no cryptography, source
4//! replay or second material dispatcher is introduced by level composition.
5
6use crate::{
7    Cades, Level,
8    basic::{Basic, SignerBinding},
9    material::{self, Placement},
10    prepare::Preparation,
11    source::{DocumentId, Node},
12};
13use structured_asn1::memory::Work;
14use structured_ltv::baseline::{ArchiveMaterial, Archives, CredentialMaterial, LongTerm, Timed};
15
16pub use structured_ltv::baseline::{archive_material, archives, long_term, timestamped};
17
18/// Original root/counter basic stage with a single retained receipt lifetime.
19pub type BasicOf<'a, 'm, 'd> = Basic<'a, 'a, 'm, 'd, 'a>;
20/// Full original TSA census and every root signature-time static association.
21pub type TimedOf<'a, 'm, 'd> = Timed<
22    'a,
23    'a,
24    'd,
25    BasicOf<'a, 'm, 'd>,
26    SignerBinding<'a, 'm, 'd, 'a>,
27    Node,
28    DocumentId<'m, 'd>,
29    (),
30    Cades,
31>;
32/// Format material tied to the actual complete authentication receipt.
33pub type CredentialsOf<'a, 'm, 'd, E> = material::credentials::Report<'a, 'a, 'm, 'd, 'a, E>;
34/// Full original time and X.509/credential validation-material stages.
35pub type LongTermOf<'a, 'm, 'd, E> = LongTerm<
36    'a,
37    TimedOf<'a, 'm, 'd>,
38    material::Checks<'a, 'a, 'a, 'a, 'd, 'm>,
39    CredentialsOf<'a, 'm, 'd, E>,
40>;
41/// Every actual prior-participant material set under its original archive.
42pub type ArchivesOf<'a, 'm, 'd, E> = Archives<
43    'a,
44    Preparation<'m, 'd>,
45    ArchiveMaterial<
46        'a,
47        structured_ltv::material::ParticipantMaterial<
48            'a,
49            'a,
50            'a,
51            'a,
52            'd,
53            Node,
54            DocumentId<'m, 'd>,
55            (),
56            Placement,
57            Cades,
58        >,
59        CredentialsOf<'a, 'm, 'd, E>,
60    >,
61>;
62
63/// Complete stage set for one explicitly selected profile. Later stages retain
64/// their actual earlier reports rather than accepting independently substituted
65/// successful booleans. None of these reports decides application trust.
66pub enum Evidence<'a, 'm, 'd, E> {
67    /// B-B mandatory format and original signer/counter association.
68    Basic(&'a BasicOf<'a, 'm, 'd>),
69    /// B-T whole original TSA census and full static signature-time bounds.
70    Timestamped(&'a TimedOf<'a, 'm, 'd>),
71    /// B-LT all actual X.509 and credential-format incorporation/placement.
72    LongTerm(&'a LongTermOf<'a, 'm, 'd, E>),
73    /// B-LTA all original archive inputs, including every earlier actor's data.
74    Archival {
75        /// Whole signature, including its time/basic stages.
76        long_term: &'a LongTermOf<'a, 'm, 'd, E>,
77        /// Complete actual root archive census.
78        archives: &'a ArchivesOf<'a, 'm, 'd, E>,
79    },
80}
81impl<'a, 'm, 'd> Evidence<'a, 'm, 'd, ()> {
82    /// Basic evidence without naming an unrelated credential evidence type.
83    #[must_use]
84    pub fn basic(report: &'a BasicOf<'a, 'm, 'd>) -> Self {
85        Self::Basic(report)
86    }
87    /// Timestamped evidence without naming an unrelated credential type.
88    #[must_use]
89    pub fn timestamped(report: &'a TimedOf<'a, 'm, 'd>) -> Self {
90        Self::Timestamped(report)
91    }
92}
93impl<'m, 'd, E> Evidence<'_, 'm, 'd, E> {
94    /// Exact supplied stage level, never inferred from an unsigned OID.
95    #[must_use]
96    pub fn level(&self) -> Level {
97        match self {
98            Self::Basic(_) => Level::Basic,
99            Self::Timestamped(_) => Level::Timestamped,
100            Self::LongTerm(_) => Level::LongTerm,
101            Self::Archival { .. } => Level::Archival,
102        }
103    }
104    fn prepared(&self) -> &Preparation<'m, 'd> {
105        match self {
106            Self::Basic(basic) => basic.binding().prepared(),
107            Self::Timestamped(timed) => timed.basic().binding().prepared(),
108            Self::LongTerm(long)
109            | Self::Archival {
110                long_term: long, ..
111            } => long.timed().basic().binding().prepared(),
112        }
113    }
114}
115
116/// Requested level and all linked detailed reports. This establishes the
117/// common format/static-time/incorporation/coverage predicates only. Actual
118/// status usability, pre-revocation chronology, algorithm longevity and
119/// historical/application trust remain explicit caller decisions.
120pub struct Assessment<'a, 'm, 'd, E> {
121    evidence: Evidence<'a, 'm, 'd, E>,
122    met: bool,
123}
124impl<'a, 'm, 'd, E> Assessment<'a, 'm, 'd, E> {
125    /// Original explicitly requested level.
126    #[must_use]
127    pub fn requested(&self) -> Level {
128        self.evidence.level()
129    }
130    /// Every detailed original positive/negative stage fact.
131    #[must_use]
132    pub fn evidence(&self) -> &Evidence<'a, 'm, 'd, E> {
133        &self.evidence
134    }
135    /// All common stage predicates hold; this is not an application acceptance.
136    #[must_use]
137    pub fn all_requirements_met(&self) -> bool {
138        self.met
139    }
140}
141
142/// Wrong stage level or substituted original preparation/credential receipt.
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum LevelError {
145    /// An actual earlier archive/PoE lacks qualified historical proof/actors.
146    /// It is not malformed syntax and a modern successful prefix cannot erase it.
147    UnavailablePreservation {
148        /// Original historical attribute.
149        property: Node,
150        /// Actual historical format.
151        kind: crate::preservation::Kind,
152    },
153    /// A required historical source/authority census remains unreadable.
154    UnavailablePreservationSource {
155        /// Original source; its full diagnostic remains in preparation.
156        source: Node,
157    },
158    /// Supplied deeper/shallower reports cannot promote or downgrade a request.
159    Mismatch {
160        /// Original preparation level.
161        requested: Level,
162        /// Actual supplied stage set.
163        supplied: Level,
164    },
165    /// Archive and whole-material facts use different original receipts.
166    Foreign,
167    /// Current comparison of the complete archive census refused.
168    Resource(structured_asn1::memory::MemoryError),
169}
170
171/// Compose exactly the requested level, requiring every preceding stage.
172/// EN319122-1 §6.1 and table1 define the four accumulating baseline profiles;
173/// extra unsigned containers cannot promote a requested profile. Checking each
174/// archive's input credential identity is current query work, even when the
175/// earlier reports are already retained. No parse/provider/status replay occurs.
176/// <https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf>
177pub fn assess<'a, 'm, 'd, E>(
178    evidence: Evidence<'a, 'm, 'd, E>,
179    operation: &structured_asn1::Reader<'_>,
180) -> Result<Assessment<'a, 'm, 'd, E>, LevelError> {
181    assess_in(evidence, None, operation)
182}
183
184/// Same complete baseline composition with a sealed whole ERS/PoE closure.
185/// It admits existing preservation only; modern time/material/archive stages
186/// remain independently mandatory and cannot be promoted by a legacy proof.
187pub fn assess_preserved<'a, 'm, 'd, E>(
188    evidence: Evidence<'a, 'm, 'd, E>,
189    preservation: &crate::evidence::Closure<'_, '_, 'm, 'd, '_>,
190    operation: &structured_asn1::Reader<'_>,
191) -> Result<Assessment<'a, 'm, 'd, E>, LevelError> {
192    assess_in(evidence, Some(preservation), operation)
193}
194fn assess_in<'a, 'm, 'd, E>(
195    evidence: Evidence<'a, 'm, 'd, E>,
196    preservation: Option<&crate::evidence::Closure<'_, '_, 'm, 'd, '_>>,
197    operation: &structured_asn1::Reader<'_>,
198) -> Result<Assessment<'a, 'm, 'd, E>, LevelError> {
199    let prepared = evidence.prepared();
200    if let Some(proofs) = preservation
201        && !core::ptr::eq(proofs.prepared(), prepared)
202    {
203        return Err(LevelError::Foreign);
204    }
205    let requested = prepared.requested();
206    let supplied = evidence.level();
207    if requested != supplied {
208        return Err(LevelError::Mismatch {
209            requested,
210            supplied,
211        });
212    }
213    prepared
214        .document()
215        .message()
216        .require_session(operation, "cades.baseline")
217        .map_err(|_| LevelError::Foreign)?;
218    let work = operation.execution();
219    work.charge_work(1).map_err(LevelError::Resource)?;
220    if requested >= Level::Timestamped {
221        for required in prepared.preservation() {
222            work.charge_work(1).map_err(LevelError::Resource)?;
223            // §6.3 table1 permits AnnexA reference timestamps at B-T.
224            // Only old archive/PoE mechanisms select §5.5.3's preservation
225            // strategy. Retaining their full actor obligations is separate.
226            // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
227            if !required.kind.is_reference_timestamp() && !required.kind.is_archive() {
228                if let Some(proofs) = preservation
229                    && required.kind.is_evidence()
230                    && proofs
231                        .contains(required, &work)
232                        .map_err(LevelError::Resource)?
233                {
234                    continue;
235                }
236
237                return Err(LevelError::UnavailablePreservation {
238                    property: required.property,
239                    kind: required.kind,
240                });
241            }
242        }
243        if !prepared.preservation().is_empty() {
244            for finding in prepared.preservation_findings() {
245                work.charge_work(1).map_err(LevelError::Resource)?;
246                if finding.severity == crate::prepare::Severity::Required {
247                    return Err(LevelError::UnavailablePreservationSource {
248                        source: finding.location.node,
249                    });
250                }
251            }
252        }
253    }
254    let met = match &evidence {
255        Evidence::Basic(basic) => basic.all_basic_requirements_met(),
256        Evidence::Timestamped(timed) => timed_met(timed),
257        Evidence::LongTerm(long) => long_met(long),
258        Evidence::Archival {
259            long_term,
260            archives,
261        } => {
262            work.charge_work(1).map_err(LevelError::Resource)?;
263            if !core::ptr::eq(archives.census(), prepared) {
264                return Err(LevelError::Foreign);
265            }
266            let credentials = long_term.credentials().credentials();
267            for archive in archives.reports() {
268                work.charge_work(1).map_err(LevelError::Resource)?;
269                if !core::ptr::eq(archive.credentials().credentials(), credentials) {
270                    return Err(LevelError::Foreign);
271                }
272            }
273            long_met(long_term) && archives.all_archive_material_covered()
274        }
275    };
276    let met = met && prepared.all_format_requirements_met();
277    Ok(Assessment { evidence, met })
278}
279
280fn timed_met(timed: &TimedOf<'_, '_, '_>) -> bool {
281    timed.basic().all_basic_requirements_met() && timed.all_static_time_requirements_met()
282}
283fn long_met<E>(long: &LongTermOf<'_, '_, '_, E>) -> bool {
284    timed_met(long.timed()) && long.all_material_requirements_met()
285}