Skip to main content

structured_cades/evidence/poe/
check.rs

1//! Close the SAME source-derived `PoE` recipe with every actual TSA participant.
2use super::{InputError, Lazy};
3use crate::{
4    Error,
5    basic::PathRestriction,
6    evidence::{
7        PoeValue, View,
8        record::{Authorities, Checked, Selected, SelectionError},
9    },
10    timestamp::{self, ProfileError, VisitError},
11};
12use core::{
13    cell::{Cell, RefCell},
14    convert::Infallible,
15};
16use structured_asn1::{
17    Reader,
18    memory::{Execution, Work},
19};
20use structured_cms::{
21    evidence::{Membership, verify as ers},
22    provider::SignatureProvider,
23    stream::Source,
24    timestamp::{self as tsp, TokenExtension},
25};
26use structured_pkix::temporal::{Instant, LeapTable};
27const FIELD: &str = "cades.longTermValidation.check";
28
29type ErsFailure<SE, PE, RE, AE, HE> = ers::VerificationError<
30    InputError<SE, PE>,
31    PE,
32    RE,
33    SelectionError<AE>,
34    HE,
35    ProfileError<PE, Error>,
36>;
37/// Original `PoE`-specific refusal; completed negative facts stay in the report.
38#[derive(Debug)]
39pub enum CheckError<SE, PE, RE, AE, HE> {
40    /// Original source/schema/current work refusal or absent optional proof.
41    Processing(Error),
42    /// Original carried-information and algorithm preflight failure.
43    Plan(ers::PlanError),
44    /// Original caller candidate/path discovery failure.
45    Authority(AE),
46    /// Actual complete ERS execution over the per-algorithm eight-hash source.
47    Record(ErsFailure<SE, PE, RE, AE, HE>),
48    /// Complete single timestamp check, preserving input/provider/source maps.
49    Timestamp(tsp::ProfileCheckError<InputError<SE, PE>, PE, RE, ProfileError<PE, Error>>),
50    /// Actual independently executed historical validator refusal.
51    Historical(HE),
52}
53
54/// Full original proof result, preserving independent source/provider failures.
55pub type CheckResult<'a, 'm, 'd, 'path, SE, PE, RE, AE, HE> =
56    Result<Checked<'a, 'm, 'd, 'path>, CheckError<SE, PE, RE, AE, HE>>;
57
58impl<'a, 'm, 'd> View<'a, 'm, 'd> {
59    /// Verifies the original TS101733 §6.5.1 `PoE`, either a timestamp or an
60    /// entire ERS record with BOTH renewal mechanisms. Each algorithm uses its
61    /// own complete eight-hash recipe. Actual content is acquired once, after
62    /// the selected candidate/key and signed ESS profile preflight.
63    ///
64    /// `poeDate` selects earlier instances only; it cannot establish historical
65    /// authority. The callback must establish path/status/algorithm validity at
66    /// the following authenticated proof, or at explicit `reference` for the
67    /// final proof. Closing ALL other original obligations remains separate.
68    #[expect(
69        clippy::too_many_arguments,
70        reason = "Actual payload acquisition, current execution, path/provider selection and historical validation are separate contracts."
71    )]
72    #[expect(
73        clippy::result_large_err,
74        reason = "Original source maps and opaque acquisition/provider/history failures survive allocation refusal inline."
75    )]
76    pub fn check_poe<'path, P, PE, A, S, RE, HE>(
77        &self,
78        provider: &P,
79        operation: &Reader<'d>,
80        authorities: &'path A,
81        utc: &LeapTable<'d>,
82        reference: Instant,
83        detached: Option<&mut S>,
84        recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
85        historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
86    ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
87    where
88        P: for<'scope> SignatureProvider<'scope, Error = PE>,
89        A: Authorities<'d>,
90        S: Source,
91        'd: 'path,
92    {
93        self.check_poe_with_later(
94            provider,
95            operation,
96            authorities,
97            utc,
98            reference,
99            detached,
100            &[],
101            recognizes,
102            historical,
103        )
104    }
105
106    /// Same source recipe, excluding ONLY a subsequently added ATSv3/Annex B
107    /// proof whose already verified coverage authenticates this actual `PoE`.
108    /// EN319122-1 §5.5.3 requires this admission before ignoring later proofs;
109    /// untrusted dates/order never authorize an exclusion.
110    #[expect(
111        clippy::too_many_arguments,
112        reason = "Sealed later-proof coverage supplements the independent source, execution and verification callbacks."
113    )]
114    #[expect(
115        clippy::result_large_err,
116        reason = "Neither the mapped ERS nor timestamp failure can be boxed on an exhausted operation budget."
117    )]
118    #[expect(
119        clippy::too_many_lines,
120        reason = "The two first-read formats share one atomic source/profile/history ledger and return no successful partial report."
121    )]
122    pub fn check_poe_with_later<'path, P, PE, A, S, RE, HE>(
123        &self,
124        provider: &P,
125        operation: &Reader<'d>,
126        authorities: &'path A,
127        utc: &LeapTable<'d>,
128        reference: Instant,
129        detached: Option<&mut S>,
130        later: &[crate::timestamp::historical::Later<'_, 'm, 'd>],
131        mut recognizes: impl FnMut(usize, &TokenExtension<'_>, &Execution<'_>) -> Result<bool, RE>,
132        mut historical: impl FnMut(ers::Historical<'_, '_>, &Execution<'_>) -> Result<ers::Decision, HE>,
133    ) -> CheckResult<'a, 'm, 'd, 'path, S::Error, PE, RE, A::Error, HE>
134    where
135        P: for<'scope> SignatureProvider<'scope, Error = PE>,
136        A: Authorities<'d>,
137        S: Source,
138        'd: 'path,
139    {
140        let poe = self.poe().ok_or_else(|| {
141            CheckError::Processing(Error::Foreign {
142                at: self.required().property.span().start,
143            })
144        })?;
145        let required = self.required();
146        if required.placement != structured_cms::attribute::Placement::Unsigned {
147            return Err(CheckError::Processing(Error::Syntax {
148                code: structured_cms::ErrorCode::AttributePlacement,
149                field: FIELD,
150                at: required.property.span().start,
151            }));
152        }
153        let signer = self
154            .prepared
155            .signer_encoding(required.signature, operation)
156            .map_err(CheckError::Processing)?;
157        let mut single = false;
158        for ordinal in 0..signer.attributes() {
159            operation
160                .charge(1)
161                .map_err(|cause| CheckError::Processing(Error::encoding(cause, FIELD)))?;
162            let attribute =
163                signer
164                    .attribute(ordinal)
165                    .ok_or(CheckError::Processing(Error::Foreign {
166                        at: required.property.span().start,
167                    }))?;
168            if attribute.span == required.property.span() {
169                single = attribute.single.is_some()
170                    && attribute.single == required.value.map(|value| value.element().span());
171                break;
172            }
173        }
174        if !single {
175            return Err(CheckError::Processing(Error::Syntax {
176                code: structured_cms::ErrorCode::AttributeCardinality,
177                field: FIELD,
178                at: required.property.span().start,
179            }));
180        }
181        let at = poe.element().span().start;
182        let resource = |cause| CheckError::Processing(Error::resource(cause, FIELD, at));
183        let work = operation.execution();
184        let proof = poe
185            .proof()
186            .ok_or(CheckError::Processing(Error::Foreign { at }))?;
187        match proof {
188            PoeValue::EvidenceRecord(record) => {
189                let plan = record.input_plan(operation).map_err(CheckError::Plan)?;
190                self.admit_poe_algorithms(plan.algorithms(), operation)
191                    .map_err(CheckError::Processing)?;
192                let mut slots = work.vector();
193                let mut restrictions = work.vector();
194                slots
195                    .try_reserve_exact(record.timestamps().len())
196                    .map_err(resource)?;
197                restrictions
198                    .try_reserve_exact(record.timestamps().len())
199                    .map_err(resource)?;
200                for _ in record.timestamps() {
201                    slots.try_push(None).map_err(resource)?;
202                    restrictions
203                        .try_push(Cell::new(PathRestriction::Unproven))
204                        .map_err(resource)?;
205                }
206                let selected = Selected {
207                    source: authorities,
208                    start: self.entry.timestamps.start,
209                    items: RefCell::new(slots.into_owned()),
210                };
211                let mut sources = [Lazy {
212                    view: self,
213                    later,
214                    provider,
215                    algorithms: plan.algorithms(),
216                    operation,
217                    utc,
218                    detached,
219                    ready: None,
220                }];
221                let report = plan
222                    .verify_requests_with_profile(
223                        provider,
224                        operation,
225                        &selected,
226                        utc,
227                        reference,
228                        &mut sources,
229                        Membership::Object,
230                        |index, extension, execution| {
231                            recognizes(self.entry.timestamps.start + index, extension, execution)
232                        },
233                        |mut facts, execution| {
234                            facts.index += self.entry.timestamps.start;
235                            historical(facts, execution)
236                        },
237                        |index, input| {
238                            let path = selected
239                                .path(index, &input.parent.execution(), at)
240                                .map_err(|cause| tsp::ProfileFailure {
241                                    source: tsp::ProfileSource::Envelope,
242                                    cause: ProfileError::Input(cause),
243                                })?;
244                            restrictions[index]
245                                .set(timestamp::Context::profile_rules(&input, path, provider)?);
246                            Ok(())
247                        },
248                    )
249                    .map_err(CheckError::Record)?;
250                let covered = sources[0].ready.take().map(|eight| eight.covered);
251                let mut checked = self
252                    .finish_record(report, &selected, &restrictions, operation)
253                    .map_err(CheckError::Processing)?;
254                checked
255                    .bind_coverage(covered, &work)
256                    .map_err(CheckError::Processing)?;
257                Ok(checked)
258            }
259            PoeValue::Timestamp(token) => {
260                let source = poe
261                    .timestamp_source()
262                    .ok_or(CheckError::Processing(Error::Foreign { at }))?;
263                let reader = source
264                    .reader_in(operation)
265                    .map_err(CheckError::Processing)?;
266                // Same checked algorithm grammar as ERS InputPlan; retain its
267                // real carried source map for malformed/unknown identifiers.
268                if !token.faults().is_empty()
269                    || !token.message().faults().is_empty()
270                    || !token.signer().faults().is_empty()
271                {
272                    return Err(CheckError::Plan(ers::PlanError::Nonconforming {
273                        index: self.entry.timestamps.start,
274                        span: token.message().span(),
275                    }));
276                }
277                let algorithm = ers::token_algorithm(token, &reader)
278                    .map_err(|cause| CheckError::Plan(ers::PlanError::Information(cause)))?;
279                self.admit_poe_algorithms(&[algorithm], operation)
280                    .map_err(CheckError::Processing)?;
281                let choice = authorities
282                    .select(self.entry.timestamps.start, token, &reader.execution())
283                    .map_err(CheckError::Authority)?;
284                let target = choice
285                    .path
286                    .members()
287                    .last()
288                    .ok_or(CheckError::Processing(Error::Foreign { at }))?;
289                let selection = ers::Selection {
290                    certificate: target.certificate(),
291                    certificate_parent: choice.certificate_parent,
292                    key: target.key(),
293                };
294                let context = tsp::CheckContext {
295                    certificate: selection.certificate,
296                    certificate_parent: selection.certificate_parent,
297                    key: selection.key,
298                    requested: None,
299                    utc,
300                    reference: None,
301                    window: None,
302                };
303                let mut covered = Lazy {
304                    view: self,
305                    later,
306                    provider,
307                    algorithms: &[algorithm],
308                    operation,
309                    utc,
310                    detached,
311                    ready: None,
312                };
313                let restriction = Cell::new(PathRestriction::Unproven);
314                let common = token
315                    .check_historical_input_with_profile(
316                        provider,
317                        &reader,
318                        &context,
319                        &mut tsp::RawInput::new(&mut covered),
320                        |extension, execution| {
321                            recognizes(self.entry.timestamps.start, extension, execution)
322                        },
323                        |input| {
324                            restriction.set(timestamp::Context::profile_rules(
325                                &input,
326                                choice.path,
327                                provider,
328                            )?);
329                            Ok(())
330                        },
331                    )
332                    .map_err(CheckError::Timestamp)?;
333                let history = historical(
334                    ers::Historical {
335                        index: self.entry.timestamps.start,
336                        current: &common,
337                        selection: &selection,
338                        following: None,
339                        following_chain: None,
340                        verification_time: reference,
341                    },
342                    &reader.execution(),
343                )
344                .map_err(CheckError::Historical)?;
345                let mut complete = history == ers::Decision::Valid && common.evidence().is_some();
346                let checked = timestamp::with_token(
347                    self.prepared,
348                    self.entry.timestamps.start,
349                    operation,
350                    |context| {
351                        Ok::<_, Infallible>(context.checked(
352                            common,
353                            restriction.get(),
354                            target.certificate(),
355                            target.key(),
356                        ))
357                    },
358                )
359                .map_err(|cause| {
360                    CheckError::Processing(match cause {
361                        VisitError::Processing(cause) => cause,
362                        VisitError::Unreadable { property } => Error::Foreign {
363                            at: property.span().start,
364                        },
365                        VisitError::Callback(never) => match never {},
366                    })
367                })?;
368                if checked.actor().is_none() {
369                    complete = false;
370                }
371                let mut checks = work.vector();
372                checks.try_push(checked).map_err(resource)?;
373                let spans = covered.ready.take().map(|eight| eight.covered);
374                let mut checked = Checked {
375                    prepared: self.prepared,
376                    required: self.required(),
377                    proof: self.ordinal,
378                    covered: None,
379                    timestamps: self.entry.timestamps.clone(),
380                    checks: checks.into_owned(),
381                    ordering: work.vector().into_owned(),
382                    complete,
383                };
384                checked
385                    .bind_coverage(spans, &work)
386                    .map_err(CheckError::Processing)?;
387                Ok(checked)
388            }
389        }
390    }
391}