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
10pub 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 hk.expand(&info, &mut okm)
20 .expect("HKDF expand of 32 bytes is always valid");
21 okm
22}
23
24pub 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
35pub 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 assert!(!verify_tag(&key, &header, b"hellp", &tag));
85 let mut h2 = header;
87 h2[8] ^= 0xff;
88 assert!(!verify_tag(&key, &h2, &payload, &tag));
89 let mut t2 = tag;
91 t2[0] ^= 0xff;
92 assert!(!verify_tag(&key, &header, &payload, &t2));
93 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}