Skip to main content

veyron_wire/
mac.rs

1use hkdf::Hkdf;
2use hmac::{Hmac, Mac};
3use sha2::Sha256;
4
5pub const SESSION_NONCE_LEN: usize = 16;
6pub const MAC_TAG_LEN: usize = 32;
7
8type HmacSha256 = Hmac<Sha256>;
9
10/// Derive the per-connection MAC key. Inputs are known to both the kernel and
11/// the plugin: the shared `jwt_secret`, the per-registration `nonce` from the
12/// ack, and the registered `plugin_id`.
13pub fn derive_session_key(jwt_secret: &[u8], nonce: &[u8], plugin_id: &str) -> [u8; 32] {
14    let hk = Hkdf::<Sha256>::new(Some(nonce), jwt_secret);
15    let mut info = b"veyron-frame-mac-v1|".to_vec();
16    info.extend_from_slice(plugin_id.as_bytes());
17    let mut okm = [0u8; 32];
18    // HKDF-SHA256 expand of 32 bytes never exceeds the length limit -> safe.
19    hk.expand(&info, &mut okm)
20        .expect("HKDF expand of 32 bytes is always valid");
21    okm
22}
23
24/// HMAC-SHA256 over `header || payload`.
25pub fn compute_tag(key: &[u8; 32], header: &[u8], payload: &[u8]) -> [u8; MAC_TAG_LEN] {
26    let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
27    mac.update(header);
28    mac.update(payload);
29    let out = mac.finalize().into_bytes();
30    let mut tag = [0u8; MAC_TAG_LEN];
31    tag.copy_from_slice(&out);
32    tag
33}
34
35/// Constant-time verification of a tag over `header || payload`.
36pub fn verify_tag(key: &[u8; 32], header: &[u8], payload: &[u8], tag: &[u8]) -> bool {
37    let mut mac = HmacSha256::new_from_slice(key).expect("HMAC accepts any key length");
38    mac.update(header);
39    mac.update(payload);
40    mac.verify_slice(tag).is_ok()
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn derive_is_deterministic_and_input_sensitive() {
49        let k1 = derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "plugin-a");
50        let k2 = derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "plugin-a");
51        assert_eq!(k1, k2, "same inputs -> same key");
52
53        assert_ne!(
54            k1,
55            derive_session_key(b"secret", b"nonce-bbbbbbbbbb", "plugin-a")
56        );
57        assert_ne!(
58            k1,
59            derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "plugin-b")
60        );
61        assert_ne!(
62            k1,
63            derive_session_key(b"other!", b"nonce-aaaaaaaaaa", "plugin-a")
64        );
65    }
66
67    #[test]
68    fn tag_round_trips() {
69        let key = derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "p");
70        let header = [1u8; 44];
71        let payload = b"hello".to_vec();
72        let tag = compute_tag(&key, &header, &payload);
73        assert!(verify_tag(&key, &header, &payload, &tag));
74    }
75
76    #[test]
77    fn tampering_is_detected() {
78        let key = derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "p");
79        let header = [1u8; 44];
80        let payload = b"hello".to_vec();
81        let tag = compute_tag(&key, &header, &payload);
82
83        // flipped payload
84        assert!(!verify_tag(&key, &header, b"hellp", &tag));
85        // flipped header byte
86        let mut h2 = header;
87        h2[8] ^= 0xff;
88        assert!(!verify_tag(&key, &h2, &payload, &tag));
89        // flipped tag byte
90        let mut t2 = tag;
91        t2[0] ^= 0xff;
92        assert!(!verify_tag(&key, &header, &payload, &t2));
93        // wrong key
94        let other = derive_session_key(b"secret", b"nonce-bbbbbbbbbb", "p");
95        assert!(!verify_tag(&other, &header, &payload, &tag));
96    }
97
98    #[test]
99    fn verify_rejects_wrong_length_tag() {
100        let key = derive_session_key(b"secret", b"nonce-aaaaaaaaaa", "p");
101        assert!(!verify_tag(&key, &[0u8; 44], b"x", &[0u8; 8]));
102    }
103}