Skip to main content

structured_pkix/ocsp/
authority.rs

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