herolib_crypt/httpsig/
digest.rs1use crate::httpsig::error::HttpSigError;
6use base64::Engine;
7use sha2::{Digest, Sha256};
8
9pub const EMPTY_BODY_DIGEST: &str = "sha-256=:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=:";
14
15pub fn compute_content_digest(body: &[u8]) -> String {
30 if body.is_empty() {
31 return EMPTY_BODY_DIGEST.to_string();
32 }
33
34 let mut hasher = Sha256::new();
35 hasher.update(body);
36 let hash = hasher.finalize();
37
38 let b64 = base64::engine::general_purpose::STANDARD.encode(hash);
39 format!("sha-256=:{}:", b64)
40}
41
42pub fn verify_content_digest(body: &[u8], digest_header: &str) -> Result<(), HttpSigError> {
61 let computed = compute_content_digest(body);
62
63 if digest_header != computed {
64 return Err(HttpSigError::DigestMismatch);
65 }
66
67 Ok(())
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_empty_body_digest() {
76 let digest = compute_content_digest(b"");
77 assert_eq!(digest, EMPTY_BODY_DIGEST);
78 }
79
80 #[test]
81 fn test_compute_digest() {
82 let body = b"Hello, World!";
83 let digest = compute_content_digest(body);
84
85 assert!(digest.starts_with("sha-256=:"));
86 assert!(digest.ends_with(":"));
87 assert!(digest.len() > 20);
88 }
89
90 #[test]
91 fn test_verify_digest_success() {
92 let body = b"Test body";
93 let digest = compute_content_digest(body);
94
95 assert!(verify_content_digest(body, &digest).is_ok());
96 }
97
98 #[test]
99 fn test_verify_digest_mismatch() {
100 let body = b"Test body";
101 let digest = compute_content_digest(body);
102
103 let result = verify_content_digest(b"Different body", &digest);
104 assert!(matches!(result, Err(HttpSigError::DigestMismatch)));
105 }
106
107 #[test]
108 fn test_digest_deterministic() {
109 let body = b"Deterministic test";
110 let digest1 = compute_content_digest(body);
111 let digest2 = compute_content_digest(body);
112
113 assert_eq!(digest1, digest2);
114 }
115}