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 let digest = provider.digest(algorithm, data)?;
93 let expected = algorithm.output_len();
94 if digest.len() != expected {
95 return Err(crate::provider::ProviderError::InvalidOutputSize {
96 operation: crate::provider::ProviderOperation::Digest,
97 expected,
98 actual: digest.len(),
99 });
100 }
101 Ok(digest)
102}
103
104pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
113 a.ct_eq(b).into()
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 struct RejectingDigestProvider {
121 output: Option<Vec<u8>>,
122 }
123
124 impl crate::provider::CryptoProvider for RejectingDigestProvider {
125 fn name(&self) -> &'static str {
126 "rejecting-digest"
127 }
128
129 fn supports(&self, query: crate::provider::CapabilityQuery<'_>) -> bool {
130 crate::provider::default_provider().supports(query)
131 }
132
133 fn fill_random(&self, output: &mut [u8]) -> Result<(), crate::provider::ProviderError> {
134 crate::provider::default_provider().fill_random(output)
135 }
136
137 fn digest(
138 &self,
139 algorithm: DigestAlgorithm,
140 _data: &[u8],
141 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
142 if let Some(output) = &self.output {
143 return Ok(output.clone());
144 }
145 Err(crate::provider::ProviderError::Unsupported {
146 operation: crate::provider::ProviderOperation::Digest,
147 algorithm: Some(algorithm.uri().to_owned()),
148 })
149 }
150
151 fn sign(
152 &self,
153 key: &dyn super::super::SigningKey,
154 algorithm: super::super::SignatureAlgorithm,
155 data: &[u8],
156 ) -> Result<Vec<u8>, super::super::SigningKeyError> {
157 crate::provider::default_provider().sign(key, algorithm, data)
158 }
159
160 fn verify(
161 &self,
162 key: &dyn super::super::VerifyingKey,
163 algorithm: super::super::SignatureAlgorithm,
164 data: &[u8],
165 signature: &[u8],
166 ) -> Result<bool, super::super::DsigError> {
167 crate::provider::default_provider().verify(key, algorithm, data, signature)
168 }
169
170 #[cfg(feature = "xmlenc")]
171 fn encrypt_data(
172 &self,
173 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
174 key: &[u8],
175 plaintext: &[u8],
176 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
177 crate::provider::default_provider().encrypt_data(algorithm, key, plaintext)
178 }
179
180 #[cfg(feature = "xmlenc")]
181 fn decrypt_data(
182 &self,
183 algorithm: crate::xmlenc::DataEncryptionAlgorithm,
184 key: &[u8],
185 ciphertext: &[u8],
186 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
187 crate::provider::default_provider().decrypt_data(algorithm, key, ciphertext)
188 }
189
190 #[cfg(feature = "xmlenc")]
191 fn wrap_key(
192 &self,
193 algorithm: crate::xmlenc::KeyWrapAlgorithm,
194 kek: &[u8],
195 key: &[u8],
196 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
197 crate::provider::default_provider().wrap_key(algorithm, kek, key)
198 }
199
200 #[cfg(feature = "xmlenc")]
201 fn unwrap_key(
202 &self,
203 algorithm: crate::xmlenc::KeyWrapAlgorithm,
204 kek: &[u8],
205 wrapped: &[u8],
206 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
207 crate::provider::default_provider().unwrap_key(algorithm, kek, wrapped)
208 }
209
210 #[cfg(feature = "xmlenc")]
211 fn transport_key(
212 &self,
213 key: &rsa::RsaPublicKey,
214 parameters: &crate::xmlenc::RsaOaepParameters,
215 plaintext: &[u8],
216 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
217 crate::provider::default_provider().transport_key(key, parameters, plaintext)
218 }
219
220 #[cfg(feature = "xmlenc")]
221 fn recover_key(
222 &self,
223 key: &rsa::RsaPrivateKey,
224 parameters: &crate::xmlenc::RsaOaepParameters,
225 ciphertext: &[u8],
226 ) -> Result<Vec<u8>, crate::provider::ProviderError> {
227 crate::provider::default_provider().recover_key(key, parameters, ciphertext)
228 }
229 }
230
231 #[test]
232 fn explicit_provider_digest_failures_are_returned() {
233 assert!(matches!(
236 compute_digest_with_provider(
237 &RejectingDigestProvider { output: None },
238 DigestAlgorithm::Sha256,
239 b"x"
240 ),
241 Err(crate::provider::ProviderError::Unsupported { .. })
242 ));
243 }
244
245 #[test]
246 fn explicit_provider_digest_output_must_match_the_algorithm() {
247 for actual in [0, 31, 33] {
250 assert!(matches!(
251 compute_digest_with_provider(
252 &RejectingDigestProvider {
253 output: Some(vec![0_u8; actual]),
254 },
255 DigestAlgorithm::Sha256,
256 b"x",
257 ),
258 Err(crate::provider::ProviderError::InvalidOutputSize {
259 operation: crate::provider::ProviderOperation::Digest,
260 expected: 32,
261 actual: output_len,
262 }) if output_len == actual
263 ));
264 }
265
266 assert_eq!(
267 compute_digest_with_provider(
268 &RejectingDigestProvider {
269 output: Some(vec![7_u8; 32]),
270 },
271 DigestAlgorithm::Sha256,
272 b"x",
273 ),
274 Ok(vec![7_u8; 32]),
275 );
276 }
277
278 #[test]
281 fn from_uri_sha1() {
282 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2000/09/xmldsig#sha1");
283 assert_eq!(algo, Some(DigestAlgorithm::Sha1));
284 }
285
286 #[test]
287 fn from_uri_sha256() {
288 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha256");
289 assert_eq!(algo, Some(DigestAlgorithm::Sha256));
290 }
291
292 #[test]
293 fn from_uri_sha384() {
294 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmldsig-more#sha384");
295 assert_eq!(algo, Some(DigestAlgorithm::Sha384));
296 }
297
298 #[test]
299 fn from_uri_sha512() {
300 let algo = DigestAlgorithm::from_uri("http://www.w3.org/2001/04/xmlenc#sha512");
301 assert_eq!(algo, Some(DigestAlgorithm::Sha512));
302 }
303
304 #[test]
305 fn from_uri_unknown() {
306 assert_eq!(
307 DigestAlgorithm::from_uri("http://example.com/unknown"),
308 None
309 );
310 }
311
312 #[test]
313 fn uri_round_trip() {
314 for algo in [
315 DigestAlgorithm::Sha1,
316 DigestAlgorithm::Sha256,
317 DigestAlgorithm::Sha384,
318 DigestAlgorithm::Sha512,
319 ] {
320 assert_eq!(
321 DigestAlgorithm::from_uri(algo.uri()),
322 Some(algo),
323 "round-trip failed for {algo:?}"
324 );
325 }
326 }
327
328 #[test]
331 fn sha1_verify_only() {
332 assert!(!DigestAlgorithm::Sha1.signing_allowed());
333 }
334
335 #[test]
336 fn sha256_signing_allowed() {
337 assert!(DigestAlgorithm::Sha256.signing_allowed());
338 }
339
340 #[test]
341 fn sha384_signing_allowed() {
342 assert!(DigestAlgorithm::Sha384.signing_allowed());
343 }
344
345 #[test]
346 fn sha512_signing_allowed() {
347 assert!(DigestAlgorithm::Sha512.signing_allowed());
348 }
349
350 #[test]
353 fn output_lengths() {
354 assert_eq!(DigestAlgorithm::Sha1.output_len(), 20);
355 assert_eq!(DigestAlgorithm::Sha256.output_len(), 32);
356 assert_eq!(DigestAlgorithm::Sha384.output_len(), 48);
357 assert_eq!(DigestAlgorithm::Sha512.output_len(), 64);
358 }
359
360 #[test]
364 fn sha1_empty() {
365 let digest = compute_digest(DigestAlgorithm::Sha1, b"");
367 assert_eq!(digest.len(), 20);
368 assert_eq!(hex(&digest), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
369 }
370
371 #[test]
372 fn sha256_empty() {
373 let digest = compute_digest(DigestAlgorithm::Sha256, b"");
375 assert_eq!(digest.len(), 32);
376 assert_eq!(
377 hex(&digest),
378 "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
379 );
380 }
381
382 #[test]
383 fn sha384_empty() {
384 let digest = compute_digest(DigestAlgorithm::Sha384, b"");
386 assert_eq!(digest.len(), 48);
387 assert_eq!(
388 hex(&digest),
389 "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"
390 );
391 }
392
393 #[test]
394 fn sha512_empty() {
395 let digest = compute_digest(DigestAlgorithm::Sha512, b"");
397 assert_eq!(digest.len(), 64);
398 assert_eq!(
399 hex(&digest),
400 "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"
401 );
402 }
403
404 #[test]
405 fn sha256_hello_world() {
406 let digest = compute_digest(DigestAlgorithm::Sha256, b"hello world");
408 assert_eq!(
409 hex(&digest),
410 "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
411 );
412 }
413
414 #[test]
415 fn sha1_abc() {
416 let digest = compute_digest(DigestAlgorithm::Sha1, b"abc");
418 assert_eq!(hex(&digest), "a9993e364706816aba3e25717850c26c9cd0d89d");
419 }
420
421 #[test]
424 fn constant_time_eq_identical() {
425 let a = compute_digest(DigestAlgorithm::Sha256, b"test");
426 let b = compute_digest(DigestAlgorithm::Sha256, b"test");
427 assert!(constant_time_eq(&a, &b));
428 }
429
430 #[test]
431 fn constant_time_eq_different_content() {
432 let a = compute_digest(DigestAlgorithm::Sha256, b"test1");
433 let b = compute_digest(DigestAlgorithm::Sha256, b"test2");
434 assert!(!constant_time_eq(&a, &b));
435 }
436
437 #[test]
438 fn constant_time_eq_different_lengths() {
439 assert!(!constant_time_eq(&[1, 2, 3], &[1, 2]));
440 }
441
442 #[test]
443 fn constant_time_eq_empty() {
444 assert!(constant_time_eq(&[], &[]));
445 }
446
447 #[test]
450 fn digest_output_matches_declared_length() {
451 let data = b"test data for length verification";
452 for algo in [
453 DigestAlgorithm::Sha1,
454 DigestAlgorithm::Sha256,
455 DigestAlgorithm::Sha384,
456 DigestAlgorithm::Sha512,
457 ] {
458 let digest = compute_digest(algo, data);
459 assert_eq!(
460 digest.len(),
461 algo.output_len(),
462 "output length mismatch for {algo:?}"
463 );
464 }
465 }
466
467 fn hex(bytes: &[u8]) -> String {
469 bytes.iter().map(|b| format!("{b:02x}")).collect()
470 }
471}