Skip to main content

structured_pades/
credential.rs

1//! Original protected credential authentication in the PDF source domain.
2
3use crate::{
4    Session,
5    basic::Basic,
6    binding::Mapping,
7    error::{Error as PdfError, ErrorKind},
8    model::admit,
9    query::Query,
10    range::RangeSource,
11    timestamp::Node,
12};
13use structured_asn1::memory::{Memory, OwnedVec, Work};
14use structured_cades::credential as cms;
15use structured_ltv::{
16    pki::{self, CredentialGroup, CredentialReceipts},
17    source::{Query as _, Snapshot},
18};
19
20/// Every original root/counter credential's independent authentication result.
21pub struct Report<'m, 'd, 'p, 's, D, G, E> {
22    common: cms::Report<'m, 'd, 'p, E>,
23    mapping: Mapping<'m, 'd, D, G>,
24    session: &'s Session,
25}
26/// One original complete group projected without cloning retained evidence.
27pub struct Group<'a, 'm, 'd, 'p, D, G, E> {
28    pub(crate) common: &'a cms::Group<'m, 'd, 'p, E>,
29    mapping: Mapping<'m, 'd, D, G>,
30}
31impl<'d, 'p, D: Copy, G: Copy, E> CredentialGroup<'p, 'd> for Group<'_, '_, 'd, 'p, D, G, E> {
32    type Id = Node;
33    fn signature(&self) -> Node {
34        self.mapping.node(self.common.signature())
35    }
36    fn len(&self) -> usize {
37        self.common.len()
38    }
39    fn credential(&self, ordinal: usize) -> pki::Credential<'p, 'd, Node> {
40        let actual = self.common.credential(ordinal);
41        pki::Credential {
42            signature: self.mapping.node(actual.signature),
43            node: self.mapping.node(actual.node),
44            authorities: actual.authorities,
45        }
46    }
47}
48/// Complete authenticated original census with stable source-domain views.
49pub struct Complete<'a, 'm, 'd, 'p, D, G, E> {
50    common: cms::Complete<'a, 'm, 'd, 'p, E>,
51    pub(crate) mapping: Mapping<'m, 'd, D, G>,
52    pub(crate) root: Option<Group<'a, 'm, 'd, 'p, D, G, E>>,
53    pub(crate) counters: OwnedVec<Group<'a, 'm, 'd, 'p, D, G, E>, Memory>,
54}
55impl<'a, 'm, 'd, 'p, D: Copy + Eq, G: Copy + Eq, E> CredentialReceipts<'p, 'd>
56    for Complete<'a, 'm, 'd, 'p, D, G, E>
57{
58    type Id = Node;
59    type Document = D;
60    type Generation = G;
61    type Group = Group<'a, 'm, 'd, 'p, D, G, E>;
62    fn snapshot(&self) -> Snapshot<D, G> {
63        self.mapping.snapshot
64    }
65    fn signature(&self) -> Node {
66        self.mapping.root
67    }
68    fn root(&self) -> Option<&Self::Group> {
69        self.root.as_ref()
70    }
71    fn counters(&self) -> usize {
72        self.counters.len()
73    }
74    fn counter(&self, ordinal: usize) -> (Node, &Self::Group) {
75        let group = &self.counters[ordinal];
76        (group.signature(), group)
77    }
78}
79impl<'m, 'd, 'p, D: Copy + Eq, G: Copy + Eq, E> Report<'m, 'd, 'p, '_, D, G, E> {
80    /// Full original authentication details, including every negative result.
81    #[must_use]
82    pub fn common(&self) -> &cms::Report<'m, 'd, 'p, E> {
83        &self.common
84    }
85    /// Issues a full participant receipt only if every original authentication
86    /// supplied its actual authorities. Current work funds the group views;
87    /// authentication and retained evidence are neither replayed nor copied.
88    ///
89    /// # Panics
90    /// Panics if the internally retained complete counter-group census is inconsistent.
91    pub fn complete<'a>(
92        &'a self,
93        operation: &impl Work,
94    ) -> crate::Result<Option<Complete<'a, 'm, 'd, 'p, D, G, E>>> {
95        let work = self.session.query(operation);
96        let location = self.mapping.carrier;
97        admit(&work, location, 1)?;
98        let Some(common) = self.common.complete() else {
99            return Ok(None);
100        };
101        let root = common.root_group().map(|value| Group {
102            common: value,
103            mapping: self.mapping,
104        });
105        let mut counters = work.vector();
106        counters
107            .try_reserve_exact(common.counters())
108            .map_err(|cause| PdfError {
109                kind: ErrorKind::Resource(cause),
110                location,
111            })?;
112        for ordinal in 0..common.counters() {
113            let value = common
114                .counter_group(ordinal, &work)
115                .map_err(|cause| PdfError {
116                    kind: ErrorKind::Resource(cause),
117                    location,
118                })?
119                .expect("bounded original counter group");
120            counters
121                .try_push(Group {
122                    common: value,
123                    mapping: self.mapping,
124                })
125                .map_err(|cause| PdfError {
126                    kind: ErrorKind::Resource(cause),
127                    location,
128                })?;
129        }
130        Ok(Some(Complete {
131            common,
132            mapping: self.mapping,
133            root,
134            counters: counters.into_owned(),
135        }))
136    }
137}
138impl<'a, 'm, 'd, 'p, D, G, E> Complete<'a, 'm, 'd, 'p, D, G, E> {
139    /// Exact existing CMS authentication receipt used for format dependencies.
140    #[must_use]
141    pub fn common(&self) -> &cms::Complete<'a, 'm, 'd, 'p, E> {
142        &self.common
143    }
144}
145
146/// Original PDF/current refusal versus unchanged protected-property failure.
147#[derive(Debug)]
148pub enum Error<E> {
149    /// Original source, membership or current resource refusal.
150    Pdf(PdfError),
151    /// Original source generation or scoped current admission refused.
152    Scope(structured_ltv::source::ScopeError<Node>),
153    /// Original `PAdES` basic requirements prevent protected authentication.
154    Nonconforming {
155        /// Actual source signature dictionary.
156        location: crate::source::Location,
157    },
158    /// Original CMS schema/source/handler cause in the carried domain.
159    Cms(cms::CheckError<E>),
160}
161impl<'m, 'd, 'p, 's, D: Copy + Eq, G: Copy + Eq> Basic<'_, '_, '_, 'm, 'd, '_, 's, 'p, D, G> {
162    /// Derives the original complete participant census from the SAME root,
163    /// all counters, authenticated credentials and complete TSA-path receipt.
164    /// No caller-defined positive receipt or selected successful subset is used.
165    pub fn participants<'r, 'certificate, E, S: RangeSource<Document = D, Generation = G>>(
166        &'p self,
167        credentials: &'p Complete<'r, 'm, 'd, 'p, D, G, E>,
168        timestamps: &'p structured_ltv::timestamp::TimestampPaths<
169            'p,
170            'certificate,
171            'd,
172            Node,
173            D,
174            G,
175            crate::timestamp::Pades,
176        >,
177        query: &Query<'_, '_, S>,
178    ) -> Result<
179        pki::ParticipantRequirements<'p, 'd, Node, D, G, crate::timestamp::Pades>,
180        pki::ParticipantError<Node>,
181    > {
182        let source = self.binding().prepared().container.signature();
183        query.admit(self.binding().mapping.root, 1)?;
184        if !query.belongs(source.owner().value.session)
185            || credentials.mapping.snapshot != self.binding().mapping.snapshot
186            || credentials.mapping.root != self.binding().mapping.root
187        {
188            return Err(pki::ParticipantError::Foreign {
189                signature: self.binding().mapping.root,
190            });
191        }
192        pki::participants(
193            crate::timestamp::ISSUER,
194            self.binding(),
195            self.counters(),
196            credentials,
197            timestamps,
198            query,
199        )
200    }
201
202    /// Authenticates every original protected credential using the shared
203    /// property kernel and this operation's actual scoped producing reader.
204    /// `PAdES` root requirements are checked before the first handler call.
205    pub fn authenticate_credentials<
206        P: cms::Provider<'p, 'd>,
207        S: RangeSource<Document = D, Generation = G>,
208    >(
209        &self,
210        provider: &P,
211        query: &Query<'_, '_, S>,
212    ) -> Result<Report<'m, 'd, 'p, 's, D, G, P::Evidence>, Error<P::Error>> {
213        let bound = self.binding();
214        let prepared = bound.prepared();
215        let signature = prepared.container.signature();
216        let location = signature.location();
217        query.admit(bound.mapping.root, 1).map_err(Error::Scope)?;
218        if !query.belongs(signature.owner().value.session)
219            || query.snapshot() != bound.mapping.snapshot
220        {
221            return Err(Error::Pdf(PdfError {
222                kind: ErrorKind::Foreign,
223                location,
224            }));
225        }
226        if !self.all_basic_requirements_met() {
227            return Err(Error::Nonconforming { location });
228        }
229        let work = signature.owner().value.session.query(query.execution());
230        let reader = prepared
231            .cms
232            .signer()
233            .fields_source()
234            .reader_in(&work)
235            .map_err(|cause| Error::Cms(cms::CheckError::Processing(cause.into())))?;
236        let common = cms::check_properties(
237            prepared.cms,
238            bound.binding(),
239            &self.counters().common,
240            provider,
241            &reader,
242        )
243        .map_err(Error::Cms)?;
244        query.admit(bound.mapping.root, 1).map_err(Error::Scope)?;
245        Ok(Report {
246            common,
247            mapping: bound.mapping,
248            session: signature.owner().value.session,
249        })
250    }
251}