1use crate::xmldsig::{DsigError, SignatureAlgorithm, SignatureContext, VerifyingKey};
4use structured_asn1::memory::MemoryError;
5use structured_pkix::path::WorkingKey;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum KeyAssociation {
10 Same,
12 Mismatch,
14 Unproven,
16}
17
18#[derive(Clone, Copy)]
20pub struct SignatureVerificationInput<'a> {
21 pub algorithm: SignatureAlgorithm,
23 pub context: &'a SignatureContext,
25 pub data: &'a [u8],
27 pub signature: &'a [u8],
29}
30
31pub enum KeyVerificationEvent<'a> {
33 BeforeCandidate {
35 depth: usize,
37 },
38 Verified(&'a dyn VerifyingKey),
40}
41
42pub(super) fn observed_leaf<F, O>(
43 key: &dyn VerifyingKey,
44 observer: &mut O,
45 verify_leaf: &F,
46) -> Result<bool, DsigError>
47where
48 F: Fn(&dyn VerifyingKey) -> Result<bool, DsigError>,
49 O: FnMut(KeyVerificationEvent<'_>) -> Result<(), DsigError> + ?Sized,
50{
51 let mut admitted = 0;
52 let mut observer_refused = false;
53 let result = observed_at_depth(
54 key,
55 &mut |event| {
56 if observer_refused {
57 return Err(MemoryError::WorkExceeded.into());
58 }
59 let result = observer(event);
60 if matches!(
61 result,
62 Err(DsigError::SharedExecution(MemoryError::WorkExceeded))
63 ) {
64 observer_refused = true;
65 }
66 result
67 },
68 verify_leaf,
69 0,
70 &mut admitted,
71 );
72 if observer_refused || admitted > crate::hard_limits::KEY_CANDIDATE_CEILING {
75 return Err(MemoryError::WorkExceeded.into());
76 }
77 result
78}
79
80fn observed_at_depth<F, O>(
81 key: &dyn VerifyingKey,
82 observer: &mut O,
83 verify_leaf: &F,
84 depth: usize,
85 admitted: &mut usize,
86) -> Result<bool, DsigError>
87where
88 F: Fn(&dyn VerifyingKey) -> Result<bool, DsigError>,
89 O: FnMut(KeyVerificationEvent<'_>) -> Result<(), DsigError> + ?Sized,
90{
91 if depth == crate::hard_limits::KEY_CANDIDATE_CEILING
92 || *admitted >= crate::hard_limits::KEY_CANDIDATE_CEILING
93 {
94 *admitted = crate::hard_limits::KEY_CANDIDATE_CEILING + 1;
95 return Err(MemoryError::WorkExceeded.into());
96 }
97 *admitted += 1;
98 observer(KeyVerificationEvent::BeforeCandidate { depth })?;
99 if let Some(result) = key.verify_candidate_keys(&mut |candidate| {
100 observed_at_depth(candidate, observer, verify_leaf, depth + 1, admitted)
101 })? {
102 return Ok(result);
103 }
104 let verified = verify_leaf(key)?;
105 if verified {
106 observer(KeyVerificationEvent::Verified(key))?;
107 }
108 Ok(verified)
109}
110
111#[cfg(any(feature = "aws-lc-fips", feature = "pkcs11"))]
112pub(super) fn compare(
113 actual: &[u8],
114 expected: WorkingKey<'_>,
115 charge: &dyn Fn(u64) -> Result<(), MemoryError>,
116) -> Result<KeyAssociation, DsigError> {
117 compare_with_points(actual, expected, charge, false)
118}
119
120pub(super) fn compare_rustcrypto(
121 actual: &[u8],
122 expected: WorkingKey<'_>,
123 charge: &dyn Fn(u64) -> Result<(), MemoryError>,
124) -> Result<KeyAssociation, DsigError> {
125 compare_with_points(actual, expected, charge, true)
126}
127
128fn compare_with_points(
129 actual: &[u8],
130 expected: WorkingKey<'_>,
131 charge: &dyn Fn(u64) -> Result<(), MemoryError>,
132 rustcrypto_points: bool,
133) -> Result<KeyAssociation, DsigError> {
134 use der::{Decode as _, Tagged as _};
135 let size = actual
138 .len()
139 .checked_add(expected.key().as_bytes().len())
140 .and_then(|size| size.checked_add(expected.parameters().map_or(0, <[u8]>::len)))
141 .and_then(|size| size.checked_mul(8))
142 .and_then(|size| size.checked_add(256))
143 .and_then(|size| u64::try_from(size).ok())
144 .ok_or(MemoryError::WorkExceeded)?;
145 charge(size)?;
146 let Ok(actual) = pkcs8::SubjectPublicKeyInfoRef::from_der(actual) else {
147 return Ok(KeyAssociation::Unproven);
148 };
149 let Some(bits) = actual.subject_public_key.as_bytes() else {
150 return Ok(KeyAssociation::Unproven);
151 };
152 let Some(expected_bits) = expected.key().key_octets() else {
153 return Ok(KeyAssociation::Unproven);
154 };
155 let algorithm = expected.key().algorithm();
156 let rsa_pss = der::asn1::ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.10");
157 let effective_parameters = if algorithm.algorithm().as_bytes() == rsa_pss.as_bytes() {
162 algorithm.parameters()
163 } else {
164 expected.parameters()
165 };
166 let parameters = match effective_parameters {
167 Some(bytes) => match der::asn1::AnyRef::from_der(bytes) {
168 Ok(value) => Some(value),
169 Err(_) => return Ok(KeyAssociation::Unproven),
170 },
171 None => None,
172 };
173 let expected_oid = algorithm.algorithm().as_bytes();
174 let actual_rsa =
175 actual.algorithm.oid == pkcs1::ALGORITHM_OID || actual.algorithm.oid == rsa_pss;
176 let expected_rsa =
177 expected_oid == pkcs1::ALGORITHM_OID.as_bytes() || expected_oid == rsa_pss.as_bytes();
178 if actual_rsa && expected_rsa {
179 if (actual.algorithm.oid == pkcs1::ALGORITHM_OID
183 && actual
184 .algorithm
185 .parameters
186 .is_some_and(|value| !value.is_null()))
187 || (expected_oid == pkcs1::ALGORITHM_OID.as_bytes()
188 && parameters.is_some_and(|value| !value.is_null()))
189 || (actual.algorithm.oid == rsa_pss
190 && actual
191 .algorithm
192 .parameters
193 .is_some_and(|value| value.tag() != der::Tag::Sequence))
194 || (expected_oid == rsa_pss.as_bytes()
195 && parameters.is_some_and(|value| value.tag() != der::Tag::Sequence))
196 {
197 return Ok(KeyAssociation::Unproven);
198 }
199 let components = rsa_components(bits, expected_bits);
200 return Ok(
206 if components == KeyAssociation::Same
207 && (actual.algorithm.oid.as_bytes() != expected_oid
208 || (actual.algorithm.oid == rsa_pss
209 && actual.algorithm.parameters != parameters))
210 {
211 KeyAssociation::Unproven
212 } else {
213 components
214 },
215 );
216 }
217 if actual.algorithm.oid.as_bytes() != expected_oid {
218 return Ok(KeyAssociation::Unproven);
219 }
220 let dsa = der::asn1::ObjectIdentifier::new_unwrap("1.2.840.10040.4.1");
221 if actual.algorithm.oid == dsa {
222 let (Some(actual_params), Some(expected_params)) =
226 (actual.algorithm.parameters, parameters)
227 else {
228 return Ok(KeyAssociation::Unproven);
229 };
230 let (Ok(actual_params), Ok(expected_params), Ok(actual_y), Ok(expected_y)) = (
231 actual_params.decode_as::<crate::xmldsig::signature::BorrowedDsaPublicParameters<'_>>(),
232 expected_params
233 .decode_as::<crate::xmldsig::signature::BorrowedDsaPublicParameters<'_>>(),
234 der::asn1::UintRef::from_der(bits),
235 der::asn1::UintRef::from_der(expected_bits),
236 ) else {
237 return Ok(KeyAssociation::Unproven);
238 };
239 return Ok(
240 if actual_y == expected_y
241 && actual_params.p == expected_params.p
242 && actual_params.q == expected_params.q
243 && actual_params.g == expected_params.g
244 {
245 KeyAssociation::Same
246 } else {
247 KeyAssociation::Mismatch
248 },
249 );
250 }
251 #[cfg(feature = "experimental-pq")]
252 if rustcrypto_points
253 && let Some(algorithm) = crate::xmldsig::PqAlgorithm::from_oid(actual.algorithm.oid)
254 {
255 return Ok(
261 if actual.algorithm.parameters.is_some()
262 || expected.key().algorithm().parameters().is_some()
263 || parameters.is_some()
264 || !valid_pq_public_encoding(algorithm, bits)
265 || !valid_pq_public_encoding(algorithm, expected_bits)
266 {
267 KeyAssociation::Unproven
268 } else if bits == expected_bits {
269 KeyAssociation::Same
270 } else {
271 KeyAssociation::Mismatch
272 },
273 );
274 }
275 if rustcrypto_points
276 && (actual.algorithm.oid == ed25519_dalek::pkcs8::ALGORITHM_OID
277 || actual.algorithm.oid == ed448_goldilocks::ALGORITHM_OID)
278 {
279 if actual.algorithm.parameters.is_some()
288 || expected.key().algorithm().parameters().is_some()
289 || parameters.is_some()
290 {
291 return Ok(KeyAssociation::Unproven);
292 }
293 macro_rules! compare_edwards {
294 ($key:ty, $length:expr, $width:expr, $point:ident, $encoded:expr) => {{
295 let (Ok(left), Ok(right)) = (
296 <&[u8; $length]>::try_from(bits),
297 <&[u8; $length]>::try_from(expected_bits),
298 ) else {
299 return Ok(KeyAssociation::Unproven);
300 };
301 charge($width * $width * 8)?;
302 let Ok(left) = <$key>::from_bytes(left) else {
303 return Ok(KeyAssociation::Unproven);
304 };
305 let $point = left.to_edwards();
306 let left = $encoded;
307 if left.as_slice() != bits {
310 KeyAssociation::Unproven
311 } else if bits == expected_bits {
312 KeyAssociation::Same
314 } else {
315 charge($width * $width * 8)?;
316 let Ok(right) = <$key>::from_bytes(right) else {
317 return Ok(KeyAssociation::Unproven);
318 };
319 let $point = right.to_edwards();
320 let right = $encoded;
321 if right.as_slice() != expected_bits {
322 KeyAssociation::Unproven
323 } else {
324 KeyAssociation::Mismatch
325 }
326 }
327 }};
328 }
329 return Ok(
330 if actual.algorithm.oid == ed25519_dalek::pkcs8::ALGORITHM_OID {
331 compare_edwards!(
332 ed25519_dalek::VerifyingKey,
333 32,
334 256,
335 point,
336 point.compress().to_bytes()
337 )
338 } else {
339 compare_edwards!(
340 ed448_goldilocks::VerifyingKey,
341 57,
342 448,
343 point,
344 point.to_affine().compress().0
345 )
346 },
347 );
348 }
349 if actual.algorithm.parameters != parameters {
350 return Ok(KeyAssociation::Unproven);
354 }
355 if bits == expected_bits {
358 return Ok(KeyAssociation::Same);
359 }
360 if rustcrypto_points && actual.algorithm.oid == p256::elliptic_curve::ALGORITHM_OID {
361 let Some(curve) =
362 parameters.and_then(|value| value.decode_as::<der::asn1::ObjectIdentifier>().ok())
363 else {
364 return Ok(KeyAssociation::Unproven);
365 };
366 macro_rules! compare_points {
370 ($curve:ty, $bits:expr) => {{
371 charge($bits * $bits * 16)?;
374 match (
375 <$curve>::from_sec1_bytes(bits),
376 <$curve>::from_sec1_bytes(expected_bits),
377 ) {
378 (Ok(left), Ok(right)) => {
379 if left == right {
380 KeyAssociation::Same
381 } else {
382 KeyAssociation::Mismatch
383 }
384 }
385 _ => KeyAssociation::Unproven,
386 }
387 }};
388 }
389 return Ok(
390 if curve == der::asn1::ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7") {
391 compare_points!(p256::PublicKey, 256)
392 } else if curve == der::asn1::ObjectIdentifier::new_unwrap("1.3.132.0.34") {
393 compare_points!(p384::PublicKey, 384)
394 } else if curve == der::asn1::ObjectIdentifier::new_unwrap("1.3.132.0.35") {
395 compare_points!(p521::PublicKey, 521)
396 } else {
397 KeyAssociation::Unproven
398 },
399 );
400 }
401 Ok(KeyAssociation::Unproven)
402}
403
404#[cfg(feature = "experimental-pq")]
405fn valid_pq_public_encoding(algorithm: crate::xmldsig::PqAlgorithm, bytes: &[u8]) -> bool {
406 use crate::rustcrypto_ml_dsa as ml_dsa;
407 use crate::xmldsig::PqAlgorithm;
408 fn ml<P: ml_dsa::MlDsaParams>(bytes: &[u8]) -> bool {
414 <&ml_dsa::EncodedVerifyingKey<P>>::try_from(bytes).is_ok()
415 }
416 fn slh<P: slh_dsa::ParameterSet>(bytes: &[u8]) -> bool {
420 <&hybrid_array::Array<u8, P::VkLen>>::try_from(bytes).is_ok()
421 }
422 match algorithm {
423 PqAlgorithm::MlDsa44 => ml::<ml_dsa::MlDsa44>(bytes),
424 PqAlgorithm::MlDsa65 => ml::<ml_dsa::MlDsa65>(bytes),
425 PqAlgorithm::MlDsa87 => ml::<ml_dsa::MlDsa87>(bytes),
426 PqAlgorithm::SlhDsaSha2_128f => slh::<slh_dsa::Sha2_128f>(bytes),
427 PqAlgorithm::SlhDsaSha2_128s => slh::<slh_dsa::Sha2_128s>(bytes),
428 PqAlgorithm::SlhDsaSha2_192f => slh::<slh_dsa::Sha2_192f>(bytes),
429 PqAlgorithm::SlhDsaSha2_192s => slh::<slh_dsa::Sha2_192s>(bytes),
430 PqAlgorithm::SlhDsaSha2_256f => slh::<slh_dsa::Sha2_256f>(bytes),
431 PqAlgorithm::SlhDsaSha2_256s => slh::<slh_dsa::Sha2_256s>(bytes),
432 }
433}
434
435fn rsa_components(actual: &[u8], expected: &[u8]) -> KeyAssociation {
436 use der::Decode as _;
437 let (Ok(actual), Ok(expected)) = (
438 pkcs1::RsaPublicKeyRef::from_der(actual),
439 pkcs1::RsaPublicKeyRef::from_der(expected),
440 ) else {
441 return KeyAssociation::Unproven;
442 };
443 if actual.modulus == expected.modulus && actual.public_exponent == expected.public_exponent {
444 KeyAssociation::Same
445 } else {
446 KeyAssociation::Mismatch
447 }
448}