1use crate::{
4 Error,
5 basic::SignerBinding,
6 counters::Paths,
7 prepare::Preparation,
8 properties::{OpenRole, OpenValue, Properties},
9 source::{DocumentId, Node, Original},
10};
11use structured_asn1::{
12 Reader,
13 memory::{OwnedVec, Work},
14};
15use structured_ltv::source::Snapshot;
16use structured_ltv::{
17 credential::Authentication,
18 pki::{self, Authorities, CounterReceipts, CredentialGroup, CredentialReceipts},
19};
20use structured_pkix::Certificate;
21
22pub struct Context<'scope, 'model, 'document> {
25 source: Original<'model, 'document>,
26 signature: Node,
27 value: OpenValue<'document>,
28 signer: &'scope Certificate<'document>,
29 operation: &'scope Reader<'document>,
30}
31impl<'m, 'd> Context<'_, 'm, 'd> {
32 #[must_use]
34 pub fn source(&self) -> Original<'m, 'd> {
35 self.source
36 }
37 #[must_use]
39 pub fn signature(&self) -> Node {
40 self.signature
41 }
42 #[must_use]
44 pub fn signer(&self) -> &Certificate<'d> {
45 self.signer
46 }
47 #[must_use]
49 pub fn value(&self) -> OpenValue<'d> {
50 self.value
51 }
52 #[must_use]
55 pub fn operation(&self) -> &Reader<'d> {
56 self.operation
57 }
58}
59
60pub trait Provider<'path, 'document> {
71 type Evidence;
73 type Error;
75 fn authenticate(
77 &self,
78 context: Context<'_, '_, 'document>,
79 ) -> Result<Authentication<'path, 'document, Self::Evidence>, Self::Error>;
80}
81
82pub enum Outcome<'path, 'document, E> {
84 Authentication(Authentication<'path, 'document, E>),
86 Absent,
88 MissingAuthorities(E),
90}
91pub struct Checked<'model, 'document, 'path, E> {
93 source: Original<'model, 'document>,
94 outcome: Outcome<'path, 'document, E>,
95}
96impl<'m, 'd, 'p, E> Checked<'m, 'd, 'p, E> {
97 #[must_use]
99 pub fn source(&self) -> Original<'m, 'd> {
100 self.source
101 }
102 #[must_use]
104 pub fn outcome(&self) -> &Outcome<'p, 'd, E> {
105 &self.outcome
106 }
107}
108
109pub struct Group<'model, 'document, 'path, E> {
111 signature: Node,
112 entries: OwnedVec<Checked<'model, 'document, 'path, E>>,
113}
114impl<'d, 'p, E> CredentialGroup<'p, 'd> for Group<'_, 'd, 'p, E> {
115 type Id = Node;
116 fn signature(&self) -> Node {
117 self.signature
118 }
119 fn len(&self) -> usize {
120 self.entries.len()
121 }
122 fn credential(&self, ordinal: usize) -> pki::Credential<'p, 'd, Node> {
123 let entry = &self.entries[ordinal];
124 let Outcome::Authentication(Authentication::Authenticated { authorities, .. }) =
125 &entry.outcome
126 else {
127 unreachable!("only complete groups are exposed as credential receipts");
128 };
129 pki::Credential {
130 signature: self.signature,
131 node: entry.source.node,
132 authorities: *authorities,
133 }
134 }
135}
136
137pub struct Report<'model, 'document, 'path, E> {
140 signature: Original<'model, 'document>,
141 root: Group<'model, 'document, 'path, E>,
142 counters: OwnedVec<Group<'model, 'document, 'path, E>>,
143 complete: bool,
144}
145pub type CounterGroup<'view, 'm, 'd, 'p, E> = (Node, &'view [Checked<'m, 'd, 'p, E>]);
147impl<'m, 'd, 'p, E> Report<'m, 'd, 'p, E> {
148 #[must_use]
150 pub fn root(&self) -> &[Checked<'m, 'd, 'p, E>] {
151 &self.root.entries
152 }
153 #[must_use]
155 pub fn counters(&self) -> usize {
156 self.counters.len()
157 }
158 pub fn counter<W: Work>(
162 &self,
163 ordinal: usize,
164 work: &W,
165 ) -> Result<Option<CounterGroup<'_, 'm, 'd, 'p, E>>, structured_asn1::memory::MemoryError> {
166 work.charge_work(1)?;
167 Ok(self
168 .counters
169 .get(ordinal)
170 .map(|group| (group.signature, &*group.entries)))
171 }
172 #[must_use]
175 pub fn complete(&self) -> Option<Complete<'_, 'm, 'd, 'p, E>> {
176 if self.complete {
177 Some(Complete { report: self })
178 } else {
179 None
180 }
181 }
182}
183
184pub struct Complete<'receipt, 'model, 'document, 'path, E> {
187 pub(crate) report: &'receipt Report<'model, 'document, 'path, E>,
188}
189impl<'m, 'd, 'p, E> CredentialReceipts<'p, 'd> for Complete<'_, 'm, 'd, 'p, E> {
190 type Id = Node;
191 type Document = DocumentId<'m, 'd>;
192 type Generation = ();
193 type Group = Group<'m, 'd, 'p, E>;
194 fn snapshot(&self) -> Snapshot<DocumentId<'m, 'd>, ()> {
195 self.report.signature.snapshot
196 }
197 fn signature(&self) -> Node {
198 self.report.signature.node
199 }
200 fn root(&self) -> Option<&Self::Group> {
201 if self.report.root.entries.is_empty() {
202 None
203 } else {
204 Some(&self.report.root)
205 }
206 }
207 fn counters(&self) -> usize {
208 self.report.counters.len()
209 }
210 fn counter(&self, ordinal: usize) -> (Node, &Self::Group) {
211 let group = &self.report.counters[ordinal];
212 (group.signature, group)
213 }
214}
215
216#[derive(Debug)]
218pub enum CheckError<E> {
219 Processing(Error),
221 Unbound {
223 signature: Node,
225 },
226 Nonconforming {
230 signature: Node,
232 },
233 Provider {
235 node: Node,
237 cause: E,
239 },
240}
241
242pub fn check<'m, 'd, 'p, E, P: Provider<'p, 'd, Evidence = E>>(
246 prepared: &Preparation<'m, 'd>,
247 signer: &SignerBinding<'_, 'm, 'd, 'p>,
248 counters: &Paths<'_, 'm, 'd, 'p>,
249 provider: &P,
250 operation: &Reader<'d>,
251) -> Result<Report<'m, 'd, 'p, E>, CheckError<P::Error>> {
252 let processing = CheckError::Processing;
253 prepared
254 .document()
255 .message()
256 .require_session(operation, "cades.credentials")
257 .map_err(|cause| processing(Error::Cms(cause)))?;
258 let selected = prepared.document().locate(prepared.signer().span());
259 operation
260 .charge(1)
261 .map_err(|cause| processing(Error::encoding(cause, "cades.credentials")))?;
262 if !core::ptr::eq(prepared, signer.prepared())
263 || selected.snapshot != counters.snapshot()
264 || selected.node != counters.signature()
265 || counters.len() != prepared.counters().len()
266 {
267 return Err(processing(Error::Foreign {
268 at: selected.node.span().start,
269 }));
270 }
271 if !signer.is_bound() {
272 return Err(CheckError::Unbound {
273 signature: selected.node,
274 });
275 }
276 if !prepared.basic_format_requirements_met() {
280 return Err(CheckError::Nonconforming {
281 signature: selected.node,
282 });
283 }
284 for (ordinal, expected) in prepared.counters().iter().enumerate() {
286 operation
287 .charge(1)
288 .map_err(|cause| processing(Error::encoding(cause, "cades.credentials")))?;
289 if counters.counter(ordinal).signature != expected.node() {
290 return Err(processing(Error::Foreign {
291 at: expected.node().span().start,
292 }));
293 }
294 }
295 let mut complete = true;
296 let root = group(
297 prepared,
298 selected.node,
299 prepared.properties(),
300 signer.certificate(),
301 provider,
302 operation,
303 &mut complete,
304 )?;
305 let work = operation.execution();
306 let mut groups = work.vector();
307 for (ordinal, counter) in prepared.counters().iter().enumerate() {
308 operation
309 .charge(1)
310 .map_err(|cause| processing(Error::encoding(cause, "cades.credentials")))?;
311 let Some(properties) = counter.properties() else {
312 return Err(CheckError::Unbound {
313 signature: counter.node(),
314 });
315 };
316 let path = counters.counter(ordinal).path;
317 let target = path.members().last().ok_or(CheckError::Unbound {
318 signature: counter.node(),
319 })?;
320 let group = group(
321 prepared,
322 counter.node(),
323 properties,
324 target.certificate(),
325 provider,
326 operation,
327 &mut complete,
328 )?;
329 if !group.entries.is_empty() {
330 groups.try_push(group).map_err(|cause| {
331 processing(Error::resource(
332 cause,
333 "cades.credentials",
334 counter.node().span().start,
335 ))
336 })?;
337 }
338 }
339 Ok(Report {
340 signature: selected,
341 root,
342 counters: groups.into_owned(),
343 complete,
344 })
345}
346
347fn group<'m, 'd, 'p, E, P: Provider<'p, 'd, Evidence = E>>(
348 prepared: &Preparation<'m, 'd>,
349 signature: Node,
350 properties: &Properties<'d>,
351 signer: &Certificate<'d>,
352 provider: &P,
353 operation: &Reader<'d>,
354 complete: &mut bool,
355) -> Result<Group<'m, 'd, 'p, E>, CheckError<P::Error>> {
356 let work = operation.execution();
357 let mut entries = work.vector();
358 for value in properties.open_values() {
359 work.charge_work(1).map_err(|cause| {
360 CheckError::Processing(Error::resource(
361 cause,
362 "cades.credentials",
363 signature.span().start,
364 ))
365 })?;
366 if !matches!(
367 value.role,
368 OpenRole::AttributeCertificate
369 | OpenRole::OtherAttributeCertificate
370 | OpenRole::SignedAssertion
371 ) {
372 continue;
373 }
374 let source = prepared.document().locate(value.enclosure.span());
375 let outcome = if value.value.is_none() {
376 *complete = false;
377 Outcome::Absent
378 } else {
379 match provider
380 .authenticate(Context {
381 source,
382 signature,
383 value: *value,
384 signer,
385 operation,
386 })
387 .map_err(|cause| CheckError::Provider {
388 node: source.node,
389 cause,
390 })? {
391 Authentication::Authenticated {
392 evidence,
393 authorities: Authorities::Paths([]),
394 } => {
395 *complete = false;
396 Outcome::MissingAuthorities(evidence)
397 }
398 Authentication::Unsupported => {
399 *complete = false;
400 Outcome::Authentication(Authentication::Unsupported)
401 }
402 Authentication::Rejected => {
403 *complete = false;
404 Outcome::Authentication(Authentication::Rejected)
405 }
406 authenticated @ Authentication::Authenticated { .. } => {
407 Outcome::Authentication(authenticated)
408 }
409 }
410 };
411 entries
412 .try_push(Checked { source, outcome })
413 .map_err(|cause| {
414 CheckError::Processing(Error::resource(
415 cause,
416 "cades.credentials",
417 source.node.span().start,
418 ))
419 })?;
420 }
421 Ok(Group {
422 signature,
423 entries: entries.into_owned(),
424 })
425}