1use super::CertId;
4use super::ResponderId;
5use crate::AlgorithmIdentifier;
6use crate::Certificate;
7use crate::error::{CallbackError, CallbackResult, Error};
8use core::num::NonZeroUsize;
9use structured_asn1::memory::{Memory, MemoryError, Work};
10
11#[derive(Debug, Clone, Copy)]
13pub enum DigestAlgorithm<'a> {
14 Identifier(&'a AlgorithmIdentifier<'a>),
16 Sha1,
18}
19
20#[derive(Debug, Clone, Copy)]
23pub struct ResolvedDigest<H: Copy> {
24 pub handle: H,
26 pub width: NonZeroUsize,
28}
29
30pub trait Digest<E: Work = Memory> {
35 type Handle: Copy;
37 type Error;
39 fn resolve(
41 &self,
42 algorithm: DigestAlgorithm<'_>,
43 execution: &E,
44 ) -> core::result::Result<Option<ResolvedDigest<Self::Handle>>, Self::Error>;
45 fn matches(
48 &self,
49 handle: Self::Handle,
50 input: &[u8],
51 expected: &[u8],
52 execution: &E,
53 ) -> core::result::Result<bool, Self::Error>;
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum CertMatch {
59 Matches,
61 Differs,
63 MalformedHashWidth,
65 UnsupportedHash,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum ResponderMatch {
72 Matches,
74 Differs,
76 MalformedHashWidth,
78 UnsupportedHash,
80}
81
82impl ResponderId<'_> {
83 pub fn matches_certificate<D: Digest<E>, E: Work>(
92 &self,
93 certificate: &Certificate<'_>,
94 digest: &D,
95 execution: &E,
96 field: &'static str,
97 at: u64,
98 ) -> CallbackResult<ResponderMatch, D::Error> {
99 let charge = |work| {
100 execution
101 .charge_work(work)
102 .map_err(|reason| Error::resource(reason, field, at))
103 };
104 charge(1)?;
105 let same = match self {
106 Self::Name(name) => name.matches(certificate.tbs().subject(), execution)?,
107 Self::KeyHash(expected) => {
108 if expected.len() != 20 {
109 return Ok(ResponderMatch::MalformedHashWidth);
110 }
111 let Some(resolved) = digest
112 .resolve(DigestAlgorithm::Sha1, execution)
113 .map_err(CallbackError::Callback)?
114 else {
115 return Ok(ResponderMatch::UnsupportedHash);
116 };
117 if resolved.width.get() != 20 {
118 return Ok(ResponderMatch::MalformedHashWidth);
119 }
120 digest
121 .matches(
122 resolved.handle,
123 certificate.tbs().public_key().key().as_bytes(),
124 expected,
125 execution,
126 )
127 .map_err(CallbackError::Callback)?
128 }
129 };
130 Ok(if same {
131 ResponderMatch::Matches
132 } else {
133 ResponderMatch::Differs
134 })
135 }
136}
137
138impl CertId<'_> {
139 pub fn matches<D: Digest<E>, E: Work>(
145 &self,
146 target: &Certificate<'_>,
147 issuer: &Certificate<'_>,
148 digest: &D,
149 execution: &E,
150 ) -> CallbackResult<CertMatch, D::Error> {
151 const FIELD: &str = "ocspResponse.certID";
152 let at = self.span.start;
153 let left = self.serial.as_bytes();
154 let right = target.tbs().serial().as_bytes();
155 let work = u64::try_from(left.len().max(right.len()))
156 .ok()
157 .and_then(|n| n.checked_mul(2))
158 .and_then(|n| n.checked_add(1))
159 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, at))?;
160 execution
161 .charge_work(work)
162 .map_err(|reason| Error::resource(reason, FIELD, at))?;
163 if left != right {
164 return Ok(CertMatch::Differs);
165 }
166 if !target
167 .tbs()
168 .issuer()
169 .matches(issuer.tbs().subject(), execution)?
170 {
171 return Ok(CertMatch::Differs);
172 }
173 execution
174 .charge_work(1)
175 .map_err(|reason| Error::resource(reason, FIELD, at))?;
176 let Some(resolved) = digest
177 .resolve(DigestAlgorithm::Identifier(&self.algorithm), execution)
178 .map_err(CallbackError::Callback)?
179 else {
180 return Ok(CertMatch::UnsupportedHash);
181 };
182 if self.issuer_name_hash.len() != resolved.width.get()
184 || self.issuer_key_hash.len() != resolved.width.get()
185 {
186 return Ok(CertMatch::MalformedHashWidth);
187 }
188 if !digest
189 .matches(
190 resolved.handle,
191 target.tbs().issuer().as_bytes(),
192 self.issuer_name_hash,
193 execution,
194 )
195 .map_err(CallbackError::Callback)?
196 {
197 return Ok(CertMatch::Differs);
198 }
199 if !digest
200 .matches(
201 resolved.handle,
202 issuer.tbs().public_key().key().as_bytes(),
203 self.issuer_key_hash,
204 execution,
205 )
206 .map_err(CallbackError::Callback)?
207 {
208 return Ok(CertMatch::Differs);
209 }
210 Ok(CertMatch::Matches)
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::{CertMatch, Digest, DigestAlgorithm, ResolvedDigest};
217 use core::cell::Cell;
218 use core::num::NonZeroUsize;
219 use structured_asn1::memory::Memory;
220
221 struct Observed<'a> {
222 original_algorithm: &'a [u8],
223 name: &'a [u8],
224 key: &'a [u8],
225 hashes: Cell<usize>,
226 supported: bool,
227 failure: bool,
228 }
229 impl Digest for Observed<'_> {
230 type Handle = u8;
231 type Error = &'static str;
232 fn resolve(
233 &self,
234 algorithm: DigestAlgorithm<'_>,
235 _: &Memory,
236 ) -> Result<Option<ResolvedDigest<u8>>, Self::Error> {
237 let DigestAlgorithm::Identifier(algorithm) = algorithm else {
238 panic!("CertID does not impose SHA1")
239 };
240 assert_eq!(algorithm.as_bytes(), self.original_algorithm);
241 assert_eq!(
242 algorithm.as_bytes().as_ptr(),
243 self.original_algorithm.as_ptr()
244 );
245 if !self.supported {
246 return Ok(None);
247 }
248 Ok(Some(ResolvedDigest {
249 handle: 1,
250 width: NonZeroUsize::new(20).unwrap(),
251 }))
252 }
253 fn matches(
254 &self,
255 handle: u8,
256 input: &[u8],
257 expected: &[u8],
258 _: &Memory,
259 ) -> Result<bool, Self::Error> {
260 assert_eq!(handle, 1);
261 assert_eq!(expected.len(), 20);
262 if self.failure {
263 return Err("provider failure");
264 }
265 let calls = self.hashes.get();
266 self.hashes.set(calls + 1);
267 let original = if calls == 0 { self.name } else { self.key };
268 assert_eq!(input, original);
269 assert_eq!(input.as_ptr(), original.as_ptr());
270 Ok(true)
271 }
272 }
273
274 #[test]
275 fn cert_id_prechecks_width_and_forwards_original_bytes_without_output_copy() {
276 let parts = crate::fixtures::CertificateParts::default();
279 let issuer_bytes = crate::fixtures::CertificateParts {
280 subject: parts.issuer.clone(),
281 ..crate::fixtures::CertificateParts::default()
282 }
283 .build();
284 let target_bytes = parts.build();
285 let execution = crate::fixtures::memory();
286 let parser = structured_asn1::Parser::der(execution.clone());
287 let target = crate::Certificate::read(&parser, &target_bytes, 100).unwrap();
288 let issuer = crate::Certificate::read(&parser, &issuer_bytes, 300).unwrap();
289 let id = super::CertId {
290 algorithm: target.signature_algorithm(),
291 serial: target.tbs().serial(),
292 issuer_name_hash: &[0x11; 20],
293 issuer_key_hash: &[0x22; 20],
294 span: structured_asn1::Span {
295 start: 700,
296 length: 50,
297 },
298 };
299 let provider = Observed {
300 original_algorithm: id.algorithm.as_bytes(),
301 name: target.tbs().issuer().as_bytes(),
302 key: issuer.tbs().public_key().key().as_bytes(),
303 hashes: Cell::new(0),
304 supported: true,
305 failure: false,
306 };
307 let storage = execution.used();
308 assert_eq!(
309 id.matches(&target, &issuer, &provider, &execution).unwrap(),
310 CertMatch::Matches
311 );
312 assert_eq!(provider.hashes.get(), 2);
313 assert_eq!(execution.used(), storage);
314 let malformed = super::CertId {
315 issuer_name_hash: &[0x11; 19],
316 ..id
317 };
318 provider.hashes.set(0);
319 assert_eq!(
320 malformed
321 .matches(&target, &issuer, &provider, &execution)
322 .unwrap(),
323 CertMatch::MalformedHashWidth
324 );
325 assert_eq!(provider.hashes.get(), 0);
326 let mut provider = provider;
327 provider.supported = false;
328 assert_eq!(
329 id.matches(&target, &issuer, &provider, &execution).unwrap(),
330 CertMatch::UnsupportedHash
331 );
332 provider.supported = true;
333 provider.failure = true;
334 assert!(matches!(
335 id.matches(&target, &issuer, &provider, &execution)
336 .unwrap_err(),
337 crate::error::CallbackError::Callback("provider failure")
338 ));
339 let stopped = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
340 structured_asn1::memory::MemoryAccount::new(0, 0),
341 ));
342 assert!(
343 matches!(id.matches(&target, &issuer, &provider, &stopped).unwrap_err(), crate::error::CallbackError::Processing(error) if error.is_resource() && error.offset() == 700)
344 );
345 }
346}