1use super::{
4 ExtensionProcessing, Lookup, NonceBinding, RequestNonce, ScopedResponse, SingleResponse,
5 UpdateWindow,
6};
7use crate::algorithm::digest::AsnDigest;
8use crate::error::{CallbackResult, Result};
9use crate::revocation::{RevocationScheme, RevocationTarget, TargetKind};
10use crate::temporal::{LeapTable, Reading};
11use crate::{Certificate, Error};
12use core::marker::PhantomData;
13use structured_asn1::memory::{Memory, MemoryError, Work};
14use structured_asn1::{Oid, Span};
15
16pub struct Request<'p, 'a, T = Certificate<'a>> {
18 pub target: &'p T,
20 pub nonce: Option<RequestNonce<'p>>,
22 pub intended_signer: Option<&'p Certificate<'a>>,
25}
26
27pub struct Evaluation<'p, E: Work = Memory> {
29 pub now: Reading,
31 pub utc: &'p LeapTable<'p>,
33 pub execution: &'p E,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum RecipientBinding {
40 NotSpecified,
42 Matches,
44}
45
46#[derive(Debug, Clone, Copy)]
50pub struct Evidence<'r, 'a> {
51 source: ScopedResponse<'r, 'a>,
52 assertion: &'r SingleResponse<'a>,
53 window: UpdateWindow,
54 nonce: NonceBinding,
55 recipient: RecipientBinding,
56 revocation_time: Option<crate::time::Relation>,
57 status: super::Status,
58 archive_cutoff: Option<Reading>,
59}
60
61impl<'r, 'a> Evidence<'r, 'a> {
62 #[must_use]
64 pub fn source(self) -> ScopedResponse<'r, 'a> {
65 self.source
66 }
67 #[must_use]
69 pub fn assertion(self) -> &'r SingleResponse<'a> {
70 self.assertion
71 }
72 #[must_use]
74 pub fn window(self) -> UpdateWindow {
75 self.window
76 }
77 #[must_use]
79 pub fn nonce(self) -> NonceBinding {
80 self.nonce
81 }
82 #[must_use]
84 pub fn recipient(self) -> RecipientBinding {
85 self.recipient
86 }
87
88 #[must_use]
90 pub fn revocation_time(self) -> Option<crate::time::Relation> {
91 self.revocation_time
92 }
93
94 #[must_use]
97 pub fn status(self) -> super::Status {
98 self.status
99 }
100
101 #[must_use]
103 pub fn archive_cutoff(self) -> Option<Reading> {
104 self.archive_cutoff
105 }
106}
107
108#[derive(Debug, Clone, Copy)]
110#[expect(
111 clippy::large_enum_variant,
112 reason = "retain source/key/time facts by value rather than allocate a box per target merely to discriminate the result"
113)]
114pub enum TargetEvidence<'r, 'a> {
115 NoRevAvail,
117 AttributeDelegation,
121 Evidence(Evidence<'r, 'a>),
123 UnexpectedSigner,
125 Lookup(Lookup<'r, 'a>),
127 UnsupportedCritical {
129 kind: Oid<'a>,
131 span: Span,
133 },
134}
135
136#[derive(Debug)]
140pub struct TargetReading<'target, 'request, 'response, 'source, T = Certificate<'request>> {
141 target: &'target T,
142 request_source: PhantomData<&'request ()>,
145 evaluated_at: Reading,
146 outcome: TargetEvidence<'response, 'source>,
147}
148
149impl<'target, 'request, 'response, 'source, T>
150 TargetReading<'target, 'request, 'response, 'source, T>
151{
152 #[must_use]
154 pub fn target(&self) -> &'target T {
155 self.target
156 }
157 #[must_use]
159 pub fn evaluated_at(&self) -> Reading {
160 self.evaluated_at
161 }
162 #[must_use]
164 pub fn outcome(&self) -> &TargetEvidence<'response, 'source> {
165 &self.outcome
166 }
167 #[must_use]
171 pub fn evidence(&self) -> Option<BoundEvidence<'target, 'request, 'response, 'source, T>> {
172 match self.outcome {
173 TargetEvidence::Evidence(evidence) => Some(BoundEvidence {
174 target: self.target,
175 request_source: PhantomData,
176 evaluated_at: self.evaluated_at,
177 evidence,
178 }),
179 _ => None,
180 }
181 }
182 #[must_use]
184 pub fn into_outcome(self) -> TargetEvidence<'response, 'source> {
185 self.outcome
186 }
187}
188
189#[derive(Debug)]
192pub struct BoundEvidence<'target, 'request, 'response, 'source, T = Certificate<'request>> {
193 target: &'target T,
194 request_source: PhantomData<&'request ()>,
195 evaluated_at: Reading,
196 evidence: Evidence<'response, 'source>,
197}
198
199impl<T> Copy for BoundEvidence<'_, '_, '_, '_, T> {}
200impl<T> Clone for BoundEvidence<'_, '_, '_, '_, T> {
201 fn clone(&self) -> Self {
202 *self
203 }
204}
205
206impl<'target, 'response, 'source, T> BoundEvidence<'target, '_, 'response, 'source, T> {
207 #[must_use]
209 pub fn target(self) -> &'target T {
210 self.target
211 }
212 #[must_use]
214 pub fn evaluated_at(self) -> Reading {
215 self.evaluated_at
216 }
217 #[must_use]
219 pub fn evidence(self) -> Evidence<'response, 'source> {
220 self.evidence
221 }
222}
223
224fn exact_signer<E: Work>(
225 left: &Certificate<'_>,
226 right: &Certificate<'_>,
227 execution: &E,
228 at: u64,
229) -> Result<bool> {
230 let left = left.as_bytes();
231 let right = right.as_bytes();
232 let work = u64::try_from(left.len().max(right.len()))
233 .ok()
234 .and_then(|n| n.checked_mul(2))
235 .and_then(|n| n.checked_add(1))
236 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.recipient", at))?;
237 execution
238 .charge_work(work)
239 .map_err(|reason| Error::resource(reason, "ocspResponse.recipient", at))?;
240 Ok(left == right)
241}
242
243impl<'r, 'a> ScopedResponse<'r, 'a> {
244 pub fn evaluate_with_target<
249 'target,
250 'request,
251 't,
252 'd,
253 T: RevocationTarget<'t>,
254 D: AsnDigest<'d> + ?Sized,
255 E: Work,
256 >(
257 self,
258 request: &Request<'target, 'request, T>,
259 time: &Evaluation<'_, E>,
260 digest: &'d D,
261 ) -> CallbackResult<TargetReading<'target, 'request, 'r, 'a, T>, D::Error> {
262 let outcome = self.evaluate(request, time, digest)?;
263 Ok(TargetReading {
264 target: request.target,
265 request_source: PhantomData,
266 evaluated_at: time.now,
267 outcome,
268 })
269 }
270
271 pub fn evaluate<'t, 'd, T: RevocationTarget<'t>, D: AsnDigest<'d> + ?Sized, E: Work>(
276 self,
277 request: &Request<'_, '_, T>,
278 time: &Evaluation<'_, E>,
279 digest: &'d D,
280 ) -> CallbackResult<TargetEvidence<'r, 'a>, D::Error> {
281 let response = self.signed().response();
282 let execution = &response.signature_algorithm().query_in(time.execution);
283 execution.charge_work(1).map_err(|reason| {
284 Error::resource(reason, "ocspResponse.target", response.span().start)
285 })?;
286 let target_work = request.target.execution_in(time.execution);
287 target_work.charge_work(1).map_err(|reason| {
288 Error::resource(reason, "ocspResponse.target", request.target.span().start)
289 })?;
290 if request.target.revocation_scheme() == RevocationScheme::NoRevAvail {
291 return Ok(TargetEvidence::NoRevAvail);
292 }
293 if request.target.kind() == TargetKind::Attribute
298 && matches!(self.source(), super::AuthoritySource::Delegate { .. })
299 {
300 return Ok(TargetEvidence::AttributeDelegation);
301 }
302 let recipient = match request.intended_signer {
303 None => RecipientBinding::NotSpecified,
304 Some(expected) => {
305 if !exact_signer(
306 self.signed().signer(),
307 expected,
308 execution,
309 response.span().start,
310 )? {
311 return Ok(TargetEvidence::UnexpectedSigner);
312 }
313 RecipientBinding::Matches
314 }
315 };
316 let processed = response.process_extensions(execution)?;
317 if let ExtensionProcessing::Unsupported { kind, span } = processed.processing {
318 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
319 }
320 let assertion = match self.find(request.target, digest, execution)? {
321 Lookup::Found(assertion) => assertion,
322 outcome => return Ok(TargetEvidence::Lookup(outcome)),
323 };
324 let single = assertion.process_extensions(execution)?;
325 if let ExtensionProcessing::Unsupported { kind, span } = single.processing {
326 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
327 }
328 let status = super::retention::status(assertion, processed.extended_revoke, execution)?;
329 let nonce = super::nonce::bind(
330 request.nonce,
331 processed.nonce,
332 (execution, "ocspResponse.nonce", response.span().start),
333 (
334 &response.signature_algorithm().query_in(execution),
335 "ocspResponse.nonce",
336 response.span().start,
337 ),
338 )?;
339 let window = assertion.update_window(time.now, time.utc, execution)?;
340 let revocation_time = assertion.revocation_relation(time.now, time.utc, execution)?;
341 Ok(TargetEvidence::Evidence(Evidence {
342 source: self,
343 assertion,
344 nonce,
345 window,
346 recipient,
347 revocation_time,
348 status,
349 archive_cutoff: single.archive_cutoff,
350 }))
351 }
352}
353
354#[cfg(test)]
355mod tests;