1use super::{AaPath, Permission};
3use crate::attribute_certificate::AttributeCertificate;
4use crate::attribute_certificate::controls;
5use crate::attribute_certificate::validation::authentication::{self, AlgorithmAgreement};
6use crate::attribute_certificate::validation::{Check, Rejection, charge};
7use crate::{Error, Result};
8use structured_asn1::memory::{Execution, Work};
9use structured_asn1::{Oid, Span};
10use structured_pkix::Certificate;
11use structured_pkix::extension::Recognizer;
12use structured_pkix::path::{self, Anchor, EffectiveAnchor, Interval, Verify, Watch, WorkingKey};
13
14#[derive(Debug)]
16pub enum PathCheck<T, P> {
17 Accepted(T),
20 MissingAaCa,
23 TrustBoundary,
26 AnchorKey,
28 Controls(Rejection),
30 Path(P),
32 Incomplete(path::Truncation),
34}
35
36#[derive(Debug)]
38pub enum PathError<V, R> {
39 Processing(Error),
41 Verification(V),
43 Recognition(R),
45}
46impl<V, R> From<Error> for PathError<V, R> {
47 fn from(error: Error) -> Self {
48 Self::Processing(error)
49 }
50}
51
52pub type PathResult<'p, 'a, V, R> =
54 core::result::Result<PathCheck<ValidatedPath<'p, 'a>, path::Rejection<'a>>, PathError<V, R>>;
55
56#[derive(Debug)]
63pub struct ValidatedPath<'p, 'a> {
64 controls: AaPath<'p, 'a>,
65 facts: path::ValidationFacts<'a>,
66 anchor: EffectiveAnchor<'a>,
67 interval: Interval,
68}
69
70impl<'p, 'a> ValidatedPath<'p, 'a> {
71 #[must_use]
73 pub fn certificates(&self) -> &[&Certificate<'a>] {
74 self.controls.certificates()
75 }
76 #[must_use]
78 pub fn anchor(&self) -> EffectiveAnchor<'a> {
79 self.anchor
80 }
81 #[must_use]
83 pub fn facts(&self) -> &path::ValidationFacts<'a> {
84 &self.facts
85 }
86 #[must_use]
88 pub fn interval(&self) -> Interval {
89 self.interval
90 }
91 pub fn allows<W: Work>(&self, kind: Oid<'_>, work: &W) -> Result<Permission> {
94 self.controls.allows(kind, work)
95 }
96 pub fn authenticate<'r, 'c, V, W: Work>(
101 &'r self,
102 certificate: &'r AttributeCertificate<'c>,
103 verifier: &V,
104 work: &W,
105 ) -> core::result::Result<
106 authentication::Outcome<Issued<'r, 'p, 'c, 'a>>,
107 authentication::AuthenticationError<<V as AlgorithmAgreement>::Error>,
108 >
109 where
110 V: AlgorithmAgreement,
111 for<'op> V: Verify<Execution<'op>, Error = <V as AlgorithmAgreement>::Error>,
112 {
113 Ok(
114 match certificate.authenticate(
115 self.controls.certificates[0],
116 self.facts.key,
117 self.interval,
118 verifier,
119 work,
120 )? {
121 authentication::Outcome::Authenticated(facts) => {
122 authentication::Outcome::Authenticated(Issued { path: self, facts })
123 }
124 authentication::Outcome::Rejected(error) => {
125 authentication::Outcome::Rejected(error)
126 }
127 authentication::Outcome::Unsupported(stage) => {
128 authentication::Outcome::Unsupported(stage)
129 }
130 },
131 )
132 }
133}
134
135#[derive(Debug)]
138pub struct Issued<'r, 'p, 'c, 'a> {
139 path: &'r ValidatedPath<'p, 'a>,
140 facts: authentication::Authenticated<'r, 'c, 'a>,
141}
142impl<'r, 'p, 'c, 'a> Issued<'r, 'p, 'c, 'a> {
143 #[must_use]
145 pub fn path(&self) -> &'r ValidatedPath<'p, 'a> {
146 self.path
147 }
148 #[must_use]
150 pub fn facts(&self) -> &authentication::Authenticated<'r, 'c, 'a> {
151 &self.facts
152 }
153 pub fn allows<W: Work>(&self, kind: Oid<'_>, work: &W) -> Result<Permission> {
158 self.path.allows(kind, work)
159 }
160}
161
162pub fn validate<'p, 'a, V, T, R, W>(
182 certificates: &'p [&'a Certificate<'a>],
183 trusted_aa_ca: &'a Certificate<'a>,
184 context: &path::ValidationContext<'a, '_, V, T, R, W>,
185) -> PathResult<'p, 'a, <V as Verify<W>>::Error, R::Error>
186where
187 W: Work,
188 V: Verify<W>,
189 for<'op> V: Verify<Execution<'op>, Error = <V as Verify<W>>::Error>,
190 T: Watch,
191 R: Recognizer<W>,
192{
193 validate_inner(certificates, trusted_aa_ca, None, context)
194}
195
196pub fn validate_with_key<'p, 'a, V, T, R, W>(
200 certificates: &'p [&'a Certificate<'a>],
201 trusted_aa_ca: &'a Certificate<'a>,
202 anchor_key: WorkingKey<'a>,
203 context: &path::ValidationContext<'a, '_, V, T, R, W>,
204) -> PathResult<'p, 'a, <V as Verify<W>>::Error, R::Error>
205where
206 W: Work,
207 V: Verify<W>,
208 for<'op> V: Verify<Execution<'op>, Error = <V as Verify<W>>::Error>,
209 T: Watch,
210 R: Recognizer<W>,
211{
212 validate_inner(certificates, trusted_aa_ca, Some(anchor_key), context)
213}
214
215enum RecognitionError<E> {
216 Processing(Error),
217 Callback(E),
218}
219
220pub(in crate::attribute_certificate::validation) enum VerificationError<E> {
221 Processing(Error),
222 Callback(E),
223}
224pub(in crate::attribute_certificate::validation) struct ScopedVerifier<'r, V>(
225 pub(in crate::attribute_certificate::validation) &'r V,
226);
227impl<W: Work, V> Verify<W> for ScopedVerifier<'_, V>
228where
229 V: Verify<W>,
230 for<'op> V: Verify<Execution<'op>, Error = <V as Verify<W>>::Error>,
231{
232 type Error = VerificationError<<V as Verify<W>>::Error>;
233 fn verify(
234 &self,
235 algorithm: &structured_pkix::AlgorithmIdentifier<'_>,
236 key: WorkingKey<'_>,
237 input: &[u8],
238 signature: structured_pkix::signature::SignatureValue<'_>,
239 work: &W,
240 ) -> core::result::Result<path::Verified, Self::Error> {
241 let execution = signature.execution_in(work);
242 charge(&execution, 1, signature.span().start).map_err(VerificationError::Processing)?;
243 self.0
244 .verify(algorithm, key, input, signature, &execution)
245 .map_err(VerificationError::Callback)
246 }
247}
248struct Recognized<'r, R> {
249 inner: &'r R,
250 source: Span,
251}
252impl<W: Work, R: Recognizer<W>> Recognizer<W> for Recognized<'_, R> {
253 type Error = RecognitionError<R::Error>;
254 fn recognizes(&self, kind: Oid<'_>, work: &W) -> core::result::Result<bool, Self::Error> {
255 charge(work, 10, self.source.start).map_err(RecognitionError::Processing)?;
256 if kind == controls::IDENTIFIER {
257 Ok(true)
258 } else {
259 self.inner
260 .recognizes(kind, work)
261 .map_err(RecognitionError::Callback)
262 }
263 }
264 fn recognizes_other_name(
265 &self,
266 kind: Oid<'_>,
267 work: &W,
268 ) -> core::result::Result<bool, Self::Error> {
269 self.inner
270 .recognizes_other_name(kind, work)
271 .map_err(RecognitionError::Callback)
272 }
273 fn recognizes_name_constraint(
274 &self,
275 kind: Oid<'_>,
276 work: &W,
277 ) -> core::result::Result<bool, Self::Error> {
278 self.inner
279 .recognizes_name_constraint(kind, work)
280 .map_err(RecognitionError::Callback)
281 }
282 fn recognizes_access_method(
283 &self,
284 kind: Oid<'_>,
285 work: &W,
286 ) -> core::result::Result<bool, Self::Error> {
287 self.inner
288 .recognizes_access_method(kind, work)
289 .map_err(RecognitionError::Callback)
290 }
291 fn recognizes_statement(
292 &self,
293 kind: Oid<'_>,
294 work: &W,
295 ) -> core::result::Result<bool, Self::Error> {
296 self.inner
297 .recognizes_statement(kind, work)
298 .map_err(RecognitionError::Callback)
299 }
300}
301
302fn validate_inner<'p, 'a, V, T, R, W>(
303 certificates: &'p [&'a Certificate<'a>],
304 trusted_aa_ca: &'a Certificate<'a>,
305 key: Option<WorkingKey<'a>>,
306 context: &path::ValidationContext<'a, '_, V, T, R, W>,
307) -> PathResult<'p, 'a, <V as Verify<W>>::Error, R::Error>
308where
309 W: Work,
310 V: Verify<W>,
311 for<'op> V: Verify<Execution<'op>, Error = <V as Verify<W>>::Error>,
312 T: Watch,
313 R: Recognizer<W>,
314{
315 let Some((aa_ca, chain)) = certificates.split_last() else {
316 return Ok(PathCheck::MissingAaCa);
317 };
318 if chain.is_empty() {
319 return Ok(PathCheck::MissingAaCa);
320 }
321 if !core::ptr::eq(*aa_ca, trusted_aa_ca) {
322 return Ok(PathCheck::TrustBoundary);
323 }
324 if chain.len() > context.bounds.depth {
325 return Ok(PathCheck::Incomplete(path::Truncation::Depth));
326 }
327 let anchor = Anchor::from_certificate(aa_ca);
328 let key = key.unwrap_or_else(|| WorkingKey::standing(anchor.key()));
329 let Some(anchor) = EffectiveAnchor::bind(anchor, key, context.execution)
330 .map_err(|e| PathError::Processing(Error::pkix(e)))?
331 else {
332 return Ok(PathCheck::AnchorKey);
333 };
334 let controls = match AaPath::read(certificates, context.execution)? {
335 Check::Accepted(value) => value,
336 Check::Rejected(error) => return Ok(PathCheck::Controls(error)),
337 };
338 let recognizer = Recognized {
339 inner: context.recognizer,
340 source: chain[0].span(),
341 };
342 let verifier = ScopedVerifier(context.verifier);
345 let scoped = path::ValidationContext {
346 execution: context.execution,
347 interval: context.interval,
348 policy: context.policy,
349 bounds: context.bounds,
350 verifier: &verifier,
351 watch: context.watch,
352 recognizer: &recognizer,
353 };
354 let facts = path::validate_effective(chain, anchor, &scoped).map_err(|error| match error {
355 path::BuildError::Processing(e) => PathError::Processing(Error::pkix(e)),
356 path::BuildError::Verification(VerificationError::Callback(e)) => {
357 PathError::Verification(e)
358 }
359 path::BuildError::Verification(VerificationError::Processing(e)) => {
360 PathError::Processing(e)
361 }
362 path::BuildError::Recognition(RecognitionError::Callback(e)) => PathError::Recognition(e),
363 path::BuildError::Recognition(RecognitionError::Processing(e)) => PathError::Processing(e),
364 })?;
365 Ok(match facts {
366 path::Validation::Accepted(facts) => PathCheck::Accepted(ValidatedPath {
367 controls,
368 facts,
369 anchor,
370 interval: context.interval,
371 }),
372 path::Validation::Rejected(error) => PathCheck::Path(error),
373 path::Validation::Incomplete(reason) => PathCheck::Incomplete(reason),
374 })
375}
376
377#[cfg(test)]
378mod tests {
379 use super::*;
380 use alloc::sync::Arc;
381 use core::cell::Cell;
382 use structured_asn1::memory::{Memory, MemoryAccount};
383
384 struct Spy {
385 calls: [Cell<u32>; 5],
386 fail: bool,
387 }
388 impl Spy {
389 fn observe(&self, role: usize) -> core::result::Result<bool, usize> {
390 self.calls[role].set(self.calls[role].get() + 1);
391 if self.fail { Err(role) } else { Ok(true) }
392 }
393 }
394 impl Recognizer for Spy {
395 type Error = usize;
396 fn recognizes(&self, _: Oid<'_>, _: &Memory) -> core::result::Result<bool, usize> {
397 self.observe(0)
398 }
399 fn recognizes_other_name(
400 &self,
401 _: Oid<'_>,
402 _: &Memory,
403 ) -> core::result::Result<bool, usize> {
404 self.observe(1)
405 }
406 fn recognizes_name_constraint(
407 &self,
408 _: Oid<'_>,
409 _: &Memory,
410 ) -> core::result::Result<bool, usize> {
411 self.observe(2)
412 }
413 fn recognizes_access_method(
414 &self,
415 _: Oid<'_>,
416 _: &Memory,
417 ) -> core::result::Result<bool, usize> {
418 self.observe(3)
419 }
420 fn recognizes_statement(
421 &self,
422 _: Oid<'_>,
423 _: &Memory,
424 ) -> core::result::Result<bool, usize> {
425 self.observe(4)
426 }
427 }
428 #[test]
429 fn control_recognition_preserves_all_other_scheme_roles_and_causes() {
430 let work = Memory::new(Arc::new(MemoryAccount::new(0, 100)));
432 let kind = Oid::from_bytes(&[42]);
433 for fail in [false, true] {
434 let spy = Spy {
435 calls: core::array::from_fn(|_| Cell::new(0)),
436 fail,
437 };
438 let adapter = Recognized {
439 inner: &spy,
440 source: Span {
441 start: 10,
442 length: 10,
443 },
444 };
445 assert!(matches!(
446 adapter.recognizes(controls::IDENTIFIER, &work),
447 Ok(true)
448 ));
449 let outcomes = [
450 adapter.recognizes(kind, &work),
451 adapter.recognizes_other_name(kind, &work),
452 adapter.recognizes_name_constraint(kind, &work),
453 adapter.recognizes_access_method(kind, &work),
454 adapter.recognizes_statement(kind, &work),
455 ];
456 for (role, result) in outcomes.into_iter().enumerate() {
457 if fail {
458 assert!(
459 matches!(result, Err(RecognitionError::Callback(cause)) if cause == role)
460 );
461 } else {
462 assert!(matches!(result, Ok(true)));
463 }
464 assert_eq!(spy.calls[role].get(), 1);
465 }
466 }
467 }
468 #[test]
469 fn control_recognition_refuses_current_work_before_comparison_or_delegate() {
470 let work = Memory::new(Arc::new(MemoryAccount::new(0, 0)));
472 let spy = Spy {
473 calls: core::array::from_fn(|_| Cell::new(0)),
474 fail: false,
475 };
476 let adapter = Recognized {
477 inner: &spy,
478 source: Span {
479 start: 10,
480 length: 10,
481 },
482 };
483 assert!(
484 matches!(adapter.recognizes(controls::IDENTIFIER, &work), Err(RecognitionError::Processing(error)) if error.is_resource() && error.offset() == 10)
485 );
486 assert_eq!(spy.calls[0].get(), 0);
487 }
488}