Skip to main content

structured_pkix/ocsp/
authority.rs

1//! Explicit issuer scope, separate from request/time/revocation acceptance.
2
3use super::{DelegateFacts, Lookup, ResponderRevocation, SignedResponse};
4use crate::Certificate;
5use crate::algorithm::digest::AsnDigest;
6use crate::error::{CallbackResult, Error, Result};
7use crate::path::WorkingKey;
8use structured_asn1::memory::{MemoryError, Work};
9
10/// How the caller-bound signer is related to the selected target issuer.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum AuthoritySource {
13    /// The actual target issuing key itself signed the response.
14    Issuer,
15    /// Common local path and explicit purpose established direct delegation.
16    Delegate {
17        /// The delegate's revocation exemption or unresolved checking fact.
18        revocation: ResponderRevocation,
19    },
20    /// Caller explicitly selected a locally configured trusted responder for
21    /// this issuer. This branch is never selected from response content.
22    Configured,
23}
24
25/// Completed source/key-scope rejection, distinct from resource errors.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum AuthorityFailure {
28    /// Effective issuing key is not bound to the supplied issuer certificate.
29    IssuerKeyBinding,
30    /// Signed response's actual signer does not match the selected authority.
31    Signer,
32    /// Signature was checked with a different effective parameter state.
33    EffectiveKey,
34    /// Present keyUsage allows neither applicable object-signing use.
35    KeyUsage,
36}
37
38/// Signature and issuer-scope facts. Caller trust provenance, intended recipient,
39/// pending delegate revocation, nonce, time and critical demands remain explicit.
40#[derive(Debug, Clone, Copy)]
41pub struct ScopedResponse<'r, 'a> {
42    signed: SignedResponse<'r, 'a>,
43    issuer: &'r Certificate<'a>,
44    // Keep the actual input SPKI provenance as well as inherited parameters.
45    // Rebuilding from an equal issuer certificate could change parser/source
46    // identity and its lazy-reading ceilings, even if its DER bytes agree.
47    issuing_key: Option<WorkingKey<'a>>,
48    source: AuthoritySource,
49}
50
51impl<'r, 'a> ScopedResponse<'r, 'a> {
52    /// Original authenticated response/signing source.
53    #[must_use]
54    pub fn signed(self) -> SignedResponse<'r, 'a> {
55        self.signed
56    }
57    /// Selected target issuer, never inferred from included certificates.
58    #[must_use]
59    pub fn issuer(self) -> &'r Certificate<'a> {
60        self.issuer
61    }
62    /// Actual effective issuer key used by issuer-signature or delegation
63    /// authentication. Explicit configured-responder trust does not run that
64    /// operation and honestly returns None, never a fabricated standing key.
65    #[must_use]
66    pub fn issuing_key(self) -> Option<WorkingKey<'a>> {
67        self.issuing_key
68    }
69    /// Explicit scope source, including unresolved delegated revocation.
70    #[must_use]
71    pub fn source(self) -> AuthoritySource {
72        self.source
73    }
74    /// Select a target assertion using the same issuer as the signer binding.
75    pub fn find<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
76        self,
77        target: &Certificate<'_>,
78        digest: &'d D,
79        execution: &E,
80    ) -> CallbackResult<Lookup<'r, 'a>, D::Error> {
81        self.signed.find(target, self.issuer, digest, execution)
82    }
83}
84
85/// Completed issuer-scope binding outcome.
86#[derive(Debug, Clone, Copy)]
87#[expect(
88    clippy::large_enum_variant,
89    reason = "return the original response and actual issuing key in-place without a new allocation during authentication"
90)]
91pub enum AuthorityBinding<'r, 'a> {
92    /// Scope established; this is not the final status-acceptance decision.
93    Bound(ScopedResponse<'r, 'a>),
94    /// Completed mismatch.
95    Rejected(AuthorityFailure),
96}
97
98fn equal_bytes<E: Work>(left: &[u8], right: &[u8], execution: &E, at: u64) -> Result<bool> {
99    let work = u64::try_from(left.len().max(right.len()))
100        .ok()
101        .and_then(|n| n.checked_mul(2))
102        .and_then(|n| n.checked_add(1))
103        .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.authority", at))?;
104    execution
105        .charge_work(work)
106        .map_err(|reason| Error::resource(reason, "ocspResponse.authority", at))?;
107    Ok(left == right)
108}
109
110fn equal_key<E: Work>(
111    left: WorkingKey<'_>,
112    right: WorkingKey<'_>,
113    execution: &E,
114    at: u64,
115) -> Result<bool> {
116    if !equal_bytes(left.key().as_bytes(), right.key().as_bytes(), execution, at)? {
117        return Ok(false);
118    }
119    match (left.parameters(), right.parameters()) {
120        (None, None) => Ok(true),
121        (Some(left), Some(right)) => equal_bytes(left, right, execution, at),
122        _ => Ok(false),
123    }
124}
125
126pub(super) fn signing_usage<E: Work>(certificate: &Certificate<'_>, execution: &E) -> Result<bool> {
127    // RFC5280 §§4.2.1.3/4.2.1.12: OCSP is neither a certificate nor a CRL
128    // signature; digitalSignature and/or contentCommitment are applicable.
129    // <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>
130    Ok(certificate
131        .tbs()
132        .extensions()
133        .key_usage(execution)?
134        .is_none_or(|usage| usage.digital_signature() || usage.content_commitment()))
135}
136
137impl<'r, 'a> SignedResponse<'r, 'a> {
138    /// Bind issuer-signed response to the actual authenticated target issuing
139    /// key, including inherited parameters. Caller supplies issuer trust and
140    /// current validity from its target path; a matching DN alone is inadequate.
141    /// RFC6960 §4.2.2.2: <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2>
142    pub fn bind_issuer<E: Work>(
143        self,
144        issuer: &'r Certificate<'a>,
145        issuing_key: WorkingKey<'a>,
146        execution: &E,
147    ) -> Result<AuthorityBinding<'r, 'a>> {
148        let at = self.response().span().start;
149        if !equal_bytes(
150            issuer.tbs().public_key().as_bytes(),
151            issuing_key.key().as_bytes(),
152            execution,
153            at,
154        )? {
155            return Ok(AuthorityBinding::Rejected(
156                AuthorityFailure::IssuerKeyBinding,
157            ));
158        }
159        if !equal_bytes(
160            self.signer().tbs().public_key().as_bytes(),
161            issuer.tbs().public_key().as_bytes(),
162            execution,
163            at,
164        )? || !self
165            .signer()
166            .tbs()
167            .subject()
168            .matches(issuer.tbs().subject(), execution)?
169        {
170            return Ok(AuthorityBinding::Rejected(AuthorityFailure::Signer));
171        }
172        if !equal_key(self.key(), issuing_key, execution, at)? {
173            return Ok(AuthorityBinding::Rejected(AuthorityFailure::EffectiveKey));
174        }
175        if !signing_usage(issuer, execution)? {
176            return Ok(AuthorityBinding::Rejected(AuthorityFailure::KeyUsage));
177        }
178        Ok(AuthorityBinding::Bound(ScopedResponse {
179            signed: self,
180            issuer,
181            issuing_key: Some(issuing_key),
182            source: AuthoritySource::Issuer,
183        }))
184    }
185
186    /// Attach completed direct local delegation to this exact actual signer.
187    /// An unresolved revocation fact remains unresolved in the returned source.
188    pub fn bind_delegate<E: Work>(
189        self,
190        facts: DelegateFacts<'a>,
191        execution: &E,
192    ) -> Result<AuthorityBinding<'r, 'a>>
193    where
194        'a: 'r,
195    {
196        let at = self.response().span().start;
197        if !equal_bytes(
198            self.signer().as_bytes(),
199            facts.responder().as_bytes(),
200            execution,
201            at,
202        )? {
203            return Ok(AuthorityBinding::Rejected(AuthorityFailure::Signer));
204        }
205        if !equal_key(self.key(), facts.key(), execution, at)? {
206            return Ok(AuthorityBinding::Rejected(AuthorityFailure::EffectiveKey));
207        }
208        Ok(AuthorityBinding::Bound(ScopedResponse {
209            signed: self,
210            issuer: facts.issuer(),
211            issuing_key: Some(facts.issuing_key()),
212            source: AuthoritySource::Delegate {
213                revocation: facts.revocation(),
214            },
215        }))
216    }
217
218    /// Explicit caller assertion of locally configured responder trust for this
219    /// target issuer, the separate RFC6960 §4.2.2.2 branch. Never call this merely
220    /// because a certificate was included or carried an OCSP EKU. No traversal,
221    /// allocation, trust discovery or policy selection occurs in this operation.
222    #[must_use]
223    pub fn trust_for_issuer(self, issuer: &'r Certificate<'a>) -> ScopedResponse<'r, 'a> {
224        ScopedResponse {
225            signed: self,
226            issuer,
227            issuing_key: None,
228            source: AuthoritySource::Configured,
229        }
230    }
231}