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    let prepared = evidence.prepared();
182    let requested = prepared.requested();
183    let supplied = evidence.level();
184    if requested != supplied {
185        return Err(LevelError::Mismatch {
186            requested,
187            supplied,
188        });
189    }
190    prepared
191        .document()
192        .message()
193        .require_session(operation, "cades.baseline")
194        .map_err(|_| LevelError::Foreign)?;
195    let work = operation.execution();
196    work.charge_work(1).map_err(LevelError::Resource)?;
197    if requested >= Level::Timestamped {
198        for required in prepared.preservation() {
199            work.charge_work(1).map_err(LevelError::Resource)?;
200            // §6.3 table1 permits AnnexA reference timestamps at B-T.
201            // Only old archive/PoE mechanisms select §5.5.3's preservation
202            // strategy. Retaining their full actor obligations is separate.
203            // https://www.etsi.org/deliver/etsi_en/319100_319199/31912201/01.03.01_60/en_31912201v010301p.pdf
204            if !required.kind.is_reference_timestamp() && !required.kind.is_archive() {
205                return Err(LevelError::UnavailablePreservation {
206                    property: required.property,
207                    kind: required.kind,
208                });
209            }
210        }
211        if !prepared.preservation().is_empty() {
212            for finding in prepared.preservation_findings() {
213                work.charge_work(1).map_err(LevelError::Resource)?;
214                if finding.severity == crate::prepare::Severity::Required {
215                    return Err(LevelError::UnavailablePreservationSource {
216                        source: finding.location.node,
217                    });
218                }
219            }
220        }
221    }
222    let met = match &evidence {
223        Evidence::Basic(basic) => basic.all_basic_requirements_met(),
224        Evidence::Timestamped(timed) => timed_met(timed),
225        Evidence::LongTerm(long) => long_met(long),
226        Evidence::Archival {
227            long_term,
228            archives,
229        } => {
230            work.charge_work(1).map_err(LevelError::Resource)?;
231            if !core::ptr::eq(archives.census(), prepared) {
232                return Err(LevelError::Foreign);
233            }
234            let credentials = long_term.credentials().credentials();
235            for archive in archives.reports() {
236                work.charge_work(1).map_err(LevelError::Resource)?;
237                if !core::ptr::eq(archive.credentials().credentials(), credentials) {
238                    return Err(LevelError::Foreign);
239                }
240            }
241            long_met(long_term) && archives.all_archive_material_covered()
242        }
243    };
244    let met = met && prepared.all_format_requirements_met();
245    Ok(Assessment { evidence, met })
246}
247
248fn timed_met(timed: &TimedOf<'_, '_, '_>) -> bool {
249    timed.basic().all_basic_requirements_met() && timed.all_static_time_requirements_met()
250}
251fn long_met<E>(long: &LongTermOf<'_, '_, '_, E>) -> bool {
252    timed_met(long.timed()) && long.all_material_requirements_met()
253}