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}
56
57impl<'r, 'a> Evidence<'r, 'a> {
58 #[must_use]
60 pub fn source(self) -> ScopedResponse<'r, 'a> {
61 self.source
62 }
63 #[must_use]
65 pub fn assertion(self) -> &'r SingleResponse<'a> {
66 self.assertion
67 }
68 #[must_use]
70 pub fn window(self) -> UpdateWindow {
71 self.window
72 }
73 #[must_use]
75 pub fn nonce(self) -> NonceBinding {
76 self.nonce
77 }
78 #[must_use]
80 pub fn recipient(self) -> RecipientBinding {
81 self.recipient
82 }
83
84 #[must_use]
86 pub fn revocation_time(self) -> Option<crate::time::Relation> {
87 self.revocation_time
88 }
89}
90
91#[derive(Debug, Clone, Copy)]
93#[expect(
94 clippy::large_enum_variant,
95 reason = "retain source/key/time facts by value rather than allocate a box per target merely to discriminate the result"
96)]
97pub enum TargetEvidence<'r, 'a> {
98 Evidence(Evidence<'r, 'a>),
100 UnexpectedSigner,
102 Lookup(Lookup<'r, 'a>),
104 UnsupportedCritical {
106 kind: Oid<'a>,
108 span: Span,
110 },
111}
112
113#[derive(Debug)]
117pub struct TargetReading<'target, 'request, 'response, 'source> {
118 target: &'target Certificate<'request>,
119 evaluated_at: Reading,
120 outcome: TargetEvidence<'response, 'source>,
121}
122
123impl<'target, 'request, 'response, 'source> TargetReading<'target, 'request, 'response, 'source> {
124 #[must_use]
126 pub fn target(&self) -> &'target Certificate<'request> {
127 self.target
128 }
129 #[must_use]
131 pub fn evaluated_at(&self) -> Reading {
132 self.evaluated_at
133 }
134 #[must_use]
136 pub fn outcome(&self) -> &TargetEvidence<'response, 'source> {
137 &self.outcome
138 }
139 #[must_use]
143 pub fn evidence(&self) -> Option<BoundEvidence<'target, 'request, 'response, 'source>> {
144 match self.outcome {
145 TargetEvidence::Evidence(evidence) => Some(BoundEvidence {
146 target: self.target,
147 evaluated_at: self.evaluated_at,
148 evidence,
149 }),
150 _ => None,
151 }
152 }
153 #[must_use]
155 pub fn into_outcome(self) -> TargetEvidence<'response, 'source> {
156 self.outcome
157 }
158}
159
160#[derive(Debug, Clone, Copy)]
163pub struct BoundEvidence<'target, 'request, 'response, 'source> {
164 target: &'target Certificate<'request>,
165 evaluated_at: Reading,
166 evidence: Evidence<'response, 'source>,
167}
168
169impl<'target, 'request, 'response, 'source> BoundEvidence<'target, 'request, 'response, 'source> {
170 #[must_use]
172 pub fn target(self) -> &'target Certificate<'request> {
173 self.target
174 }
175 #[must_use]
177 pub fn evaluated_at(self) -> Reading {
178 self.evaluated_at
179 }
180 #[must_use]
182 pub fn evidence(self) -> Evidence<'response, 'source> {
183 self.evidence
184 }
185}
186
187fn exact_signer<E: Work>(
188 left: &Certificate<'_>,
189 right: &Certificate<'_>,
190 execution: &E,
191 at: u64,
192) -> Result<bool> {
193 let left = left.as_bytes();
194 let right = right.as_bytes();
195 let work = u64::try_from(left.len().max(right.len()))
196 .ok()
197 .and_then(|n| n.checked_mul(2))
198 .and_then(|n| n.checked_add(1))
199 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, "ocspResponse.recipient", at))?;
200 execution
201 .charge_work(work)
202 .map_err(|reason| Error::resource(reason, "ocspResponse.recipient", at))?;
203 Ok(left == right)
204}
205
206impl<'r, 'a> ScopedResponse<'r, 'a> {
207 pub fn evaluate_with_target<'target, 'request, 'd, D: AsnDigest<'d> + ?Sized, E: Work>(
212 self,
213 request: &Request<'target, 'request>,
214 time: &Evaluation<'_, E>,
215 digest: &'d D,
216 ) -> CallbackResult<TargetReading<'target, 'request, 'r, 'a>, D::Error> {
217 let outcome = self.evaluate(request, time, digest)?;
218 Ok(TargetReading {
219 target: request.target,
220 evaluated_at: time.now,
221 outcome,
222 })
223 }
224
225 pub fn evaluate<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
230 self,
231 request: &Request<'_, '_>,
232 time: &Evaluation<'_, E>,
233 digest: &'d D,
234 ) -> CallbackResult<TargetEvidence<'r, 'a>, D::Error> {
235 let execution = time.execution;
236 let response = self.signed().response();
237 let recipient = match request.intended_signer {
238 None => RecipientBinding::NotSpecified,
239 Some(expected) => {
240 if !exact_signer(
241 self.signed().signer(),
242 expected,
243 execution,
244 response.span().start,
245 )? {
246 return Ok(TargetEvidence::UnexpectedSigner);
247 }
248 RecipientBinding::Matches
249 }
250 };
251 let processed = response.process_extensions(execution)?;
252 if let ExtensionProcessing::Unsupported { kind, span } = processed.processing {
253 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
254 }
255 let assertion = match self.find(request.target, digest, execution)? {
256 Lookup::Found(assertion) => assertion,
257 outcome => return Ok(TargetEvidence::Lookup(outcome)),
258 };
259 if let ExtensionProcessing::Unsupported { kind, span } =
260 assertion.check_extensions(execution)?
261 {
262 return Ok(TargetEvidence::UnsupportedCritical { kind, span });
263 }
264 let nonce = super::nonce::bind(
265 request.nonce,
266 processed.nonce,
267 execution,
268 response.span().start,
269 )?;
270 let window = assertion.update_window(time.now, time.utc, execution)?;
271 let revocation_time = assertion.revocation_relation(time.now, time.utc, execution)?;
272 Ok(TargetEvidence::Evidence(Evidence {
273 source: self,
274 assertion,
275 nonce,
276 window,
277 recipient,
278 revocation_time,
279 }))
280 }
281}
282
283#[cfg(test)]
284mod tests;