1use super::{
4 ExtensionProcessing, Lookup, NonceBinding, RequestNonce, ScopedResponse, SingleResponse,
5 UpdateWindow,
6};
7use crate::algorithm::digest::AsnDigest;
8use crate::error::{CallbackResult, Result};
9use crate::temporal::{LeapTable, Reading};
10use crate::{Certificate, Error};
11use structured_asn1::memory::{Memory, MemoryError, Work};
12use structured_asn1::{Oid, Span};
13
14pub struct Request<'p, 'a> {
16 pub target: &'p Certificate<'a>,
18 pub nonce: Option<RequestNonce<'p>>,
20 pub intended_signer: Option<&'p Certificate<'a>>,
23}
24
25pub struct Evaluation<'p, E: Work = Memory> {
27 pub now: Reading,
29 pub utc: &'p LeapTable<'p>,
31 pub execution: &'p E,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum RecipientBinding {
38 NotSpecified,
40 Matches,
42}
43
44#[derive(Debug, Clone, Copy)]
48pub struct Evidence<'r, 'a> {
49 source: ScopedResponse<'r, 'a>,
50 assertion: &'r SingleResponse<'a>,
51 window: UpdateWindow,
52 nonce: NonceBinding,
53 recipient: RecipientBinding,
54 revocation_time: Option<crate::time::Relation>,
55 status: super::Status,
56 archive_cutoff: Option<Reading>,
57}
58
59impl<'r, 'a> Evidence<'r, 'a> {
60 #[must_use]
62 pub fn source(self) -> ScopedResponse<'r, 'a> {
63 self.source
64 }
65 #[must_use]
67 pub fn assertion(self) -> &'r SingleResponse<'a> {
68 self.assertion
69 }
70 #[must_use]
72 pub fn window(self) -> UpdateWindow {
73 self.window
74 }
75 #[must_use]
77 pub fn nonce(self) -> NonceBinding {
78 self.nonce
79 }
80 #[must_use]
82 pub fn recipient(self) -> RecipientBinding {
83 self.recipient
84 }
85
86 #[must_use]
88 pub fn revocation_time(self) -> Option<crate::time::Relation> {
89 self.revocation_time
90 }
91
92 #[must_use]
95 pub fn status(self) -> super::Status {
96 self.status
97 }
98
99 #[must_use]
101 pub fn archive_cutoff(self) -> Option<Reading> {
102 self.archive_cutoff
103 }
104}
105
106#[derive(Debug, Clone, Copy)]
108#[expect(
109 clippy::large_enum_variant,
110 reason = "retain source/key/time facts by value rather than allocate a box per target merely to discriminate the result"
111)]
112pub enum TargetEvidence<'r, 'a> {
113 Evidence(Evidence<'r, 'a>),
115 UnexpectedSigner,
117 Lookup(Lookup<'r, 'a>),
119 UnsupportedCritical {
121 kind: Oid<'a>,
123 span: Span,
125 },
126}
127
128#[derive(Debug)]
132pub struct TargetReading<'target, 'request, 'response, 'source> {
133 target: &'target Certificate<'request>,
134 evaluated_at: Reading,
135 outcome: TargetEvidence<'response, 'source>,
136}
137
138impl<'target, 'request, 'response, 'source> TargetReading<'target, 'request, 'response, 'source> {
139 #[must_use]
141 pub fn target(&self) -> &'target Certificate<'request> {
142 self.target
143 }
144 #[must_use]
146 pub fn evaluated_at(&self) -> Reading {
147 self.evaluated_at
148 }
149 #[must_use]
151 pub fn outcome(&self) -> &TargetEvidence<'response, 'source> {
152 &self.outcome
153 }
154 #[must_use]
158 pub fn evidence(&self) -> Option<BoundEvidence<'target, 'request, 'response, 'source>> {
159 match self.outcome {
160 TargetEvidence::Evidence(evidence) => Some(BoundEvidence {
161 target: self.target,
162 evaluated_at: self.evaluated_at,
163 evidence,
164 }),
165 _ => None,
166 }
167 }
168 #[must_use]
170 pub fn into_outcome(self) -> TargetEvidence<'response, 'source> {
171 self.outcome
172 }
173}
174
175#[derive(Debug, Clone, Copy)]
178pub struct BoundEvidence<'target, 'request, 'response, 'source> {
179 target: &'target Certificate<'request>,
180 evaluated_at: Reading,
181 evidence: Evidence<'response, 'source>,
182}
183
184impl<'target, 'request, 'response, 'source> BoundEvidence<'target, 'request, 'response, 'source> {
185 #[must_use]
187 pub fn target(self) -> &'target Certificate<'request> {
188 self.target
189 }
190 #[must_use]
192 pub fn evaluated_at(self) -> Reading {
193 self.evaluated_at
194 }
195 #[must_use]
197 pub fn evidence(self) -> Evidence<'response, 'source> {
198 self.evidence
199 }
200}
201
202fn exact_signer<E: Work>(
203 left: &Certificate<'_>,
204 right: &Certificate<'_>,
205 execution: &E,
206 at: u64,
207) -> Result<bool> {
208 let left = left.as_bytes();
209 let right = right.as_bytes();
210 let work = u64::try_from(left.len().max(right.len()))
211 .ok()
212 .and_then(|n| n.checked_mul(2))
213 .and_then(|n| n.checked_add(1))
214 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.recipient", at))?;
215 execution
216 .charge_work(work)
217 .map_err(|reason| Error::resource(reason, "ocspResponse.recipient", at))?;
218 Ok(left == right)
219}
220
221impl<'r, 'a> ScopedResponse<'r, 'a> {
222 pub fn evaluate_with_target<'target, 'request, 'd, D: AsnDigest<'d> + ?Sized, E: Work>(
227 self,
228 request: &Request<'target, 'request>,
229 time: &Evaluation<'_, E>,
230 digest: &'d D,
231 ) -> CallbackResult<TargetReading<'target, 'request, 'r, 'a>, D::Error> {
232 let outcome = self.evaluate(request, time, digest)?;
233 Ok(TargetReading {
234 target: request.target,
235 evaluated_at: time.now,
236 outcome,
237 })
238 }
239
240 pub fn evaluate<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
245 self,
246 request: &Request<'_, '_>,
247 time: &Evaluation<'_, E>,
248 digest: &'d D,
249 ) -> CallbackResult<TargetEvidence<'r, 'a>, D::Error> {
250 let execution = time.execution;
251 let response = self.signed().response();
252 let recipient = match request.intended_signer {
253 None => RecipientBinding::NotSpecified,
254 Some(expected) => {
255 if !exact_signer(
256 self.signed().signer(),
257 expected,
258 execution,
259 response.span().start,
260 )? {
261 return Ok(TargetEvidence::UnexpectedSigner);
262 }
263 RecipientBinding::Matches
264 }
265 };
266 let processed = response.process_extensions(execution)?;
267 if let ExtensionProcessing::Unsupported { kind, span } = processed.processing {
268 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
269 }
270 let assertion = match self.find(request.target, digest, execution)? {
271 Lookup::Found(assertion) => assertion,
272 outcome => return Ok(TargetEvidence::Lookup(outcome)),
273 };
274 let single = assertion.process_extensions(execution)?;
275 if let ExtensionProcessing::Unsupported { kind, span } = single.processing {
276 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
277 }
278 let status = super::retention::status(assertion, processed.extended_revoke, execution)?;
279 let nonce = super::nonce::bind(
280 request.nonce,
281 processed.nonce,
282 execution,
283 response.span().start,
284 )?;
285 let window = assertion.update_window(time.now, time.utc, execution)?;
286 let revocation_time = assertion.revocation_relation(time.now, time.utc, execution)?;
287 Ok(TargetEvidence::Evidence(Evidence {
288 source: self,
289 assertion,
290 nonce,
291 window,
292 recipient,
293 revocation_time,
294 status,
295 archive_cutoff: single.archive_cutoff,
296 }))
297 }
298}
299
300#[cfg(test)]
301mod tests;