1use super::CertId;
4use crate::AlgorithmIdentifier;
5use crate::Certificate;
6use crate::error::{CallbackError, CallbackResult, Error};
7use core::num::NonZeroUsize;
8use structured_asn1::memory::{Memory, MemoryError};
9
10#[derive(Debug, Clone, Copy)]
12pub enum DigestAlgorithm<'a> {
13 Identifier(&'a AlgorithmIdentifier<'a>),
15 Sha1,
17}
18
19#[derive(Debug, Clone, Copy)]
22pub struct ResolvedDigest<H: Copy> {
23 pub handle: H,
25 pub width: NonZeroUsize,
27}
28
29pub trait Digest {
34 type Handle: Copy;
36 type Error;
38 fn resolve(
40 &self,
41 algorithm: DigestAlgorithm<'_>,
42 execution: &Memory,
43 ) -> core::result::Result<Option<ResolvedDigest<Self::Handle>>, Self::Error>;
44 fn matches(
47 &self,
48 handle: Self::Handle,
49 input: &[u8],
50 expected: &[u8],
51 execution: &Memory,
52 ) -> core::result::Result<bool, Self::Error>;
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum CertMatch {
58 Matches,
60 Differs,
62 MalformedHashWidth,
64 UnsupportedHash,
66}
67
68impl CertId<'_> {
69 pub fn matches<D: Digest>(
75 &self,
76 target: &Certificate<'_>,
77 issuer: &Certificate<'_>,
78 digest: &D,
79 execution: &Memory,
80 ) -> CallbackResult<CertMatch, D::Error> {
81 const FIELD: &str = "ocspResponse.certID";
82 let at = self.span.start;
83 let left = self.serial.as_bytes();
84 let right = target.tbs().serial().as_bytes();
85 let work = u64::try_from(left.len().max(right.len()))
86 .ok()
87 .and_then(|n| n.checked_mul(2))
88 .and_then(|n| n.checked_add(1))
89 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, at))?;
90 execution
91 .charge_work(work)
92 .map_err(|reason| Error::resource(reason, FIELD, at))?;
93 if left != right {
94 return Ok(CertMatch::Differs);
95 }
96 if !target
97 .tbs()
98 .issuer()
99 .matches(issuer.tbs().subject(), execution)?
100 {
101 return Ok(CertMatch::Differs);
102 }
103 execution
104 .charge_work(1)
105 .map_err(|reason| Error::resource(reason, FIELD, at))?;
106 let Some(resolved) = digest
107 .resolve(DigestAlgorithm::Identifier(&self.algorithm), execution)
108 .map_err(CallbackError::Callback)?
109 else {
110 return Ok(CertMatch::UnsupportedHash);
111 };
112 if self.issuer_name_hash.len() != resolved.width.get()
114 || self.issuer_key_hash.len() != resolved.width.get()
115 {
116 return Ok(CertMatch::MalformedHashWidth);
117 }
118 if !digest
119 .matches(
120 resolved.handle,
121 target.tbs().issuer().as_bytes(),
122 self.issuer_name_hash,
123 execution,
124 )
125 .map_err(CallbackError::Callback)?
126 {
127 return Ok(CertMatch::Differs);
128 }
129 if !digest
130 .matches(
131 resolved.handle,
132 issuer.tbs().public_key().key().as_bytes(),
133 self.issuer_key_hash,
134 execution,
135 )
136 .map_err(CallbackError::Callback)?
137 {
138 return Ok(CertMatch::Differs);
139 }
140 Ok(CertMatch::Matches)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::{CertMatch, Digest, DigestAlgorithm, ResolvedDigest};
147 use core::cell::Cell;
148 use core::num::NonZeroUsize;
149 use structured_asn1::memory::Memory;
150
151 struct Observed<'a> {
152 original_algorithm: &'a [u8],
153 name: &'a [u8],
154 key: &'a [u8],
155 hashes: Cell<usize>,
156 supported: bool,
157 failure: bool,
158 }
159 impl Digest for Observed<'_> {
160 type Handle = u8;
161 type Error = &'static str;
162 fn resolve(
163 &self,
164 algorithm: DigestAlgorithm<'_>,
165 _: &Memory,
166 ) -> Result<Option<ResolvedDigest<u8>>, Self::Error> {
167 let DigestAlgorithm::Identifier(algorithm) = algorithm else {
168 panic!("CertID does not impose SHA1")
169 };
170 assert_eq!(algorithm.as_bytes(), self.original_algorithm);
171 assert_eq!(
172 algorithm.as_bytes().as_ptr(),
173 self.original_algorithm.as_ptr()
174 );
175 if !self.supported {
176 return Ok(None);
177 }
178 Ok(Some(ResolvedDigest {
179 handle: 1,
180 width: NonZeroUsize::new(20).unwrap(),
181 }))
182 }
183 fn matches(
184 &self,
185 handle: u8,
186 input: &[u8],
187 expected: &[u8],
188 _: &Memory,
189 ) -> Result<bool, Self::Error> {
190 assert_eq!(handle, 1);
191 assert_eq!(expected.len(), 20);
192 if self.failure {
193 return Err("provider failure");
194 }
195 let calls = self.hashes.get();
196 self.hashes.set(calls + 1);
197 let original = if calls == 0 { self.name } else { self.key };
198 assert_eq!(input, original);
199 assert_eq!(input.as_ptr(), original.as_ptr());
200 Ok(true)
201 }
202 }
203
204 #[test]
205 fn cert_id_prechecks_width_and_forwards_original_bytes_without_output_copy() {
206 let parts = crate::fixtures::CertificateParts::default();
209 let issuer_bytes = crate::fixtures::CertificateParts {
210 subject: parts.issuer.clone(),
211 ..crate::fixtures::CertificateParts::default()
212 }
213 .build();
214 let target_bytes = parts.build();
215 let execution = crate::fixtures::memory();
216 let parser = structured_asn1::Parser::der(execution.clone());
217 let target = crate::Certificate::read(&parser, &target_bytes, 100).unwrap();
218 let issuer = crate::Certificate::read(&parser, &issuer_bytes, 300).unwrap();
219 let id = super::CertId {
220 algorithm: target.signature_algorithm(),
221 serial: target.tbs().serial(),
222 issuer_name_hash: &[0x11; 20],
223 issuer_key_hash: &[0x22; 20],
224 span: structured_asn1::Span {
225 start: 700,
226 length: 50,
227 },
228 };
229 let provider = Observed {
230 original_algorithm: id.algorithm.as_bytes(),
231 name: target.tbs().issuer().as_bytes(),
232 key: issuer.tbs().public_key().key().as_bytes(),
233 hashes: Cell::new(0),
234 supported: true,
235 failure: false,
236 };
237 let storage = execution.used();
238 assert_eq!(
239 id.matches(&target, &issuer, &provider, &execution).unwrap(),
240 CertMatch::Matches
241 );
242 assert_eq!(provider.hashes.get(), 2);
243 assert_eq!(execution.used(), storage);
244 let malformed = super::CertId {
245 issuer_name_hash: &[0x11; 19],
246 ..id
247 };
248 provider.hashes.set(0);
249 assert_eq!(
250 malformed
251 .matches(&target, &issuer, &provider, &execution)
252 .unwrap(),
253 CertMatch::MalformedHashWidth
254 );
255 assert_eq!(provider.hashes.get(), 0);
256 let mut provider = provider;
257 provider.supported = false;
258 assert_eq!(
259 id.matches(&target, &issuer, &provider, &execution).unwrap(),
260 CertMatch::UnsupportedHash
261 );
262 provider.supported = true;
263 provider.failure = true;
264 assert!(matches!(
265 id.matches(&target, &issuer, &provider, &execution)
266 .unwrap_err(),
267 crate::error::CallbackError::Callback("provider failure")
268 ));
269 let stopped = structured_asn1::memory::Memory::new(alloc::sync::Arc::new(
270 structured_asn1::memory::MemoryAccount::new(0, 0),
271 ));
272 assert!(
273 matches!(id.matches(&target, &issuer, &provider, &stopped).unwrap_err(), crate::error::CallbackError::Processing(error) if error.is_resource() && error.offset() == 700)
274 );
275 }
276}