1use subtle::ConstantTimeEq;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum DigestAlgorithm {
16 Sha1,
18 Sha256,
20 Sha384,
22 Sha512,
24}
25
26impl DigestAlgorithm {
27 pub fn from_uri(uri: &str) -> Option<Self> {
40 match uri {
41 "http://www.w3.org/2000/09/xmldsig#sha1" => Some(Self::Sha1),
42 "http://www.w3.org/2001/04/xmlenc#sha256" => Some(Self::Sha256),
43 "http://www.w3.org/2001/04/xmldsig-more#sha384" => Some(Self::Sha384),
44 "http://www.w3.org/2001/04/xmlenc#sha512" => Some(Self::Sha512),
45 _ => None,
46 }
47 }
48
49 pub fn uri(self) -> &'static str {
51 match self {
52 Self::Sha1 => "http://www.w3.org/2000/09/xmldsig#sha1",
53 Self::Sha256 => "http://www.w3.org/2001/04/xmlenc#sha256",
54 Self::Sha384 => "http://www.w3.org/2001/04/xmldsig-more#sha384",
55 Self::Sha512 => "http://www.w3.org/2001/04/xmlenc#sha512",
56 }
57 }
58
59 pub fn signing_allowed(self) -> bool {
64 !matches!(self, Self::Sha1)
65 }
66
67 pub fn output_len(self) -> usize {
69 match self {
70 Self::Sha1 => 20,
71 Self::Sha256 => 32,
72 Self::Sha384 => 48,
73 Self::Sha512 => 64,
74 }
75 }
76}
77
78pub fn compute_digest(algorithm: DigestAlgorithm, data: &[u8]) -> Vec<u8> {
82 compute_digest_with_provider(crate::provider::default_provider(), algorithm, data)
83 .expect("default provider advertises every XMLDSig digest")
84}
85
86pub fn compute_digest_with_provider(
88 provider: &dyn crate::provider::CryptoProvider,
89 algorithm: DigestAlgorithm,
90 data: &[u8],
91) -> Result<Vec<u8>, crate::provider::ProviderError> {
92 provider.require_capability(crate::provider::ProviderCapability::Digest(algorithm))?;
93 let digest = provider.digest(algorithm, data)?;
94 let expected = algorithm.output_len();
95 if digest.len() != expected {
96 return Err(crate::provider::ProviderError::InvalidOutputSize {
97 operation: crate::provider::ProviderOperation::Digest,
98 expected,
99 actual: digest.len(),
100 });
101 }
102 Ok(digest)
103}
104
105pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
114 a.ct_eq(b).into()
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 struct RejectingDigestProvider {
122 output: Option<Vec<u8>>,
123 }
124
125 impl crate::provider::CryptoProvider for RejectingDigestProvider {
126 fn name(&self) -> &'static str {
127 "rejecting-digest"
128 }
129
130 fn supports(&self, capability: crate::provider::ProviderCapability<'_>) -> bool {
131 if self.output.is_none()
132 && matches!(capability, crate::provider::ProviderCapability::Digest(_))
133 {
134 return false;
135 }
136 crate::provider::default_provider().supports(capability)
137 }
138
139 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
140 crate::provider::default_provider().fill_random(output)
141 }
142
143 fn derive_key(
144 &self,
145 parameters: &crate::provider::KdfParameters<'_>,
146 secret: &[u8],
147 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
148 crate::provider::default_provider().derive_key(parameters, secret)
149 }
150
151 fn digest(
152 &self,
153 _algorithm: DigestAlgorithm,
154 _data: &[u8],
155 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
156 if let Some(output) = &self.output {
157 return Ok(output.clone());
158 }
159 panic!("an unavailable digest capability must not be dispatched")
160 }
161
162 fn sign(
163 &self,
164 key: &dyn super::super::SigningKey,
165 algorithm: super::super::SignatureAlgorithm,
166 data: &[u8],
167 ) -> Result<Vec<u8>, super::super::SigningKeyError> {
168 crate::provider::default_provider().sign(key, algorithm, data)
169 }
170
171 fn verify(
172 &self,
173 key: &dyn super::super::VerifyingKey,
174 algorithm: super::super::SignatureAlgorithm,
175 data: &[u8],
176 signature: &[u8],
177 ) -> Result<bool, super::super::DsigError> {
178 crate::provider::default_provider().verify(key, algorithm, data, signature)
179 }
180
181 #[cfg(feature = "xmlenc")]
182 fn encrypt_data(
183 &self,
184 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
185 key: &[u8],
186 plaintext: &[u8],
187 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
188 crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
189 }
190
191 #[cfg(feature = "xmlenc")]
192 fn decrypt_data(
193 &self,
194 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
195 key: &[u8],
196 ciphertext: &[u8],
197 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
198 crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
199 }
200
201 #[cfg(feature = "xmlenc")]
202 fn wrap_key(
203 &self,
204 algorithm: crate::xmlenc::KeyWrapAlgorithm,
205 kek: &[u8],
206 key: &[u8],
207 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
208 crate::provider::default_provider().wrap_key(algorithm, kek, key)
209 }
210
211 #[cfg(feature = "xmlenc")]
212 fn unwrap_key(
213 &self,
214 algorithm: crate::xmlenc::KeyWrapAlgorithm,
215 kek: &[u8],
216 wrapped: &[u8],
217 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
218 crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
219 }
220
221 #[cfg(feature = "xmlenc")]
222 fn transport_key(
223 &self,
224 key: &dyn crate::provider::KeyTransportKey,
225 parameters: &crate::xmlenc::RsaOaepParameters,
226 plaintext: &[u8],
227 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
228 crate::provider::default_provider().transport_key(key, parameters, plaintext)
229 }
230
231 #[cfg(feature = "xmlenc")]
232 fn recover_key(
233 &self,
234 key: &dyn crate::provider::KeyRecoveryKey,
235 parameters: &crate::xmlenc::RsaOaepParameters,
236 ciphertext: &[u8],
237 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
238 crate::provider::default_provider().recover_key(key, parameters, ciphertext)
239 }
240 }
241
242 #[test]
243 fn explicit_provider_digest_failures_are_returned() {
244 assert!(matches!(
247 compute_digest_with_provider(
248 &RejectingDigestProvider { output: None },
249 DigestAlgorithm::Sha256,
250 b"x"
251 ),
252 Err(crate::provider::ProviderError::Unsupported { .. })
253 ));
254 }
255
256 #[test]
257 fn explicit_provider_digest_output_must_match_the_algorithm() {
258 for actual in [0, 31, 33] {
261 assert!(matches!(
262 compute_digest_with_provider(
263 &RejectingDigestProvider {
264 output: Some(vec![0_u8; actual]),
265 },
266 DigestAlgorithm::Sha256,
267 b"x",
268 ),
269 Err(crate::provider::ProviderError::InvalidOutputSize {
270 operation: crate::provider::ProviderOperation::Digest,
271 expected: 32,
272 actual: output_len,
273 }) if output_len == actual
274 ));
275 }
276
277 assert_eq!(
278 compute_digest_with_provider(
279 &RejectingDigestProvider {
280 output: Some(vec![7_u8; 32]),
281 },
282 DigestAlgorithm::Sha256,
283 b"x",
284 ),
285 Ok(vec![7_u8; 32]),
286 );
287 }
288
289 #[test]
292 fn from_uri_sha1() {
293 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
294 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
295 }
296
297 #[test]
298 fn from_uri_sha256() {
299 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
300 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
301 }
302
303 #[test]
304 fn from_uri_sha384() {
305 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
306 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
307 }
308
309 #[test]
310 fn from_uri_sha512() {
311 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
312 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
313 }
314
315 #[test]
316 fn from_uri_unknown() {
317 assert_eq!(
318 DigestAlgorithm::from_uri("http://example.com/unknown"),
319 None
320 );
321 }
322
323 #[test]
324 fn uri_round_trip() {
325 for algo in [
326 DigestAlgorithm::Sha1,
327 DigestAlgorithm::Sha256,
328 DigestAlgorithm::Sha384,
329 DigestAlgorithm::Sha512,
330 ] {
331 assert_eq!(
332 DigestAlgorithm::from_uri(algo.uri()),
333 Some(algo),
334 "round-trip failed for {algo:?}"
335 );
336 }
337 }
338
339 #[test]
342 fn sha1_verify_only() {
343 assert!(!DigestAlgorithm::Sha1.signing_allowed());
344 }
345
346 #[test]
347 fn sha256_signing_allowed() {
348 assert!(DigestAlgorithm::Sha256.signing_allowed());
349 }
350
351 #[test]
352 fn sha384_signing_allowed() {
353 assert!(DigestAlgorithm::Sha384.signing_allowed());
354 }
355
356 #[test]
357 fn sha512_signing_allowed() {
358 assert!(DigestAlgorithm::Sha512.signing_allowed());
359 }
360
361 #[test]
364 fn output_lengths() {
365 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
366 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
367 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
368 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
369 }
370
371 #[test]
375 fn sha1_empty() {
376 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
378 assert_eq!(digest.len(), 20);
379 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
380 }
381
382 #[test]
383 fn sha256_empty() {
384 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
386 assert_eq!(digest.len(), 32);
387 assert_eq!(
388 hex(&digest),
389 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
390 );
391 }
392
393 #[test]
394 fn sha384_empty() {
395 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
397 assert_eq!(digest.len(), 48);
398 assert_eq!(
399 hex(&digest),
400 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
401 );
402 }
403
404 #[test]
405 fn sha512_empty() {
406 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
408 assert_eq!(digest.len(), 64);
409 assert_eq!(
410 hex(&digest),
411 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
412 );
413 }
414
415 #[test]
416 fn sha256_hello_world() {
417 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
419 assert_eq!(
420 hex(&digest),
421 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
422 );
423 }
424
425 #[test]
426 fn sha1_abc() {
427 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
429 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
430 }
431
432 #[test]
435 fn constant_time_eq_identical() {
436 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
437 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
438 assert!(constant_time_eq(&a, &b));
439 }
440
441 #[test]
442 fn constant_time_eq_different_content() {
443 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
444 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
445 assert!(!constant_time_eq(&a, &b));
446 }
447
448 #[test]
449 fn constant_time_eq_different_lengths() {
450 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
451 }
452
453 #[test]
454 fn constant_time_eq_empty() {
455 assert!(constant_time_eq(&[], &[]));
456 }
457
458 #[test]
461 fn digest_output_matches_declared_length() {
462 let data = b"test data for length verification";
463 for algo in [
464 DigestAlgorithm::Sha1,
465 DigestAlgorithm::Sha256,
466 DigestAlgorithm::Sha384,
467 DigestAlgorithm::Sha512,
468 ] {
469 let digest = compute_digest(algo, data);
470 assert_eq!(
471 digest.len(),
472 algo.output_len(),
473 "output length mismatch for {algo:?}"
474 );
475 }
476 }
477
478 fn hex(bytes: &[u8]) -> String {
480 bytes.iter().map(|b| format!("{b:02x}")).collect()
481 }
482}