hive_btle/discovery/
encrypted_beacon.rs1#[cfg(not(feature = "std"))]
60use alloc::vec::Vec;
61
62use crate::NodeId;
63
64pub const ENCRYPTED_BEACON_VERSION: u8 = 0x02;
66
67pub const ENCRYPTED_BEACON_SIZE: usize = 21;
69
70const ENCRYPTED_IDENTITY_SIZE: usize = 8;
72
73const MAC_SIZE: usize = 4;
75
76const NONCE_SIZE: usize = 4;
78
79#[derive(Clone)]
81pub struct BeaconKey {
82 key: [u8; 32],
84}
85
86impl BeaconKey {
87 pub fn from_base(base: &[u8; 32]) -> Self {
89 Self { key: *base }
90 }
91
92 #[cfg(test)]
94 pub fn as_bytes(&self) -> &[u8; 32] {
95 &self.key
96 }
97
98 fn derive_keystream(&self, nonce: &[u8; NONCE_SIZE]) -> [u8; ENCRYPTED_IDENTITY_SIZE] {
100 let mut input = [0u8; 36];
102 input[..32].copy_from_slice(&self.key);
103 input[32..].copy_from_slice(nonce);
104
105 let hash = blake3::hash(&input);
106 let mut keystream = [0u8; ENCRYPTED_IDENTITY_SIZE];
107 keystream.copy_from_slice(&hash.as_bytes()[..ENCRYPTED_IDENTITY_SIZE]);
108 keystream
109 }
110
111 fn compute_mac(
113 &self,
114 nonce: &[u8; NONCE_SIZE],
115 encrypted: &[u8; ENCRYPTED_IDENTITY_SIZE],
116 ) -> [u8; MAC_SIZE] {
117 let hash = blake3::keyed_hash(
118 &self.key,
119 &[nonce.as_slice(), encrypted.as_slice()].concat(),
120 );
121 let mut mac = [0u8; MAC_SIZE];
122 mac.copy_from_slice(&hash.as_bytes()[..MAC_SIZE]);
123 mac
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
132pub struct EncryptedBeacon {
133 pub node_id: NodeId,
135
136 pub capabilities: u16,
138
139 pub hierarchy_level: u8,
141
142 pub battery_percent: u8,
144}
145
146impl EncryptedBeacon {
147 pub fn new(
149 node_id: NodeId,
150 capabilities: u16,
151 hierarchy_level: u8,
152 battery_percent: u8,
153 ) -> Self {
154 Self {
155 node_id,
156 capabilities,
157 hierarchy_level,
158 battery_percent,
159 }
160 }
161
162 pub fn encrypt(&self, key: &BeaconKey, mesh_id_bytes: &[u8; 4]) -> Vec<u8> {
171 let mut buf = Vec::with_capacity(ENCRYPTED_BEACON_SIZE);
172
173 buf.push(ENCRYPTED_BEACON_VERSION);
175
176 let mut nonce = [0u8; NONCE_SIZE];
178 rand_core::OsRng.fill_bytes(&mut nonce);
179 buf.extend_from_slice(&nonce);
180
181 let mut plaintext = [0u8; ENCRYPTED_IDENTITY_SIZE];
183 plaintext[..4].copy_from_slice(mesh_id_bytes);
184 plaintext[4..].copy_from_slice(&self.node_id.as_u32().to_be_bytes());
185
186 let keystream = key.derive_keystream(&nonce);
188 let mut encrypted = [0u8; ENCRYPTED_IDENTITY_SIZE];
189 for i in 0..ENCRYPTED_IDENTITY_SIZE {
190 encrypted[i] = plaintext[i] ^ keystream[i];
191 }
192 buf.extend_from_slice(&encrypted);
193
194 let mac = key.compute_mac(&nonce, &encrypted);
196 buf.extend_from_slice(&mac);
197
198 buf.extend_from_slice(&self.capabilities.to_be_bytes());
200 buf.push(self.hierarchy_level);
201 buf.push(self.battery_percent);
202
203 buf
204 }
205
206 pub fn decrypt(data: &[u8], key: &BeaconKey) -> Option<(Self, [u8; 4])> {
216 if data.len() < ENCRYPTED_BEACON_SIZE {
217 return None;
218 }
219
220 if data[0] != ENCRYPTED_BEACON_VERSION {
222 return None;
223 }
224
225 let mut nonce = [0u8; NONCE_SIZE];
227 nonce.copy_from_slice(&data[1..5]);
228
229 let mut encrypted = [0u8; ENCRYPTED_IDENTITY_SIZE];
230 encrypted.copy_from_slice(&data[5..13]);
231
232 let mut received_mac = [0u8; MAC_SIZE];
233 received_mac.copy_from_slice(&data[13..17]);
234
235 let expected_mac = key.compute_mac(&nonce, &encrypted);
237 if received_mac != expected_mac {
238 return None;
239 }
240
241 let keystream = key.derive_keystream(&nonce);
243 let mut plaintext = [0u8; ENCRYPTED_IDENTITY_SIZE];
244 for i in 0..ENCRYPTED_IDENTITY_SIZE {
245 plaintext[i] = encrypted[i] ^ keystream[i];
246 }
247
248 let mut mesh_id_bytes = [0u8; 4];
250 mesh_id_bytes.copy_from_slice(&plaintext[..4]);
251
252 let node_id = NodeId::new(u32::from_be_bytes([
253 plaintext[4],
254 plaintext[5],
255 plaintext[6],
256 plaintext[7],
257 ]));
258
259 let capabilities = u16::from_be_bytes([data[17], data[18]]);
261 let hierarchy_level = data[19];
262 let battery_percent = data[20];
263
264 Some((
265 Self {
266 node_id,
267 capabilities,
268 hierarchy_level,
269 battery_percent,
270 },
271 mesh_id_bytes,
272 ))
273 }
274
275 pub fn is_encrypted_beacon(data: &[u8]) -> bool {
277 data.len() >= ENCRYPTED_BEACON_SIZE && data[0] == ENCRYPTED_BEACON_VERSION
278 }
279}
280
281pub fn mesh_id_to_bytes(mesh_id: &str) -> [u8; 4] {
285 let hash = blake3::hash(mesh_id.as_bytes());
286 let mut bytes = [0u8; 4];
287 bytes.copy_from_slice(&hash.as_bytes()[..4]);
288 bytes
289}
290
291pub const ENCRYPTED_DEVICE_NAME: &str = "HIVE";
293
294use rand_core::RngCore;
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_encrypt_decrypt_roundtrip() {
302 let key = BeaconKey::from_base(&[0x42; 32]);
303 let mesh_id_bytes = mesh_id_to_bytes("TEST-MESH");
304 let node_id = NodeId::new(0x12345678);
305
306 let beacon = EncryptedBeacon::new(node_id, 0x0F00, 2, 85);
307 let encrypted = beacon.encrypt(&key, &mesh_id_bytes);
308
309 assert_eq!(encrypted.len(), ENCRYPTED_BEACON_SIZE);
310 assert_eq!(encrypted[0], ENCRYPTED_BEACON_VERSION);
311
312 let (decrypted, decrypted_mesh_id) = EncryptedBeacon::decrypt(&encrypted, &key).unwrap();
313
314 assert_eq!(decrypted.node_id, node_id);
315 assert_eq!(decrypted.capabilities, 0x0F00);
316 assert_eq!(decrypted.hierarchy_level, 2);
317 assert_eq!(decrypted.battery_percent, 85);
318 assert_eq!(decrypted_mesh_id, mesh_id_bytes);
319 }
320
321 #[test]
322 fn test_wrong_key_fails() {
323 let key1 = BeaconKey::from_base(&[0x42; 32]);
324 let key2 = BeaconKey::from_base(&[0x99; 32]);
325 let mesh_id_bytes = mesh_id_to_bytes("TEST-MESH");
326 let node_id = NodeId::new(0x12345678);
327
328 let beacon = EncryptedBeacon::new(node_id, 0x0F00, 2, 85);
329 let encrypted = beacon.encrypt(&key1, &mesh_id_bytes);
330
331 assert!(EncryptedBeacon::decrypt(&encrypted, &key2).is_none());
333 }
334
335 #[test]
336 fn test_tampered_data_fails() {
337 let key = BeaconKey::from_base(&[0x42; 32]);
338 let mesh_id_bytes = mesh_id_to_bytes("TEST-MESH");
339 let node_id = NodeId::new(0x12345678);
340
341 let beacon = EncryptedBeacon::new(node_id, 0x0F00, 2, 85);
342 let mut encrypted = beacon.encrypt(&key, &mesh_id_bytes);
343
344 encrypted[7] ^= 0xFF;
346
347 assert!(EncryptedBeacon::decrypt(&encrypted, &key).is_none());
349 }
350
351 #[test]
352 fn test_different_nonces_produce_different_ciphertext() {
353 let key = BeaconKey::from_base(&[0x42; 32]);
354 let mesh_id_bytes = mesh_id_to_bytes("TEST-MESH");
355 let node_id = NodeId::new(0x12345678);
356
357 let beacon = EncryptedBeacon::new(node_id, 0x0F00, 2, 85);
358 let encrypted1 = beacon.encrypt(&key, &mesh_id_bytes);
359 let encrypted2 = beacon.encrypt(&key, &mesh_id_bytes);
360
361 assert_ne!(&encrypted1[1..5], &encrypted2[1..5]);
363
364 assert_ne!(&encrypted1[5..13], &encrypted2[5..13]);
366
367 assert!(EncryptedBeacon::decrypt(&encrypted1, &key).is_some());
369 assert!(EncryptedBeacon::decrypt(&encrypted2, &key).is_some());
370 }
371
372 #[test]
373 fn test_is_encrypted_beacon() {
374 let key = BeaconKey::from_base(&[0x42; 32]);
375 let mesh_id_bytes = mesh_id_to_bytes("TEST-MESH");
376 let beacon = EncryptedBeacon::new(NodeId::new(1), 0, 0, 0);
377 let encrypted = beacon.encrypt(&key, &mesh_id_bytes);
378
379 assert!(EncryptedBeacon::is_encrypted_beacon(&encrypted));
380 assert!(!EncryptedBeacon::is_encrypted_beacon(&[0x01; 21])); assert!(!EncryptedBeacon::is_encrypted_beacon(&[0x02; 10])); }
383
384 #[test]
385 fn test_mesh_id_to_bytes_deterministic() {
386 let bytes1 = mesh_id_to_bytes("ALPHA-TEAM");
387 let bytes2 = mesh_id_to_bytes("ALPHA-TEAM");
388 let bytes3 = mesh_id_to_bytes("BRAVO-TEAM");
389
390 assert_eq!(bytes1, bytes2);
391 assert_ne!(bytes1, bytes3);
392 }
393}