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