structured_ltv/pki/census.rs
1//! Complete original per-member status inputs, without application acceptance.
2
3use super::{CheckedPath, CrlCoverage, CrlReadingTarget, OcspTarget, ResponderPath};
4use structured_asn1::memory::{MemoryError, Work};
5use structured_pkix::temporal::Reading;
6
7/// An actual status-source association produced by this module's shared
8/// target/authority kernels. Neither variant is a caller-written Good flag.
9pub enum MemberStatus<'view, 'certificate, 'document> {
10 /// Every complete/delta reason contributor has its actual authority bound.
11 Crl(&'view CrlCoverage<'view, 'certificate, 'document>),
12 /// Actual complete/delta facts, including stale/revoked/conflicting results
13 /// which cannot mint qualified absence. No negative fact is discarded.
14 CrlReading(&'view CrlReadingTarget<'view, 'certificate, 'document>),
15 /// Original OCSP target/issuing-key association; its actual status, update
16 /// window, nonce and configured/delegated authority remain explicit.
17 Ocsp(&'view OcspTarget<'view, 'certificate, 'document>),
18 /// The final member is the actual delegated responder with a valid shared
19 /// noCheck fact. This exempts only its revocation lookup, not its path or
20 /// any non-anchor ancestor. An unresolved delegation cannot use this arm.
21 ResponderNoCheck(&'view ResponderPath<'view, 'view, 'certificate, 'document>),
22}
23
24impl<'view, 'document> MemberStatus<'view, '_, 'document> {
25 fn identity(&self) -> (&'view CheckedPath<'document>, usize, Reading) {
26 match self {
27 Self::Crl(value) => (
28 value.path(),
29 value.member(),
30 value.coverage().evaluated_at(),
31 ),
32 Self::CrlReading(value) => {
33 (value.path(), value.member(), value.reading().evaluated_at())
34 }
35 Self::Ocsp(value) => (
36 value.path(),
37 value.member(),
38 value.evidence().evaluated_at(),
39 ),
40 Self::ResponderNoCheck(value) => (
41 value.path(),
42 value.path().members().len() - 1,
43 value.target().evidence().evaluated_at(),
44 ),
45 }
46 }
47}
48
49/// Complete status-input census for one original checked path and evaluation
50/// label. The anchor is deliberately outside this non-anchor member census.
51/// No allocation, signature verification, hash or copied evidence is required.
52/// This does not approve partial reasons, OCSP Unknown/Revoked/stale assertions,
53/// responder revocation, or application trust: original facts remain accessible.
54pub struct PathStatuses<'view, 'certificate, 'document> {
55 path: &'view CheckedPath<'document>,
56 at: Reading,
57 entries: &'view [MemberStatus<'view, 'certificate, 'document>],
58}
59
60impl<'view, 'certificate, 'document> PathStatuses<'view, 'certificate, 'document> {
61 /// Same original static path, not an independently counted certificate set.
62 #[must_use]
63 pub fn path(&self) -> &'view CheckedPath<'document> {
64 self.path
65 }
66 /// Exact shared evaluation label, including its precision/remainder.
67 #[must_use]
68 pub fn evaluated_at(&self) -> Reading {
69 self.at
70 }
71 /// Every non-anchor member's actual input, in issuer-to-subject order.
72 #[must_use]
73 pub fn entries(&self) -> &'view [MemberStatus<'view, 'certificate, 'document>] {
74 self.entries
75 }
76}
77
78/// Incomplete/foreign input is not a certificate status or successful subset.
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum StatusCensusError {
81 /// Exactly one input is required for each actual non-anchor member.
82 Count,
83 /// This input belongs to a different checked path receipt or member.
84 Member {
85 /// Expected issuer-to-subject member ordinal.
86 index: usize,
87 },
88 /// Input was evaluated at a different exact label.
89 Time {
90 /// Original member ordinal.
91 index: usize,
92 },
93 /// The original delegation has no noCheck exemption. It cannot be used
94 /// instead of an actual status input for this member.
95 Exemption {
96 /// Original non-anchor member ordinal.
97 index: usize,
98 },
99 /// Current work refused before a metadata access/comparison.
100 Resource(MemoryError),
101}
102
103impl<'document> CheckedPath<'document> {
104 /// Establish complete original status-input association for the actual
105 /// path, before deriving conditional validation-material obligations.
106 /// EN319132-1 V1.3.1 ยง6.3(aa) refers to the validation data actually needed
107 /// for the signer, counters, credentials and earlier timestamps, rather
108 /// than accepting a nonempty bag of statuses as the complete path set.
109 /// Status/freshness acceptance and historical trust remain separate.
110 /// <https://www.etsi.org/deliver/etsi_en/319100_319199/31913201/01.03.01_60/en_31913201v010301p.pdf>
111 pub fn status_census<'view, 'certificate, W: Work>(
112 &'view self,
113 at: Reading,
114 entries: &'view [MemberStatus<'view, 'certificate, 'document>],
115 execution: &W,
116 ) -> Result<PathStatuses<'view, 'certificate, 'document>, StatusCensusError> {
117 execution
118 .charge_work(1)
119 .map_err(StatusCensusError::Resource)?;
120 if self.members().len() != entries.len() {
121 return Err(StatusCensusError::Count);
122 }
123 for (index, entry) in entries.iter().enumerate() {
124 execution
125 .charge_work(1)
126 .map_err(StatusCensusError::Resource)?;
127 if let MemberStatus::ResponderNoCheck(value) = entry
128 && value.revocation() != structured_pkix::ocsp::ResponderRevocation::NoCheck
129 {
130 return Err(StatusCensusError::Exemption { index });
131 }
132 let (path, member, evaluated_at) = entry.identity();
133 if !core::ptr::eq(self, path) || member != index {
134 return Err(StatusCensusError::Member { index });
135 }
136 if evaluated_at != at {
137 return Err(StatusCensusError::Time { index });
138 }
139 }
140 Ok(PathStatuses {
141 path: self,
142 at,
143 entries,
144 })
145 }
146}