Skip to main content

structured_pades/
basic.rs

1//! Original PDF range verification and existing CMS certificate/path bindings.
2
3use crate::{
4    binding::Mapping,
5    timestamp::{Node, Pades},
6};
7use crate::{
8    document::Document,
9    error::{Error, ErrorKind, ReadFailure},
10    prepare::Preparation,
11    range::RangeSource,
12    signature::CoveredError,
13};
14use structured_asn1::{Reader, memory::Work};
15use structured_cades::{
16    basic::{BindError, SignerBinding},
17    counters::Paths,
18};
19use structured_cms::{
20    prepare::{self, PrepareError},
21    provider::SignatureProvider,
22    verify::{self, ContentMode, DocumentBatch, DocumentChecks},
23};
24use structured_ltv::pki::{CounterReceipts, SignerReceipt};
25use structured_pkix::{Certificate, algorithm::digest::AsnDigest, path::WorkingKey};
26
27/// Original preparation/source/provider failure or completed unsupported input.
28/// Each class remains distinguishable; none can produce a positive receipt.
29#[derive(Debug)]
30pub enum CheckError<E, P> {
31    /// Original PDF source acquisition or current/origin resource refusal.
32    Pdf(ReadFailure<E>),
33    /// Original selected algorithm/schema preparation refusal.
34    Prepare(PrepareError<P>),
35    /// Shared verification preparation failure.
36    Check(verify::CheckError<P>),
37    /// Original streaming input/provider failure, including sticky refusal.
38    Pass(verify::PassError<CoveredError<E>, P>),
39    /// Completed unsupported selected mechanism, before payload acquisition.
40    Unsupported(prepare::Part),
41    /// Completed malformed mechanism, before payload acquisition.
42    Malformed(structured_cms::Error),
43}
44
45/// CMS facts from the actual PDF `ByteRange` pass. The private constructor
46/// prevents a result over caller replacement content from entering `PAdES`.
47pub struct Mathematical<'v, 'p, 'm, 'd, 'a, 's, D, G> {
48    prepared: &'v Preparation<'p, 'm, 'd, 'a, 's, D, G>,
49    checks: DocumentChecks<'v>,
50}
51
52impl<'v, 'p, 'm, 'd, 'a, 's, D: Copy + Eq, G: Copy + Eq>
53    Mathematical<'v, 'p, 'm, 'd, 'a, 's, D, G>
54{
55    /// Independent content and arithmetic answers of the original PDF pass.
56    #[must_use]
57    pub fn checks(&self) -> &DocumentChecks<'v> {
58        &self.checks
59    }
60
61    /// The exact original signature whose `ByteRange` was actually processed.
62    #[must_use]
63    pub fn signature(&self) -> crate::signature::Signature<'a, 's, D, G> {
64        self.prepared.container.signature()
65    }
66
67    /// Both the original range input and its selected-key signature passed.
68    /// Certificate association, paths and application trust remain separate.
69    #[must_use]
70    pub fn input_verified(&self) -> bool {
71        self.checks.checks().first().is_some_and(|checked| {
72            matches!(
73                checked.content(),
74                verify::ContentAgreement::Matches | verify::ContentAgreement::Direct
75            ) && checked.signature() == Some(structured_cms::provider::SignatureAnswer::Valid)
76        })
77    }
78
79    /// Compares the actual checked effective key, original SID, included
80    /// certificate and all protected ESS/path restrictions through their one
81    /// existing shared kernel. It does not replay the PDF or its signature.
82    ///
83    /// # Panics
84    /// Panics if the internal single-root verification invariant is violated.
85    pub fn bind<'r, 'path, 'q, P: AsnDigest<'q>>(
86        &'r self,
87        certificate: &'path Certificate<'d>,
88        path: Option<&'path structured_ltv::pki::CheckedPath<'d>>,
89        provider: &'q P,
90        parent: &Reader<'q>,
91    ) -> Result<Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>, BindError<P::Error>>
92    where
93        'd: 'q,
94    {
95        let checked = self.checks.checks().first().expect("one prepared root");
96        let binding = structured_cades::basic::bind(
97            self.prepared.cms,
98            &self.checks,
99            checked,
100            certificate,
101            path,
102            provider,
103            parent,
104        )?;
105        let source = binding.signature();
106        let signature = self.prepared.container.signature();
107        let mapping = Mapping {
108            snapshot: signature.owner().value.stamp.snapshot,
109            root: Node::of(signature.location()),
110            carrier: signature.contents(),
111            cms_root: source.node,
112            cms: source.snapshot,
113        };
114        Ok(Bound {
115            prepared: self.prepared,
116            binding,
117            mapping,
118        })
119    }
120}
121
122/// Actual source-bound root signer/certificate association. Its detailed
123/// negative facts remain available without creating a successful path receipt.
124pub struct Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G> {
125    prepared: &'v Preparation<'p, 'm, 'd, 'a, 's, D, G>,
126    binding: SignerBinding<'p, 'm, 'd, 'path>,
127    pub(crate) mapping: Mapping<'m, 'd, D, G>,
128}
129
130impl<'d, 'path, D: Copy + Eq, G: Copy + Eq> SignerReceipt<'path, 'd>
131    for Bound<'_, '_, '_, 'd, '_, '_, 'path, D, G>
132{
133    type Id = Node;
134    type Document = D;
135    type Generation = G;
136    fn signature(&self) -> structured_ltv::source::Location<Node, D, G> {
137        structured_ltv::source::Location {
138            snapshot: self.mapping.snapshot,
139            node: self.mapping.root,
140        }
141    }
142    fn path(&self) -> Option<&'path structured_ltv::pki::CheckedPath<'d>> {
143        self.binding.path()
144    }
145}
146
147/// The complete checked CMS counter set in its actual PDF source domain.
148pub struct Counters<'r, 'm, 'd, 'path, D, G> {
149    pub(crate) common: Paths<'r, 'm, 'd, 'path>,
150    mapping: Mapping<'m, 'd, D, G>,
151}
152impl<'d, 'path, D: Copy + Eq, G: Copy + Eq> CounterReceipts<'path, 'd>
153    for Counters<'_, '_, 'd, 'path, D, G>
154{
155    type Id = Node;
156    type Document = D;
157    type Generation = G;
158    fn snapshot(&self) -> structured_ltv::source::Snapshot<D, G> {
159        self.mapping.snapshot
160    }
161    fn signature(&self) -> Node {
162        self.mapping.root
163    }
164    fn len(&self) -> usize {
165        self.common.len()
166    }
167    fn counter(&self, ordinal: usize) -> structured_ltv::pki::Counter<'path, 'd, Node> {
168        let actual = self.common.counter(ordinal);
169        structured_ltv::pki::Counter {
170            signature: self.mapping.node(actual.signature),
171            property: self.mapping.node(actual.property),
172            path: actual.path,
173        }
174    }
175}
176
177impl<'v, 'p, 'm, 'd, 'a, 's, 'path, D: Copy + Eq, G: Copy + Eq>
178    Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>
179{
180    /// Existing SID/ESS/effective-key/path result, without projection losses.
181    #[must_use]
182    pub fn binding(&self) -> &SignerBinding<'p, 'm, 'd, 'path> {
183        &self.binding
184    }
185
186    /// Original PDF/CMS preparation, unchanged by the selected path result.
187    #[must_use]
188    pub fn prepared(&self) -> &'v Preparation<'p, 'm, 'd, 'a, 's, D, G> {
189        self.prepared
190    }
191}
192
193/// Complete root and original nested counter composition. Reuses the same
194/// source-domain receipts later consumed by the common LTV participant core.
195pub struct Basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D, G> {
196    bound: &'r Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>,
197    counters: Counters<'r, 'm, 'd, 'path, D, G>,
198    complete: bool,
199}
200
201impl<D: Copy + Eq, G: Copy + Eq> Basic<'_, '_, '_, '_, '_, '_, '_, '_, D, G> {
202    /// Mandatory format, actual PDF input math and whole counter census hold.
203    /// This is independent of trusted time, revocation usability and trust.
204    #[must_use]
205    pub fn all_basic_requirements_met(&self) -> bool {
206        self.complete
207    }
208}
209
210impl<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D: Copy + Eq, G: Copy + Eq>
211    Basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>
212{
213    /// Actual original signer/certificate/path binding used by this stage.
214    #[must_use]
215    pub fn binding(&self) -> &'r Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G> {
216        self.bound
217    }
218    /// The complete original counter receipt set, without caller subsetting.
219    #[must_use]
220    pub fn counters(&self) -> &Counters<'r, 'm, 'd, 'path, D, G> {
221        &self.counters
222    }
223    /// Carries an already checked original CMS timestamp into this PDF's
224    /// source domain. The original model, parent signature and exact input
225    /// remain checked by the shared `CAdES` receipt, without cryptographic replay.
226    pub fn timestamp_actor<'c>(
227        &self,
228        actor: structured_ltv::timestamp::CheckedActor<
229            'c,
230            'd,
231            structured_cades::source::Node,
232            structured_cades::source::DocumentId<'m, 'd>,
233            (),
234            structured_cades::Cades,
235        >,
236        operation: &impl Work,
237    ) -> crate::Result<structured_ltv::timestamp::CheckedActor<'c, 'd, Node, D, G, Pades>> {
238        self.bound.mapping.actor(
239            actor,
240            &self
241                .bound
242                .prepared
243                .container
244                .signature()
245                .owner()
246                .value
247                .session
248                .query(operation),
249        )
250    }
251}
252
253impl<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D: Copy + Eq, G: Copy + Eq>
254    structured_ltv::baseline::BasicStage<'path, 'd>
255    for Basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>
256{
257    type Id = Node;
258    type Document = D;
259    type Generation = G;
260    type Signer = Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>;
261    type Counters = Counters<'r, 'm, 'd, 'path, D, G>;
262    fn signer(&self) -> &Self::Signer {
263        self.bound
264    }
265    fn counters(&self) -> &Self::Counters {
266        &self.counters
267    }
268    fn all_requirements_met(&self) -> bool {
269        self.complete
270    }
271}
272
273/// Joins the actual root association with ALL original counters. The existing
274/// common assembly checks source, selected-root and exact member provenance;
275/// only `PAdES`'s independently prepared root format replaces `CAdES` B-B fields.
276pub fn basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D: Copy + Eq, G: Copy + Eq>(
277    bound: &'r Bound<'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>,
278    counters: Paths<'r, 'm, 'd, 'path>,
279    parent: &Reader<'_>,
280) -> structured_cades::Result<Basic<'r, 'v, 'p, 'm, 'd, 'a, 's, 'path, D, G>> {
281    let common = structured_cades::basic::basic(&bound.binding, counters, parent)?;
282    let counters = Counters {
283        common: common.into_counters(),
284        mapping: bound.mapping,
285    };
286    let complete = bound.prepared.all_basic_format_requirements_met() && bound.binding.is_bound();
287    Ok(Basic {
288        bound,
289        counters,
290        complete,
291    })
292}
293
294impl<'p, 'm, 'd, 'a, 's, D: Copy + Eq, G: Copy + Eq> Preparation<'p, 'm, 'd, 'a, 's, D, G> {
295    /// Resolves and verifies the original detached root through the common
296    /// asynchronous CMS driver. The source is constructed here from the exact
297    /// prepared PDF signature, never accepted as a replacement caller payload.
298    pub async fn verify<'v, S, P>(
299        &'v self,
300        document: &'v Document<'s, S>,
301        provider: &'v P,
302        key: WorkingKey<'v>,
303        parent: &Reader<'v>,
304    ) -> Result<Mathematical<'v, 'p, 'm, 'd, 'a, 's, D, G>, CheckError<S::Error, P::Error>>
305    where
306        'm: 'v,
307        'd: 'v,
308        'a: 'v,
309        's: 'v,
310        S: RangeSource<Document = D, Generation = G>,
311        P: SignatureProvider<'v>,
312    {
313        let message = self.cms.document().message();
314        self.container
315            .require(message, parent)
316            .map_err(|cause| CheckError::Pdf(cause.into()))?;
317        let work = parent.execution();
318        if message.signers().len() != 1 || !message.content().is_detached() {
319            return Err(CheckError::Pdf(
320                Error {
321                    kind: ErrorKind::Syntax(crate::error::Syntax::Container),
322                    location: self.container.signature().contents(),
323                }
324                .into(),
325            ));
326        }
327        let plan = match prepare::resolve(
328            provider,
329            self.cms.signer(),
330            key,
331            parent,
332            structured_cms::binding::Scope::Content(message.content().kind()),
333        )
334        .map_err(CheckError::Prepare)?
335        {
336            prepare::Preparation::Ready(plan) => plan,
337            prepare::Preparation::Unsupported(part) => return Err(CheckError::Unsupported(part)),
338            prepare::Preparation::Malformed(cause) => return Err(CheckError::Malformed(cause)),
339        };
340        let mut requests = work.vector();
341        requests.try_push(plan).map_err(|cause| {
342            CheckError::Pdf(
343                Error {
344                    kind: ErrorKind::Resource(cause),
345                    location: self.container.signature().location(),
346                }
347                .into(),
348            )
349        })?;
350        let batch = DocumentBatch::prepare(
351            provider,
352            message,
353            requests.into_owned(),
354            ContentMode::Detached,
355            parent,
356        )
357        .map_err(CheckError::Check)?;
358        let mut source = document
359            .covered(self.container.signature(), &work)
360            .map_err(CheckError::Pdf)?;
361        let checks = batch
362            .run_detached_async(&mut source)
363            .await
364            .map_err(CheckError::Pass)?;
365        Ok(Mathematical {
366            prepared: self,
367            checks,
368        })
369    }
370}