nodedb_cluster/rpc_codec/
auth_envelope.rs1use crate::error::{ClusterError, Result};
31
32use super::header::MAX_RPC_PAYLOAD_SIZE;
33use super::mac::{MAC_LEN, MacKey, compute_hmac, verify_hmac};
34
35pub const ENVELOPE_VERSION: u8 = 1;
38
39pub const ENVELOPE_OVERHEAD: usize = 1 + 8 + 8 + 4 + MAC_LEN;
42
43const OFF_VERSION: usize = 0;
45const OFF_FROM_NODE: usize = 1;
46const OFF_SEQ: usize = 9;
47const OFF_INNER_LEN: usize = 17;
48const ENV_HEADER_LEN: usize = 21;
49
50#[derive(Debug, Clone, Copy)]
54pub struct EnvelopeFields {
55 pub from_node_id: u64,
56 pub seq: u64,
57}
58
59pub fn write_envelope(
63 from_node_id: u64,
64 seq: u64,
65 inner_frame: &[u8],
66 key: &MacKey,
67 out: &mut Vec<u8>,
68) -> Result<()> {
69 let inner_len: u32 = inner_frame
70 .len()
71 .try_into()
72 .map_err(|_| ClusterError::Codec {
73 detail: format!("inner frame too large: {} bytes", inner_frame.len()),
74 })?;
75 if inner_len > MAX_RPC_PAYLOAD_SIZE {
76 return Err(ClusterError::Codec {
77 detail: format!(
78 "inner frame length {inner_len} exceeds maximum {MAX_RPC_PAYLOAD_SIZE}"
79 ),
80 });
81 }
82
83 let start = out.len();
84 out.reserve(ENVELOPE_OVERHEAD + inner_frame.len());
85 out.push(ENVELOPE_VERSION);
86 out.extend_from_slice(&from_node_id.to_le_bytes());
87 out.extend_from_slice(&seq.to_le_bytes());
88 out.extend_from_slice(&inner_len.to_le_bytes());
89 out.extend_from_slice(inner_frame);
90 let tag = compute_hmac(key, &out[start..]);
91 out.extend_from_slice(&tag);
92 Ok(())
93}
94
95pub fn parse_envelope<'a>(data: &'a [u8], key: &MacKey) -> Result<(EnvelopeFields, &'a [u8])> {
100 if data.len() < ENVELOPE_OVERHEAD {
101 return Err(ClusterError::Codec {
102 detail: format!(
103 "envelope too short: {} bytes, need at least {ENVELOPE_OVERHEAD}",
104 data.len()
105 ),
106 });
107 }
108
109 let version = data[OFF_VERSION];
110 if version != ENVELOPE_VERSION {
111 return Err(ClusterError::Codec {
112 detail: format!("unsupported envelope version {version}, expected {ENVELOPE_VERSION}"),
113 });
114 }
115
116 let from_node_id = u64::from_le_bytes(data[OFF_FROM_NODE..OFF_SEQ].try_into().expect("invariant: ENVELOPE_OVERHEAD/total-length checks above guarantee field bytes within bounds"));
117 let seq = u64::from_le_bytes(data[OFF_SEQ..OFF_INNER_LEN].try_into().expect("invariant: ENVELOPE_OVERHEAD/total-length checks above guarantee field bytes within bounds"));
118 let inner_len = u32::from_le_bytes(data[OFF_INNER_LEN..ENV_HEADER_LEN].try_into().expect("invariant: ENVELOPE_OVERHEAD/total-length checks above guarantee field bytes within bounds"));
119
120 if inner_len > MAX_RPC_PAYLOAD_SIZE {
121 return Err(ClusterError::Codec {
122 detail: format!(
123 "envelope inner length {inner_len} exceeds maximum {MAX_RPC_PAYLOAD_SIZE}"
124 ),
125 });
126 }
127
128 let inner_end = ENV_HEADER_LEN + inner_len as usize;
129 let expected_total = inner_end + MAC_LEN;
130 if data.len() != expected_total {
131 return Err(ClusterError::Codec {
132 detail: format!(
133 "envelope length mismatch: got {} bytes, expected {expected_total}",
134 data.len()
135 ),
136 });
137 }
138
139 let tag: &[u8; MAC_LEN] = data[inner_end..].try_into().expect("invariant: ENVELOPE_OVERHEAD/total-length checks above guarantee field bytes within bounds");
140 verify_hmac(key, &data[..inner_end], tag)?;
141
142 let inner_frame = &data[ENV_HEADER_LEN..inner_end];
143 Ok((EnvelopeFields { from_node_id, seq }, inner_frame))
144}
145
146#[cfg(test)]
147mod tests {
148 use super::*;
149 use crate::rpc_codec::header::{HEADER_SIZE, write_frame};
150
151 fn sample_inner(rpc_type: u8) -> Vec<u8> {
152 let mut out = Vec::new();
153 write_frame(rpc_type, b"payload bytes", &mut out).unwrap();
154 out
155 }
156
157 #[test]
158 fn envelope_roundtrips() {
159 let key = MacKey::from_bytes([3u8; MAC_LEN]);
160 let inner = sample_inner(0x42);
161 let mut buf = Vec::new();
162 write_envelope(7, 12345, &inner, &key, &mut buf).unwrap();
163
164 assert_eq!(buf.len(), ENVELOPE_OVERHEAD + inner.len());
165
166 let (fields, parsed_inner) = parse_envelope(&buf, &key).unwrap();
167 assert_eq!(fields.from_node_id, 7);
168 assert_eq!(fields.seq, 12345);
169 assert_eq!(parsed_inner, inner.as_slice());
170 assert!(parsed_inner.len() >= HEADER_SIZE);
171 }
172
173 #[test]
174 fn rejects_unknown_envelope_version() {
175 let key = MacKey::from_bytes([3u8; MAC_LEN]);
176 let inner = sample_inner(1);
177 let mut buf = Vec::new();
178 write_envelope(1, 1, &inner, &key, &mut buf).unwrap();
179 buf[OFF_VERSION] = 99;
180 let err = parse_envelope(&buf, &key).unwrap_err();
181 assert!(err.to_string().contains("envelope version"));
182 }
183
184 #[test]
185 fn rejects_tampered_from_node_id() {
186 let key = MacKey::from_bytes([3u8; MAC_LEN]);
187 let inner = sample_inner(1);
188 let mut buf = Vec::new();
189 write_envelope(7, 42, &inner, &key, &mut buf).unwrap();
190 buf[OFF_FROM_NODE] ^= 0x01;
192 let err = parse_envelope(&buf, &key).unwrap_err();
193 assert!(err.to_string().contains("MAC verification failed"));
194 }
195
196 #[test]
197 fn rejects_tampered_seq() {
198 let key = MacKey::from_bytes([3u8; MAC_LEN]);
199 let inner = sample_inner(1);
200 let mut buf = Vec::new();
201 write_envelope(1, 100, &inner, &key, &mut buf).unwrap();
202 buf[OFF_SEQ] ^= 0xFF;
203 let err = parse_envelope(&buf, &key).unwrap_err();
204 assert!(err.to_string().contains("MAC verification failed"));
205 }
206
207 #[test]
208 fn rejects_tampered_inner() {
209 let key = MacKey::from_bytes([3u8; MAC_LEN]);
210 let inner = sample_inner(1);
211 let mut buf = Vec::new();
212 write_envelope(1, 1, &inner, &key, &mut buf).unwrap();
213 buf[ENV_HEADER_LEN + HEADER_SIZE] ^= 0x80;
215 let err = parse_envelope(&buf, &key).unwrap_err();
216 assert!(err.to_string().contains("MAC verification failed"));
217 }
218
219 #[test]
220 fn rejects_wrong_key() {
221 let k1 = MacKey::from_bytes([1u8; MAC_LEN]);
222 let k2 = MacKey::from_bytes([2u8; MAC_LEN]);
223 let inner = sample_inner(1);
224 let mut buf = Vec::new();
225 write_envelope(1, 1, &inner, &k1, &mut buf).unwrap();
226 let err = parse_envelope(&buf, &k2).unwrap_err();
227 assert!(err.to_string().contains("MAC verification failed"));
228 }
229
230 #[test]
231 fn rejects_truncated_envelope() {
232 let key = MacKey::from_bytes([3u8; MAC_LEN]);
233 let inner = sample_inner(1);
234 let mut buf = Vec::new();
235 write_envelope(1, 1, &inner, &key, &mut buf).unwrap();
236 buf.truncate(buf.len() - 1);
237 let err = parse_envelope(&buf, &key).unwrap_err();
238 let msg = err.to_string();
239 assert!(msg.contains("envelope length mismatch") || msg.contains("envelope too short"));
240 }
241}