Skip to main content

structured_ltv/pki/
responder.rs

1//! Original-root association for an actually authenticated delegated responder.
2
3use super::{CheckedPath, OcspTarget, authority};
4use structured_asn1::memory::{MemoryError, Work};
5use structured_pkix::{
6    ocsp::{AuthoritySource, ResponderRevocation},
7    temporal::{LeapTable, Uncertainty},
8};
9
10/// Completed mismatch in a delegated responder's original-root path.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum ResponderFailure {
13    /// The full responder path uses a different original root name/key.
14    Anchor,
15    /// Its target is not the original certificate/key that signed the response.
16    Signer,
17    /// The signature used different inherited key parameters.
18    EffectiveKey,
19    /// The path does not cover the full original status-evaluation reading.
20    Interval,
21}
22
23/// Inability to bind a delegated path, never an OCSP status or trust decision.
24#[derive(Debug)]
25pub enum ResponderError {
26    /// The response was authenticated by its issuer or configured trust rather
27    /// than by direct certificate delegation. Those branches have no delegate
28    /// path to substitute and remain explicit in `OcspTarget::authority`.
29    SourceKind,
30    /// Current operation refused before comparison or temporal processing.
31    Resource(MemoryError),
32    /// Original shared Name comparison failed.
33    Name(structured_pkix::Error),
34    /// Shared UTC facts cannot establish the full original reading.
35    Time(Uncertainty),
36}
37
38impl From<authority::Error> for ResponderError {
39    fn from(value: authority::Error) -> Self {
40        match value {
41            authority::Error::Resource(cause) => Self::Resource(cause),
42            authority::Error::Name(cause) => Self::Name(cause),
43            authority::Error::Time(cause) => Self::Time(cause),
44        }
45    }
46}
47
48impl From<authority::Failure> for ResponderFailure {
49    fn from(value: authority::Failure) -> Self {
50        match value {
51            authority::Failure::Anchor => Self::Anchor,
52            authority::Failure::Signer => Self::Signer,
53            authority::Failure::EffectiveKey => Self::EffectiveKey,
54            authority::Failure::Interval => Self::Interval,
55        }
56    }
57}
58
59/// A delegated response bound to its original target and full responder path.
60/// This preserves the shared noCheck/unresolved fact; it does not establish
61/// responder revocation, status freshness or application trust acceptance.
62pub struct ResponderPath<'receipt, 'view, 'certificate, 'document> {
63    target: &'receipt OcspTarget<'view, 'certificate, 'document>,
64    path: &'receipt CheckedPath<'document>,
65    revocation: ResponderRevocation,
66}
67
68impl<'receipt, 'view, 'certificate, 'document>
69    ResponderPath<'receipt, 'view, 'certificate, 'document>
70{
71    /// Original target, issuing-key and authenticated response association.
72    #[must_use]
73    pub fn target(&self) -> &'receipt OcspTarget<'view, 'certificate, 'document> {
74        self.target
75    }
76
77    /// Full original-root responder path, including ancestor constraints.
78    #[must_use]
79    pub fn path(&self) -> &'receipt CheckedPath<'document> {
80        self.path
81    }
82
83    /// Actual common delegation fact; absence of noCheck is not success.
84    #[must_use]
85    pub fn revocation(&self) -> ResponderRevocation {
86        self.revocation
87    }
88}
89
90/// Completed association or original source/key/time mismatch.
91pub enum ResponderBinding<'receipt, 'view, 'certificate, 'document> {
92    /// Original delegated signer and complete status-reading interval agree.
93    Bound(ResponderPath<'receipt, 'view, 'certificate, 'document>),
94    /// No partial association is returned for a different root/source/time.
95    Rejected(ResponderFailure),
96}
97
98impl<'view, 'certificate, 'document> OcspTarget<'view, 'certificate, 'document> {
99    /// Bind an actual delegated response to a full path under the target's root.
100    /// The shared RFC6960 ยง4.2.2.2 delegation check establishes the direct
101    /// issuing-key relation and OCSP purpose; it does not resume the original
102    /// root's ancestor constraints. This separate binding requires those full
103    /// path facts, the original signer/effective key and complete evaluation
104    /// reading. It reuses the CRL association kernel without signature replay.
105    /// <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2>
106    pub fn bind_responder_path<'receipt, W: Work>(
107        &'receipt self,
108        path: &'receipt CheckedPath<'document>,
109        utc: &LeapTable<'_>,
110        execution: &W,
111    ) -> Result<ResponderBinding<'receipt, 'view, 'certificate, 'document>, ResponderError> {
112        execution.charge_work(1).map_err(ResponderError::Resource)?;
113        let AuthoritySource::Delegate { revocation } = self.authority() else {
114            return Err(ResponderError::SourceKind);
115        };
116        let evidence = self.evidence().evidence();
117        let signed = evidence.source().signed();
118        if let Some(cause) = self.path().path_authority(
119            path,
120            signed.signer(),
121            signed.key(),
122            self.evidence().evaluated_at(),
123            utc,
124            execution,
125        )? {
126            return Ok(ResponderBinding::Rejected(cause.into()));
127        }
128        Ok(ResponderBinding::Bound(ResponderPath {
129            target: self,
130            path,
131            revocation,
132        }))
133    }
134}