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::{Memory, MemoryError};
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    source: AuthoritySource,
44}
45
46impl<'r, 'a> ScopedResponse<'r, 'a> {
47    /// Original authenticated response/signing source.
48    #[must_use]
49    pub fn signed(self) -> SignedResponse<'r, 'a> {
50        self.signed
51    }
52    /// Selected target issuer, never inferred from included certificates.
53    #[must_use]
54    pub fn issuer(self) -> &'r Certificate<'a> {
55        self.issuer
56    }
57    /// Explicit scope source, including unresolved delegated revocation.
58    #[must_use]
59    pub fn source(self) -> AuthoritySource {
60        self.source
61    }
62    /// Select a target assertion using the same issuer as the signer binding.
63    pub fn find<D: Digest>(
64        self,
65        target: &Certificate<'_>,
66        digest: &D,
67        execution: &Memory,
68    ) -> CallbackResult<Lookup<'r, 'a>, D::Error> {
69        self.signed.find(target, self.issuer, digest, execution)
70    }
71}
72
73/// Completed issuer-scope binding outcome.
74#[derive(Debug, Clone, Copy)]
75pub enum AuthorityBinding<'r, 'a> {
76    /// Scope established; this is not the final status-acceptance decision.
77    Bound(ScopedResponse<'r, 'a>),
78    /// Completed mismatch.
79    Rejected(AuthorityFailure),
80}
81
82fn equal_bytes(left: &[u8], right: &[u8], execution: &Memory, at: u64) -> Result<bool> {
83    let work = u64::try_from(left.len().max(right.len()))
84        .ok()
85        .and_then(|n| n.checked_mul(2))
86        .and_then(|n| n.checked_add(1))
87        .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.authority", at))?;
88    execution
89        .charge_work(work)
90        .map_err(|reason| Error::resource(reason, "ocspResponse.authority", at))?;
91    Ok(left == right)
92}
93
94fn equal_key(
95    left: WorkingKey<'_>,
96    right: WorkingKey<'_>,
97    execution: &Memory,
98    at: u64,
99) -> Result<bool> {
100    if !equal_bytes(left.key().as_bytes(), right.key().as_bytes(), execution, at)? {
101        return Ok(false);
102    }
103    match (left.parameters(), right.parameters()) {
104        (None, None) => Ok(true),
105        (Some(left), Some(right)) => equal_bytes(left, right, execution, at),
106        _ => Ok(false),
107    }
108}
109
110pub(super) fn signing_usage(certificate: &Certificate<'_>, execution: &Memory) -> Result<bool> {
111    // RFC5280 §§4.2.1.3/4.2.1.12: OCSP is neither a certificate nor a CRL
112    // signature; digitalSignature and/or contentCommitment are applicable.
113    // <https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.12>
114    Ok(certificate
115        .tbs()
116        .extensions()
117        .key_usage(execution)?
118        .is_none_or(|usage| usage.digital_signature() || usage.content_commitment()))
119}
120
121impl<'r, 'a> SignedResponse<'r, 'a> {
122    /// Bind issuer-signed response to the actual authenticated target issuing
123    /// key, including inherited parameters. Caller supplies issuer trust and
124    /// current validity from its target path; a matching DN alone is inadequate.
125    /// RFC6960 §4.2.2.2: <https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2>
126    pub fn bind_issuer(
127        self,
128        issuer: &'r Certificate<'a>,
129        issuing_key: WorkingKey<'a>,
130        execution: &Memory,
131    ) -> Result<AuthorityBinding<'r, 'a>> {
132        let at = self.response().span().start;
133        if !equal_bytes(
134            issuer.tbs().public_key().as_bytes(),
135            issuing_key.key().as_bytes(),
136            execution,
137            at,
138        )? {
139            return Ok(AuthorityBinding::Rejected(
140                AuthorityFailure::IssuerKeyBinding,
141            ));
142        }
143        if !equal_bytes(
144            self.signer().tbs().public_key().as_bytes(),
145            issuer.tbs().public_key().as_bytes(),
146            execution,
147            at,
148        )? || !self
149            .signer()
150            .tbs()
151            .subject()
152            .matches(issuer.tbs().subject(), execution)?
153        {
154            return Ok(AuthorityBinding::Rejected(AuthorityFailure::Signer));
155        }
156        if !equal_key(self.key(), issuing_key, execution, at)? {
157            return Ok(AuthorityBinding::Rejected(AuthorityFailure::EffectiveKey));
158        }
159        if !signing_usage(issuer, execution)? {
160            return Ok(AuthorityBinding::Rejected(AuthorityFailure::KeyUsage));
161        }
162        Ok(AuthorityBinding::Bound(ScopedResponse {
163            signed: self,
164            issuer,
165            source: AuthoritySource::Issuer,
166        }))
167    }
168
169    /// Attach completed direct local delegation to this exact actual signer.
170    /// An unresolved revocation fact remains unresolved in the returned source.
171    pub fn bind_delegate(
172        self,
173        facts: DelegateFacts<'a>,
174        execution: &Memory,
175    ) -> Result<AuthorityBinding<'r, 'a>>
176    where
177        'a: 'r,
178    {
179        let at = self.response().span().start;
180        if !equal_bytes(
181            self.signer().as_bytes(),
182            facts.responder().as_bytes(),
183            execution,
184            at,
185        )? {
186            return Ok(AuthorityBinding::Rejected(AuthorityFailure::Signer));
187        }
188        if !equal_key(self.key(), facts.key(), execution, at)? {
189            return Ok(AuthorityBinding::Rejected(AuthorityFailure::EffectiveKey));
190        }
191        Ok(AuthorityBinding::Bound(ScopedResponse {
192            signed: self,
193            issuer: facts.issuer(),
194            source: AuthoritySource::Delegate {
195                revocation: facts.revocation(),
196            },
197        }))
198    }
199
200    /// Explicit caller assertion of locally configured responder trust for this
201    /// target issuer, the separate RFC6960 §4.2.2.2 branch. Never call this merely
202    /// because a certificate was included or carried an OCSP EKU. No traversal,
203    /// allocation, trust discovery or policy selection occurs in this operation.
204    #[must_use]
205    pub fn trust_for_issuer(self, issuer: &'r Certificate<'a>) -> ScopedResponse<'r, 'a> {
206        ScopedResponse {
207            signed: self,
208            issuer,
209            source: AuthoritySource::Configured,
210        }
211    }
212}