1use super::{
4 Digest, ExtensionProcessing, Lookup, NonceBinding, RequestNonce, ScopedResponse,
5 SingleResponse, UpdateWindow,
6};
7use crate::error::{CallbackResult, Result};
8use crate::temporal::{LeapTable, Reading};
9use crate::{Certificate, Error};
10use structured_asn1::memory::{Memory, MemoryError, Work};
11use structured_asn1::{Oid, Span};
12
13pub struct Request<'p, 'a> {
15 pub target: &'p Certificate<'a>,
17 pub nonce: Option<RequestNonce<'p>>,
19 pub intended_signer: Option<&'p Certificate<'a>>,
22}
23
24pub struct Evaluation<'p, E: Work = Memory> {
26 pub now: Reading,
28 pub utc: &'p LeapTable<'p>,
30 pub execution: &'p E,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum RecipientBinding {
37 NotSpecified,
39 Matches,
41}
42
43#[derive(Debug, Clone, Copy)]
47pub struct Evidence<'r, 'a> {
48 source: ScopedResponse<'r, 'a>,
49 assertion: &'r SingleResponse<'a>,
50 window: UpdateWindow,
51 nonce: NonceBinding,
52 recipient: RecipientBinding,
53 revocation_time: Option<crate::time::Relation>,
54}
55
56impl<'r, 'a> Evidence<'r, 'a> {
57 #[must_use]
59 pub fn source(self) -> ScopedResponse<'r, 'a> {
60 self.source
61 }
62 #[must_use]
64 pub fn assertion(self) -> &'r SingleResponse<'a> {
65 self.assertion
66 }
67 #[must_use]
69 pub fn window(self) -> UpdateWindow {
70 self.window
71 }
72 #[must_use]
74 pub fn nonce(self) -> NonceBinding {
75 self.nonce
76 }
77 #[must_use]
79 pub fn recipient(self) -> RecipientBinding {
80 self.recipient
81 }
82
83 #[must_use]
85 pub fn revocation_time(self) -> Option<crate::time::Relation> {
86 self.revocation_time
87 }
88}
89
90#[derive(Debug, Clone, Copy)]
92#[expect(
93 clippy::large_enum_variant,
94 reason = "retain source/key/time facts by value rather than allocate a box per target merely to discriminate the result"
95)]
96pub enum TargetEvidence<'r, 'a> {
97 Evidence(Evidence<'r, 'a>),
99 UnexpectedSigner,
101 Lookup(Lookup<'r, 'a>),
103 UnsupportedCritical {
105 kind: Oid<'a>,
107 span: Span,
109 },
110}
111
112#[derive(Debug)]
116pub struct TargetReading<'target, 'request, 'response, 'source> {
117 target: &'target Certificate<'request>,
118 evaluated_at: Reading,
119 outcome: TargetEvidence<'response, 'source>,
120}
121
122impl<'target, 'request, 'response, 'source> TargetReading<'target, 'request, 'response, 'source> {
123 #[must_use]
125 pub fn target(&self) -> &'target Certificate<'request> {
126 self.target
127 }
128 #[must_use]
130 pub fn evaluated_at(&self) -> Reading {
131 self.evaluated_at
132 }
133 #[must_use]
135 pub fn outcome(&self) -> &TargetEvidence<'response, 'source> {
136 &self.outcome
137 }
138 #[must_use]
142 pub fn evidence(&self) -> Option<BoundEvidence<'target, 'request, 'response, 'source>> {
143 match self.outcome {
144 TargetEvidence::Evidence(evidence) => Some(BoundEvidence {
145 target: self.target,
146 evaluated_at: self.evaluated_at,
147 evidence,
148 }),
149 _ => None,
150 }
151 }
152 #[must_use]
154 pub fn into_outcome(self) -> TargetEvidence<'response, 'source> {
155 self.outcome
156 }
157}
158
159#[derive(Debug, Clone, Copy)]
162pub struct BoundEvidence<'target, 'request, 'response, 'source> {
163 target: &'target Certificate<'request>,
164 evaluated_at: Reading,
165 evidence: Evidence<'response, 'source>,
166}
167
168impl<'target, 'request, 'response, 'source> BoundEvidence<'target, 'request, 'response, 'source> {
169 #[must_use]
171 pub fn target(self) -> &'target Certificate<'request> {
172 self.target
173 }
174 #[must_use]
176 pub fn evaluated_at(self) -> Reading {
177 self.evaluated_at
178 }
179 #[must_use]
181 pub fn evidence(self) -> Evidence<'response, 'source> {
182 self.evidence
183 }
184}
185
186fn exact_signer<E: Work>(
187 left: &Certificate<'_>,
188 right: &Certificate<'_>,
189 execution: &E,
190 at: u64,
191) -> Result<bool> {
192 let left = left.as_bytes();
193 let right = right.as_bytes();
194 let work = u64::try_from(left.len().max(right.len()))
195 .ok()
196 .and_then(|n| n.checked_mul(2))
197 .and_then(|n| n.checked_add(1))
198 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.recipient", at))?;
199 execution
200 .charge_work(work)
201 .map_err(|reason| Error::resource(reason, "ocspResponse.recipient", at))?;
202 Ok(left == right)
203}
204
205impl<'r, 'a> ScopedResponse<'r, 'a> {
206 pub fn evaluate_with_target<'target, 'request, D: Digest<E>, E: Work>(
211 self,
212 request: &Request<'target, 'request>,
213 time: &Evaluation<'_, E>,
214 digest: &D,
215 ) -> CallbackResult<TargetReading<'target, 'request, 'r, 'a>, D::Error> {
216 let outcome = self.evaluate(request, time, digest)?;
217 Ok(TargetReading {
218 target: request.target,
219 evaluated_at: time.now,
220 outcome,
221 })
222 }
223
224 pub fn evaluate<D: Digest<E>, E: Work>(
229 self,
230 request: &Request<'_, '_>,
231 time: &Evaluation<'_, E>,
232 digest: &D,
233 ) -> CallbackResult<TargetEvidence<'r, 'a>, D::Error> {
234 let execution = time.execution;
235 let response = self.signed().response();
236 let recipient = match request.intended_signer {
237 None => RecipientBinding::NotSpecified,
238 Some(expected) => {
239 if !exact_signer(
240 self.signed().signer(),
241 expected,
242 execution,
243 response.span().start,
244 )? {
245 return Ok(TargetEvidence::UnexpectedSigner);
246 }
247 RecipientBinding::Matches
248 }
249 };
250 let processed = response.process_extensions(execution)?;
251 if let ExtensionProcessing::Unsupported { kind, span } = processed.processing {
252 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
253 }
254 let assertion = match self.find(request.target, digest, execution)? {
255 Lookup::Found(assertion) => assertion,
256 outcome => return Ok(TargetEvidence::Lookup(outcome)),
257 };
258 if let ExtensionProcessing::Unsupported { kind, span } =
259 assertion.check_extensions(execution)?
260 {
261 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
262 }
263 let nonce = super::nonce::bind(
264 request.nonce,
265 processed.nonce,
266 execution,
267 response.span().start,
268 )?;
269 let window = assertion.update_window(time.now, time.utc, execution)?;
270 let revocation_time = assertion.revocation_relation(time.now, time.utc, execution)?;
271 Ok(TargetEvidence::Evidence(Evidence {
272 source: self,
273 assertion,
274 nonce,
275 window,
276 recipient,
277 revocation_time,
278 }))
279 }
280}
281
282#[cfg(test)]
283mod tests;