Skip to main content

apfsds_crypto/
hmac_auth.rs

1//! HMAC-SHA256 authentication
2
3use 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
18/// HMAC-SHA256 authenticator
19pub struct HmacAuthenticator {
20    secret: [u8; 32],
21}
22
23impl HmacAuthenticator {
24    /// Create a new authenticator with the given secret
25    pub fn new(secret: [u8; 32]) -> Self {
26        Self { secret }
27    }
28
29    /// Compute HMAC for the given data
30    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    /// Compute HMAC with timestamp
38    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(&timestamp.to_le_bytes());
43        mac.finalize().into_bytes().into()
44    }
45
46    /// Verify HMAC in constant time
47    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    /// Verify HMAC with timestamp in constant time
57    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/// Constant-time comparison to prevent timing attacks
73#[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        // Wrong timestamp should fail
121        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}