structured_pkix/ocsp/
digest.rs1use super::CertId;
4use super::ResponderId;
5use crate::Certificate;
6use crate::algorithm::digest::{self, Algorithm, AsnDigest};
7use crate::error::{CallbackError, CallbackResult, Error, ErrorCode};
8use structured_asn1::digest::{OnceError, Resolution, matches_once};
9use structured_asn1::memory::{MemoryError, Work};
10
11fn once<E>(error: OnceError<E>, field: &'static str, at: u64) -> CallbackError<E> {
13 match error {
14 OnceError::Provider(error) => CallbackError::Callback(error),
15 OnceError::OutputWidth { .. } => {
16 CallbackError::Processing(Error::new(ErrorCode::DigestOutput, field, at))
17 }
18 }
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum CertMatch {
24 Matches,
26 Differs,
28 MalformedHashWidth,
30 MalformedHashAlgorithm,
32 UnsupportedHash,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum ResponderMatch {
39 Matches,
41 Differs,
43 MalformedHashWidth,
45 UnsupportedHash,
47}
48
49impl ResponderId<'_> {
50 pub fn matches_certificate<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
59 &self,
60 certificate: &Certificate<'_>,
61 digest: &'d D,
62 execution: &E,
63 field: &'static str,
64 at: u64,
65 ) -> CallbackResult<ResponderMatch, D::Error> {
66 execution
67 .charge_work(1)
68 .map_err(|reason| Error::resource(reason, field, at))?;
69 let same = match self {
70 Self::Name(name) => name.matches(certificate.tbs().subject(), execution)?,
71 Self::KeyHash(expected) => {
72 if expected.len() != Algorithm::Sha1.width() {
73 return Ok(ResponderMatch::MalformedHashWidth);
74 }
75 let Resolution::Supported(identity) = digest::supported(digest, Algorithm::Sha1)
76 else {
77 return Ok(ResponderMatch::UnsupportedHash);
78 };
79 if digest
80 .width(&identity)
81 .is_some_and(|width| width != Algorithm::Sha1.width())
82 {
83 return Ok(ResponderMatch::MalformedHashWidth);
84 }
85 let input = certificate.tbs().public_key().key().as_bytes();
86 match matches_once(digest, &identity, input, expected, execution)
87 .map_err(|error| once(error, field, at))?
88 {
89 Resolution::Supported(same) => same,
90 Resolution::Unsupported | Resolution::Malformed => {
91 return Ok(ResponderMatch::UnsupportedHash);
92 }
93 }
94 }
95 };
96 Ok(if same {
97 ResponderMatch::Matches
98 } else {
99 ResponderMatch::Differs
100 })
101 }
102}
103
104impl CertId<'_> {
105 pub fn matches<'d, D: AsnDigest<'d> + ?Sized, E: Work>(
111 &self,
112 target: &Certificate<'_>,
113 issuer: &Certificate<'_>,
114 digest: &'d D,
115 execution: &E,
116 ) -> CallbackResult<CertMatch, D::Error> {
117 const FIELD: &str = "ocspResponse.certID";
118 let at = self.span.start;
119 let left = self.serial.as_bytes();
120 let right = target.tbs().serial().as_bytes();
121 let work = u64::try_from(left.len().max(right.len()))
122 .ok()
123 .and_then(|n| n.checked_mul(2))
124 .and_then(|n| n.checked_add(1))
125 .ok_or_else(|| Error::resource(MemoryError::WorkExceeded, FIELD, at))?;
126 execution
127 .charge_work(work)
128 .map_err(|reason| Error::resource(reason, FIELD, at))?;
129 if left != right {
130 return Ok(CertMatch::Differs);
131 }
132 if !target
133 .tbs()
134 .issuer()
135 .matches(issuer.tbs().subject(), execution)?
136 {
137 return Ok(CertMatch::Differs);
138 }
139 let identity = match digest::resolve(digest, &self.algorithm, execution)? {
140 Resolution::Supported(identity) => identity,
141 Resolution::Unsupported => return Ok(CertMatch::UnsupportedHash),
142 Resolution::Malformed => return Ok(CertMatch::MalformedHashAlgorithm),
143 };
144 if let Some(width) = digest.width(&identity)
146 && (self.issuer_name_hash.len() != width || self.issuer_key_hash.len() != width)
147 {
148 return Ok(CertMatch::MalformedHashWidth);
149 }
150 for (input, expected) in [
151 (target.tbs().issuer().as_bytes(), self.issuer_name_hash),
152 (
153 issuer.tbs().public_key().key().as_bytes(),
154 self.issuer_key_hash,
155 ),
156 ] {
157 match matches_once(digest, &identity, input, expected, execution)
158 .map_err(|error| once(error, FIELD, at))?
159 {
160 Resolution::Supported(true) => {}
161 Resolution::Supported(false) => return Ok(CertMatch::Differs),
162 Resolution::Unsupported | Resolution::Malformed => {
163 return Ok(CertMatch::UnsupportedHash);
164 }
165 }
166 }
167 Ok(CertMatch::Matches)
168 }
169}
170
171#[cfg(test)]
172mod tests;