Skip to main content

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}