apfsds_crypto/
hmac_auth.rs1use hmac::{Hmac, Mac};
4use sha2::Sha256;
5use thiserror::Error;
6
7type HmacSha256 = Hmac<Sha256>;
8
9#[derive(Error, Debug)]
10pub enum HmacError {
11 #[error("Invalid key length")]
12 InvalidKeyLength,
13
14 #[error("HMAC verification failed")]
15 VerificationFailed,
16}
17
18pub struct HmacAuthenticator {
20 secret: [u8; 32],
21}
22
23impl HmacAuthenticator {
24 pub fn new(secret: [u8; 32]) -> Self {
26 Self { secret }
27 }
28
29 pub fn compute(&self, data: &[u8]) -> [u8; 32] {
31 let mut mac =
32 HmacSha256::new_from_slice(&self.secret).expect("HMAC can take key of any size");
33 mac.update(data);
34 mac.finalize().into_bytes().into()
35 }
36
37 pub fn compute_with_timestamp(&self, data: &[u8], timestamp: u64) -> [u8; 32] {
39 let mut mac =
40 HmacSha256::new_from_slice(&self.secret).expect("HMAC can take key of any size");
41 mac.update(data);
42 mac.update(×tamp.to_le_bytes());
43 mac.finalize().into_bytes().into()
44 }
45
46 pub fn verify(&self, data: &[u8], expected: &[u8; 32]) -> Result<(), HmacError> {
48 let computed = self.compute(data);
49 if constant_time_compare(&computed, expected) {
50 Ok(())
51 } else {
52 Err(HmacError::VerificationFailed)
53 }
54 }
55
56 pub fn verify_with_timestamp(
58 &self,
59 data: &[u8],
60 timestamp: u64,
61 expected: &[u8; 32],
62 ) -> Result<(), HmacError> {
63 let computed = self.compute_with_timestamp(data, timestamp);
64 if constant_time_compare(&computed, expected) {
65 Ok(())
66 } else {
67 Err(HmacError::VerificationFailed)
68 }
69 }
70}
71
72#[inline]
74fn constant_time_compare(a: &[u8; 32], b: &[u8; 32]) -> bool {
75 let mut result = 0u8;
76 for i in 0..32 {
77 result |= a[i] ^ b[i];
78 }
79 result == 0
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_hmac_compute_verify() {
88 let secret = [42u8; 32];
89 let auth = HmacAuthenticator::new(secret);
90
91 let data = b"Hello, APFSDS!";
92 let mac = auth.compute(data);
93
94 assert!(auth.verify(data, &mac).is_ok());
95 }
96
97 #[test]
98 fn test_hmac_wrong_data() {
99 let secret = [42u8; 32];
100 let auth = HmacAuthenticator::new(secret);
101
102 let data = b"Hello, APFSDS!";
103 let wrong_data = b"Wrong data!";
104 let mac = auth.compute(data);
105
106 assert!(auth.verify(wrong_data, &mac).is_err());
107 }
108
109 #[test]
110 fn test_hmac_with_timestamp() {
111 let secret = [42u8; 32];
112 let auth = HmacAuthenticator::new(secret);
113
114 let data = b"Hello, APFSDS!";
115 let timestamp = 1234567890u64;
116
117 let mac = auth.compute_with_timestamp(data, timestamp);
118 assert!(auth.verify_with_timestamp(data, timestamp, &mac).is_ok());
119
120 assert!(
122 auth.verify_with_timestamp(data, timestamp + 1, &mac)
123 .is_err()
124 );
125 }
126
127 #[test]
128 fn test_constant_time_compare() {
129 let a = [1u8; 32];
130 let b = [1u8; 32];
131 let c = [2u8; 32];
132
133 assert!(constant_time_compare(&a, &b));
134 assert!(!constant_time_compare(&a, &c));
135 }
136}