nodedb_cluster/rpc_codec/
mac.rs1use hmac::{Hmac, Mac};
29use sha2::Sha256;
30
31use crate::error::{ClusterError, Result};
32
33pub const MAC_LEN: usize = 32;
35
36#[derive(Clone)]
41pub struct MacKey([u8; MAC_LEN]);
42
43impl MacKey {
44 pub fn from_bytes(bytes: [u8; MAC_LEN]) -> Self {
46 Self(bytes)
47 }
48
49 pub fn random() -> Self {
51 use rand::RngCore;
52 let mut out = [0u8; MAC_LEN];
53 rand::rng().fill_bytes(&mut out);
54 Self(out)
55 }
56
57 pub fn zero() -> Self {
61 Self([0u8; MAC_LEN])
62 }
63
64 pub fn as_bytes(&self) -> &[u8; MAC_LEN] {
67 &self.0
68 }
69
70 pub fn is_zero(&self) -> bool {
73 self.0 == [0u8; MAC_LEN]
74 }
75}
76
77impl std::fmt::Debug for MacKey {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 if self.is_zero() {
81 write!(f, "MacKey(zero)")
82 } else {
83 write!(f, "MacKey(<redacted>)")
84 }
85 }
86}
87
88pub fn compute_hmac(key: &MacKey, data: &[u8]) -> [u8; MAC_LEN] {
91 let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key.as_bytes())
92 .expect("HMAC-SHA256 accepts any key length");
93 mac.update(data);
94 let out = mac.finalize().into_bytes();
95 let mut tag = [0u8; MAC_LEN];
96 tag.copy_from_slice(&out);
97 tag
98}
99
100pub fn verify_hmac(key: &MacKey, data: &[u8], tag: &[u8; MAC_LEN]) -> Result<()> {
103 let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key.as_bytes())
104 .expect("HMAC-SHA256 accepts any key length");
105 mac.update(data);
106 mac.verify_slice(tag).map_err(|_| ClusterError::Codec {
107 detail: "frame MAC verification failed".into(),
108 })
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn hmac_roundtrip() {
117 let key = MacKey::from_bytes([7u8; MAC_LEN]);
118 let tag = compute_hmac(&key, b"hello world");
119 verify_hmac(&key, b"hello world", &tag).unwrap();
120 }
121
122 #[test]
123 fn hmac_rejects_tampered_data() {
124 let key = MacKey::from_bytes([7u8; MAC_LEN]);
125 let tag = compute_hmac(&key, b"hello world");
126 let err = verify_hmac(&key, b"hello WORLD", &tag).unwrap_err();
127 assert!(err.to_string().contains("MAC verification failed"));
128 }
129
130 #[test]
131 fn hmac_rejects_wrong_key() {
132 let k1 = MacKey::from_bytes([1u8; MAC_LEN]);
133 let k2 = MacKey::from_bytes([2u8; MAC_LEN]);
134 let tag = compute_hmac(&k1, b"msg");
135 assert!(verify_hmac(&k2, b"msg", &tag).is_err());
136 }
137
138 #[test]
139 fn debug_redacts_key() {
140 let k = MacKey::from_bytes([0xAA; MAC_LEN]);
141 let s = format!("{k:?}");
142 assert!(!s.contains("aa"), "debug leaked key bytes: {s}");
143 assert!(s.contains("redacted"));
144 }
145
146 #[test]
147 fn random_keys_differ() {
148 let k1 = MacKey::random();
149 let k2 = MacKey::random();
150 assert_ne!(k1.as_bytes(), k2.as_bytes());
151 }
152
153 #[test]
154 fn zero_key_reports_zero() {
155 assert!(MacKey::zero().is_zero());
156 assert!(!MacKey::random().is_zero());
157 }
158}