corevpn_crypto/
hmac_auth.rs

1//! HMAC Authentication for OpenVPN tls-auth / tls-crypt
2//!
3//! Provides pre-shared key authentication for the control channel,
4//! protecting against DoS attacks and providing an additional layer of security.
5
6use hmac::{Hmac, Mac};
7use sha2::Sha256;
8use subtle::ConstantTimeEq;
9use zeroize::ZeroizeOnDrop;
10
11use crate::{CryptoError, Result};
12
13type HmacSha256 = Hmac<Sha256>;
14
15/// HMAC authentication key for tls-auth
16#[derive(ZeroizeOnDrop)]
17pub struct HmacAuth {
18    /// Key for outgoing packets
19    tx_key: [u8; 32],
20    /// Key for incoming packets
21    rx_key: [u8; 32],
22}
23
24impl HmacAuth {
25    /// Key size in bytes
26    pub const KEY_SIZE: usize = 32;
27    /// HMAC output size in bytes
28    pub const HMAC_SIZE: usize = 32;
29
30    /// Create from separate TX and RX keys
31    pub fn new(tx_key: [u8; 32], rx_key: [u8; 32]) -> Self {
32        Self { tx_key, rx_key }
33    }
34
35    /// Create from a single key (same key for both directions)
36    pub fn from_single_key(key: [u8; 32]) -> Self {
37        Self {
38            tx_key: key,
39            rx_key: key,
40        }
41    }
42
43    /// Create from OpenVPN ta.key format (2048-bit / 256 bytes)
44    ///
45    /// OpenVPN ta.key contains 4 keys:
46    /// - Bytes 0-63: Client HMAC key (encrypt direction)
47    /// - Bytes 64-127: Server HMAC key (encrypt direction)
48    /// - Bytes 128-191: Client HMAC key (decrypt direction)
49    /// - Bytes 192-255: Server HMAC key (decrypt direction)
50    pub fn from_ta_key(ta_key: &[u8; 256], is_server: bool, key_direction: Option<u8>) -> Self {
51        let (tx_key, rx_key) = match (is_server, key_direction) {
52            // Server with key-direction 0 (normal)
53            (true, Some(0)) | (true, None) => {
54                let mut tx = [0u8; 32];
55                let mut rx = [0u8; 32];
56                tx.copy_from_slice(&ta_key[64..96]);
57                rx.copy_from_slice(&ta_key[0..32]);
58                (tx, rx)
59            }
60            // Server with key-direction 1 (reversed)
61            (true, Some(1)) => {
62                let mut tx = [0u8; 32];
63                let mut rx = [0u8; 32];
64                tx.copy_from_slice(&ta_key[0..32]);
65                rx.copy_from_slice(&ta_key[64..96]);
66                (tx, rx)
67            }
68            // Client with key-direction 1 (normal for client)
69            (false, Some(1)) | (false, None) => {
70                let mut tx = [0u8; 32];
71                let mut rx = [0u8; 32];
72                tx.copy_from_slice(&ta_key[0..32]);
73                rx.copy_from_slice(&ta_key[64..96]);
74                (tx, rx)
75            }
76            // Client with key-direction 0 (reversed for client)
77            (false, Some(0)) => {
78                let mut tx = [0u8; 32];
79                let mut rx = [0u8; 32];
80                tx.copy_from_slice(&ta_key[64..96]);
81                rx.copy_from_slice(&ta_key[0..32]);
82                (tx, rx)
83            }
84            _ => panic!("Invalid key direction"),
85        };
86
87        Self { tx_key, rx_key }
88    }
89
90    /// Compute HMAC for an outgoing packet
91    pub fn authenticate(&self, data: &[u8]) -> [u8; 32] {
92        let mut mac = HmacSha256::new_from_slice(&self.tx_key)
93            .expect("HMAC key size is always valid");
94        mac.update(data);
95        mac.finalize().into_bytes().into()
96    }
97
98    /// Verify HMAC for an incoming packet (constant-time)
99    pub fn verify(&self, data: &[u8], expected_hmac: &[u8; 32]) -> Result<()> {
100        let mut mac = HmacSha256::new_from_slice(&self.rx_key)
101            .expect("HMAC key size is always valid");
102        mac.update(data);
103        let computed = mac.finalize().into_bytes();
104
105        if computed.ct_eq(expected_hmac).into() {
106            Ok(())
107        } else {
108            Err(CryptoError::HmacVerificationFailed)
109        }
110    }
111
112    /// Wrap a packet with HMAC (prepends HMAC to data)
113    pub fn wrap(&self, data: &[u8]) -> Vec<u8> {
114        let hmac = self.authenticate(data);
115        let mut output = Vec::with_capacity(Self::HMAC_SIZE + data.len());
116        output.extend_from_slice(&hmac);
117        output.extend_from_slice(data);
118        output
119    }
120
121    /// Unwrap a packet and verify HMAC
122    pub fn unwrap(&self, packet: &[u8]) -> Result<Vec<u8>> {
123        if packet.len() < Self::HMAC_SIZE {
124            return Err(CryptoError::HmacVerificationFailed);
125        }
126
127        let (hmac, data) = packet.split_at(Self::HMAC_SIZE);
128        let hmac: [u8; 32] = hmac.try_into().unwrap();
129
130        self.verify(data, &hmac)?;
131        Ok(data.to_vec())
132    }
133}
134
135/// tls-crypt key for both HMAC and encryption
136#[derive(ZeroizeOnDrop)]
137pub struct TlsCryptKey {
138    /// Encryption key
139    cipher_key: [u8; 32],
140    /// HMAC authentication key
141    hmac_key: [u8; 32],
142}
143
144impl TlsCryptKey {
145    /// Create from raw keys
146    pub fn new(cipher_key: [u8; 32], hmac_key: [u8; 32]) -> Self {
147        Self { cipher_key, hmac_key }
148    }
149
150    /// Create from a 512-bit (64-byte) combined key
151    pub fn from_combined(key: &[u8; 64]) -> Self {
152        let mut cipher_key = [0u8; 32];
153        let mut hmac_key = [0u8; 32];
154        cipher_key.copy_from_slice(&key[0..32]);
155        hmac_key.copy_from_slice(&key[32..64]);
156        Self { cipher_key, hmac_key }
157    }
158
159    /// Get the cipher key
160    pub fn cipher_key(&self) -> &[u8; 32] {
161        &self.cipher_key
162    }
163
164    /// Get the HMAC key
165    pub fn hmac_key(&self) -> &[u8; 32] {
166        &self.hmac_key
167    }
168
169    /// Wrap control channel packet with tls-crypt (encrypt-then-MAC)
170    ///
171    /// Format: [HMAC-SHA256(ciphertext) | IV | ciphertext]
172    pub fn wrap(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
173        use crate::cipher::Cipher;
174        use crate::CipherSuite;
175
176        // Generate random IV
177        let cipher = Cipher::new(&self.cipher_key, CipherSuite::ChaCha20Poly1305);
178        let nonce = cipher.generate_nonce();
179
180        // Encrypt
181        let ciphertext = cipher.encrypt(&nonce, plaintext, &[])?;
182
183        // Compute HMAC over IV + ciphertext
184        let mut hmac_input = Vec::with_capacity(12 + ciphertext.len());
185        hmac_input.extend_from_slice(&nonce);
186        hmac_input.extend_from_slice(&ciphertext);
187
188        let mut mac = HmacSha256::new_from_slice(&self.hmac_key)
189            .expect("HMAC key size is always valid");
190        mac.update(&hmac_input);
191        let hmac = mac.finalize().into_bytes();
192
193        // Build output: HMAC | IV | ciphertext
194        let mut output = Vec::with_capacity(32 + 12 + ciphertext.len());
195        output.extend_from_slice(&hmac);
196        output.extend_from_slice(&nonce);
197        output.extend_from_slice(&ciphertext);
198
199        Ok(output)
200    }
201
202    /// Unwrap tls-crypt protected packet
203    pub fn unwrap(&self, packet: &[u8]) -> Result<Vec<u8>> {
204        use crate::cipher::Cipher;
205        use crate::CipherSuite;
206
207        if packet.len() < 32 + 12 + 16 {
208            return Err(CryptoError::DecryptionFailed);
209        }
210
211        let (hmac, rest) = packet.split_at(32);
212        let (nonce, ciphertext) = rest.split_at(12);
213
214        // Verify HMAC first (constant-time)
215        let mut mac = HmacSha256::new_from_slice(&self.hmac_key)
216            .expect("HMAC key size is always valid");
217        mac.update(nonce);
218        mac.update(ciphertext);
219        let computed = mac.finalize().into_bytes();
220
221        if !bool::from(computed.ct_eq(hmac)) {
222            return Err(CryptoError::HmacVerificationFailed);
223        }
224
225        // Decrypt
226        let nonce: [u8; 12] = nonce.try_into().unwrap();
227        let cipher = Cipher::new(&self.cipher_key, CipherSuite::ChaCha20Poly1305);
228        cipher.decrypt(&nonce, ciphertext, &[])
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::*;
235
236    #[test]
237    fn test_hmac_auth_roundtrip() {
238        let key = [0x42u8; 32];
239        let auth = HmacAuth::from_single_key(key);
240
241        let data = b"test packet data";
242        let wrapped = auth.wrap(data);
243        let unwrapped = auth.unwrap(&wrapped).unwrap();
244
245        assert_eq!(data.as_slice(), unwrapped.as_slice());
246    }
247
248    #[test]
249    fn test_hmac_auth_tamper_detection() {
250        let key = [0x42u8; 32];
251        let auth = HmacAuth::from_single_key(key);
252
253        let mut wrapped = auth.wrap(b"test data");
254        wrapped[0] ^= 0xFF; // Tamper with HMAC
255
256        assert!(auth.unwrap(&wrapped).is_err());
257    }
258
259    #[test]
260    fn test_tls_crypt_roundtrip() {
261        let key = TlsCryptKey::new([0x42u8; 32], [0x43u8; 32]);
262
263        let plaintext = b"secret control channel data";
264        let wrapped = key.wrap(plaintext).unwrap();
265        let unwrapped = key.unwrap(&wrapped).unwrap();
266
267        assert_eq!(plaintext.as_slice(), unwrapped.as_slice());
268    }
269
270    #[test]
271    fn test_tls_crypt_tamper_detection() {
272        let key = TlsCryptKey::new([0x42u8; 32], [0x43u8; 32]);
273
274        let mut wrapped = key.wrap(b"secret data").unwrap();
275        wrapped[40] ^= 0xFF; // Tamper with ciphertext
276
277        assert!(key.unwrap(&wrapped).is_err());
278    }
279}