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, ScopedReader,
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 ScopedReader<'document, 'scope>,
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) -> &ScopedReader<'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<'m, 'd, 'p, E> Group<'m, 'd, 'p, E> {
115 #[must_use]
117 pub fn entries(&self) -> &[Checked<'m, 'd, 'p, E>] {
118 &self.entries
119 }
120}
121impl<'d, 'p, E> CredentialGroup<'p, 'd> for Group<'_, 'd, 'p, E> {
122 type Id = Node;
123 fn signature(&self) -> Node {
124 self.signature
125 }
126 fn len(&self) -> usize {
127 self.entries.len()
128 }
129 fn credential(&self, ordinal: usize) -> pki::Credential<'p, 'd, Node> {
130 let entry = &self.entries[ordinal];
131 let Outcome::Authentication(Authentication::Authenticated { authorities, .. }) =
132 &entry.outcome
133 else {
134 unreachable!("only complete groups are exposed as credential receipts");
135 };
136 pki::Credential {
137 signature: self.signature,
138 node: entry.source.node,
139 authorities: *authorities,
140 }
141 }
142}
143
144pub struct Report<'model, 'document, 'path, E> {
147 signature: Original<'model, 'document>,
148 root: Group<'model, 'document, 'path, E>,
149 counters: OwnedVec<Group<'model, 'document, 'path, E>>,
150 complete: bool,
151}
152pub type CounterGroup<'view, 'm, 'd, 'p, E> = (Node, &'view [Checked<'m, 'd, 'p, E>]);
154impl<'m, 'd, 'p, E> Report<'m, 'd, 'p, E> {
155 #[must_use]
157 pub fn root(&self) -> &[Checked<'m, 'd, 'p, E>] {
158 &self.root.entries
159 }
160 #[must_use]
162 pub fn counters(&self) -> usize {
163 self.counters.len()
164 }
165 pub fn counter<W: Work>(
169 &self,
170 ordinal: usize,
171 work: &W,
172 ) -> Result<Option<CounterGroup<'_, 'm, 'd, 'p, E>>, structured_asn1::memory::MemoryError> {
173 work.charge_work(1)?;
174 Ok(self
175 .counters
176 .get(ordinal)
177 .map(|group| (group.signature, &*group.entries)))
178 }
179 #[must_use]
182 pub fn complete(&self) -> Option<Complete<'_, 'm, 'd, 'p, E>> {
183 if self.complete {
184 Some(Complete { report: self })
185 } else {
186 None
187 }
188 }
189}
190
191pub struct Complete<'receipt, 'model, 'document, 'path, E> {
194 pub(crate) report: &'receipt Report<'model, 'document, 'path, E>,
195}
196impl<'r, 'm, 'd, 'p, E> Complete<'r, 'm, 'd, 'p, E> {
197 #[must_use]
200 pub fn root_group(&self) -> Option<&'r Group<'m, 'd, 'p, E>> {
201 if self.report.root.entries.is_empty() {
202 None
203 } else {
204 Some(&self.report.root)
205 }
206 }
207 pub fn counter_group(
210 &self,
211 ordinal: usize,
212 work: &impl Work,
213 ) -> Result<Option<&'r Group<'m, 'd, 'p, E>>, structured_asn1::memory::MemoryError> {
214 work.charge_work(1)?;
215 Ok(self.report.counters.get(ordinal))
216 }
217}
218impl<'m, 'd, 'p, E> CredentialReceipts<'p, 'd> for Complete<'_, 'm, 'd, 'p, E> {
219 type Id = Node;
220 type Document = DocumentId<'m, 'd>;
221 type Generation = ();
222 type Group = Group<'m, 'd, 'p, E>;
223 fn snapshot(&self) -> Snapshot<DocumentId<'m, 'd>, ()> {
224 self.report.signature.snapshot
225 }
226 fn signature(&self) -> Node {
227 self.report.signature.node
228 }
229 fn root(&self) -> Option<&Self::Group> {
230 if self.report.root.entries.is_empty() {
231 None
232 } else {
233 Some(&self.report.root)
234 }
235 }
236 fn counters(&self) -> usize {
237 self.report.counters.len()
238 }
239 fn counter(&self, ordinal: usize) -> (Node, &Self::Group) {
240 let group = &self.report.counters[ordinal];
241 (group.signature, group)
242 }
243}
244
245#[derive(Debug)]
247pub enum CheckError<E> {
248 Processing(Error),
250 Unbound {
252 signature: Node,
254 },
255 Nonconforming {
259 signature: Node,
261 },
262 Provider {
264 node: Node,
266 cause: E,
268 },
269}
270
271pub fn check<'m, 'd, 'p, E, P: Provider<'p, 'd, Evidence = E>>(
275 prepared: &Preparation<'m, 'd>,
276 signer: &SignerBinding<'_, 'm, 'd, 'p>,
277 counters: &Paths<'_, 'm, 'd, 'p>,
278 provider: &P,
279 operation: &Reader<'d>,
280) -> Result<Report<'m, 'd, 'p, E>, CheckError<P::Error>> {
281 prepared
282 .document()
283 .message()
284 .require_session(operation, "cades.credentials")
285 .map_err(|cause| CheckError::Processing(crate::Error::Cms(cause)))?;
286 operation.charge(1).map_err(|cause| {
287 CheckError::Processing(crate::Error::encoding(cause, "cades.credentials"))
288 })?;
289 if !prepared.basic_format_requirements_met() {
290 return Err(CheckError::Nonconforming {
291 signature: prepared.document().locate(prepared.signer().span()).node,
292 });
293 }
294 check_properties(prepared, signer, counters, provider, operation)
295}
296
297pub fn check_properties<'m, 'd, 'p, E, P: Provider<'p, 'd, Evidence = E>>(
304 prepared: &Preparation<'m, 'd>,
305 signer: &SignerBinding<'_, 'm, 'd, 'p>,
306 counters: &Paths<'_, 'm, 'd, 'p>,
307 provider: &P,
308 operation: &ScopedReader<'d, '_>,
309) -> Result<Report<'m, 'd, 'p, E>, CheckError<P::Error>> {
310 let processing = CheckError::Processing;
311 prepared
312 .document()
313 .message()
314 .require_session(operation, "cades.credentials")
315 .map_err(|cause| processing(Error::Cms(cause)))?;
316 let selected = prepared.document().locate(prepared.signer().span());
317 operation
318 .charge(1)
319 .map_err(|cause| processing(Error::encoding(cause, "cades.credentials")))?;
320 if !core::ptr::eq(prepared, signer.prepared())
321 || selected.snapshot != counters.snapshot()
322 || selected.node != counters.signature()
323 || counters.len() != prepared.counters().len()
324 {
325 return Err(processing(Error::Foreign {
326 at: selected.node.span().start,
327 }));
328 }
329 if !signer.is_bound() {
330 return Err(CheckError::Unbound {
331 signature: selected.node,
332 });
333 }
334 preflight_properties(prepared, counters, operation)?;
335 let mut complete = true;
336 let root = group(
337 prepared,
338 selected.node,
339 prepared.properties(),
340 signer.certificate(),
341 provider,
342 operation,
343 &mut complete,
344 )?;
345 let work = operation.execution();
346 let mut groups = work.vector();
347 for (ordinal, counter) in prepared.counters().iter().enumerate() {
348 operation
349 .charge(1)
350 .map_err(|cause| processing(Error::encoding(cause, "cades.credentials")))?;
351 let Some(properties) = counter.properties() else {
352 return Err(CheckError::Unbound {
353 signature: counter.node(),
354 });
355 };
356 let path = counters.counter(ordinal).path;
357 let target = path.members().last().ok_or(CheckError::Unbound {
358 signature: counter.node(),
359 })?;
360 let group = group(
361 prepared,
362 counter.node(),
363 properties,
364 target.certificate(),
365 provider,
366 operation,
367 &mut complete,
368 )?;
369 if !group.entries.is_empty() {
370 groups.try_push(group).map_err(|cause| {
371 processing(Error::resource(
372 cause,
373 "cades.credentials",
374 counter.node().span().start,
375 ))
376 })?;
377 }
378 }
379 Ok(Report {
380 signature: selected,
381 root,
382 counters: groups.into_owned(),
383 complete,
384 })
385}
386
387fn preflight_properties<E>(
388 prepared: &Preparation<'_, '_>,
389 counters: &Paths<'_, '_, '_, '_>,
390 operation: &ScopedReader<'_, '_>,
391) -> Result<(), CheckError<E>> {
392 if !prepared.signer().faults().is_empty()
395 || !prepared.profile().faults().is_empty()
396 || !prepared.profile().signing().faults().is_empty()
397 || !prepared.properties().faults().is_empty()
398 {
399 return Err(CheckError::Nonconforming {
400 signature: prepared.document().locate(prepared.signer().span()).node,
401 });
402 }
403 for (ordinal, expected) in prepared.counters().iter().enumerate() {
404 operation
405 .charge(1)
406 .map_err(|cause| CheckError::Processing(Error::encoding(cause, "cades.credentials")))?;
407 if counters.counter(ordinal).signature != expected.node() {
408 return Err(CheckError::Processing(Error::Foreign {
409 at: expected.node().span().start,
410 }));
411 }
412 if expected.malformed().is_some()
413 || expected
414 .properties()
415 .is_none_or(|value| !value.faults().is_empty())
416 {
417 return Err(CheckError::Nonconforming {
418 signature: expected.node(),
419 });
420 }
421 }
422 Ok(())
423}
424
425fn group<'m, 'd, 'p, E, P: Provider<'p, 'd, Evidence = E>>(
426 prepared: &Preparation<'m, 'd>,
427 signature: Node,
428 properties: &Properties<'d>,
429 signer: &Certificate<'d>,
430 provider: &P,
431 operation: &ScopedReader<'d, '_>,
432 complete: &mut bool,
433) -> Result<Group<'m, 'd, 'p, E>, CheckError<P::Error>> {
434 let work = operation.execution();
435 let mut entries = work.vector();
436 for value in properties.open_values() {
437 work.charge_work(1).map_err(|cause| {
438 CheckError::Processing(Error::resource(
439 cause,
440 "cades.credentials",
441 signature.span().start,
442 ))
443 })?;
444 if !matches!(
445 value.role,
446 OpenRole::AttributeCertificate
447 | OpenRole::OtherAttributeCertificate
448 | OpenRole::SignedAssertion
449 ) {
450 continue;
451 }
452 let source = prepared.document().locate(value.enclosure.span());
453 let outcome = if value.value.is_none() {
454 *complete = false;
455 Outcome::Absent
456 } else {
457 match provider
458 .authenticate(Context {
459 source,
460 signature,
461 value: *value,
462 signer,
463 operation,
464 })
465 .map_err(|cause| CheckError::Provider {
466 node: source.node,
467 cause,
468 })? {
469 Authentication::Authenticated {
470 evidence,
471 authorities: Authorities::Paths([]),
472 } => {
473 *complete = false;
474 Outcome::MissingAuthorities(evidence)
475 }
476 Authentication::Unsupported => {
477 *complete = false;
478 Outcome::Authentication(Authentication::Unsupported)
479 }
480 Authentication::Rejected => {
481 *complete = false;
482 Outcome::Authentication(Authentication::Rejected)
483 }
484 authenticated @ Authentication::Authenticated { .. } => {
485 Outcome::Authentication(authenticated)
486 }
487 }
488 };
489 entries
490 .try_push(Checked { source, outcome })
491 .map_err(|cause| {
492 CheckError::Processing(Error::resource(
493 cause,
494 "cades.credentials",
495 source.node.span().start,
496 ))
497 })?;
498 }
499 Ok(Group {
500 signature,
501 entries: entries.into_owned(),
502 })
503}