hive_btle/security/
signed_payload.rs1#[cfg(not(feature = "std"))]
57use alloc::vec::Vec;
58
59use super::identity::{verify_signature, DeviceIdentity};
60
61pub const SIGNATURE_SIZE: usize = 64;
63
64pub const MIN_WIRE_SIZE: usize = 1 + SIGNATURE_SIZE;
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct DecodedPayload<'a> {
70 pub marker: u8,
72 pub payload: &'a [u8],
74 pub signature: &'a [u8; 64],
76}
77
78pub struct SignedPayload;
83
84impl SignedPayload {
85 pub fn encode(marker: u8, payload: &[u8], identity: &DeviceIdentity) -> Vec<u8> {
100 let mut to_sign = Vec::with_capacity(1 + payload.len());
102 to_sign.push(marker);
103 to_sign.extend_from_slice(payload);
104
105 let signature = identity.sign(&to_sign);
107
108 let mut wire = Vec::with_capacity(1 + payload.len() + SIGNATURE_SIZE);
110 wire.push(marker);
111 wire.extend_from_slice(payload);
112 wire.extend_from_slice(&signature);
113
114 wire
115 }
116
117 pub fn encode_with_signature(marker: u8, payload: &[u8], signature: &[u8; 64]) -> Vec<u8> {
126 let mut wire = Vec::with_capacity(1 + payload.len() + SIGNATURE_SIZE);
127 wire.push(marker);
128 wire.extend_from_slice(payload);
129 wire.extend_from_slice(signature);
130 wire
131 }
132
133 pub fn decode(wire: &[u8]) -> Option<DecodedPayload<'_>> {
144 if wire.len() < MIN_WIRE_SIZE {
145 return None;
146 }
147
148 let marker = wire[0];
149 let payload_end = wire.len() - SIGNATURE_SIZE;
150 let payload = &wire[1..payload_end];
151
152 let signature: &[u8; 64] = wire[payload_end..].try_into().ok()?;
154
155 Some(DecodedPayload {
156 marker,
157 payload,
158 signature,
159 })
160 }
161
162 pub fn verify(wire: &[u8], public_key: &[u8; 32]) -> bool {
173 let Some(decoded) = Self::decode(wire) else {
174 return false;
175 };
176
177 let signed_len = wire.len() - SIGNATURE_SIZE;
179 let to_verify = &wire[..signed_len];
180
181 verify_signature(public_key, to_verify, decoded.signature)
182 }
183
184 pub fn decode_verified<'a>(
196 wire: &'a [u8],
197 public_key: &[u8; 32],
198 ) -> Option<DecodedPayload<'a>> {
199 if !Self::verify(wire, public_key) {
200 return None;
201 }
202 Self::decode(wire)
203 }
204
205 #[inline]
209 pub const fn payload_size(wire_size: usize) -> usize {
210 wire_size.saturating_sub(MIN_WIRE_SIZE)
211 }
212
213 #[inline]
217 pub const fn wire_size(payload_size: usize) -> usize {
218 1 + payload_size + SIGNATURE_SIZE
219 }
220
221 #[inline]
225 pub fn peek_marker(wire: &[u8]) -> Option<u8> {
226 wire.first().copied()
227 }
228
229 pub fn extract_signature(wire: &[u8]) -> Option<&[u8; 64]> {
233 if wire.len() < MIN_WIRE_SIZE {
234 return None;
235 }
236 let sig_start = wire.len() - SIGNATURE_SIZE;
237 wire[sig_start..].try_into().ok()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 #[test]
246 fn test_encode_decode_roundtrip() {
247 let identity = DeviceIdentity::generate();
248 let marker = 0xAF;
249 let payload = [0x01, 0x02, 0x03, 0x04, 0x05];
250
251 let wire = SignedPayload::encode(marker, &payload, &identity);
252
253 assert_eq!(wire.len(), SignedPayload::wire_size(payload.len()));
255 assert_eq!(wire.len(), 1 + 5 + 64);
256
257 let decoded = SignedPayload::decode(&wire).unwrap();
259 assert_eq!(decoded.marker, marker);
260 assert_eq!(decoded.payload, &payload);
261 }
262
263 #[test]
264 fn test_verify_valid_signature() {
265 let identity = DeviceIdentity::generate();
266 let marker = 0xAF;
267 let payload = b"Hello, mesh!";
268
269 let wire = SignedPayload::encode(marker, payload, &identity);
270 let pubkey = identity.public_key();
271
272 assert!(SignedPayload::verify(&wire, &pubkey));
273 }
274
275 #[test]
276 fn test_verify_wrong_pubkey() {
277 let identity1 = DeviceIdentity::generate();
278 let identity2 = DeviceIdentity::generate();
279
280 let wire = SignedPayload::encode(0xAF, b"test", &identity1);
281 let wrong_pubkey = identity2.public_key();
282
283 assert!(!SignedPayload::verify(&wire, &wrong_pubkey));
284 }
285
286 #[test]
287 fn test_verify_tampered_payload() {
288 let identity = DeviceIdentity::generate();
289 let mut wire = SignedPayload::encode(0xAF, b"original", &identity);
290 let pubkey = identity.public_key();
291
292 wire[1] ^= 0xFF;
294
295 assert!(!SignedPayload::verify(&wire, &pubkey));
296 }
297
298 #[test]
299 fn test_verify_tampered_marker() {
300 let identity = DeviceIdentity::generate();
301 let mut wire = SignedPayload::encode(0xAF, b"test", &identity);
302 let pubkey = identity.public_key();
303
304 wire[0] = 0xBF;
306
307 assert!(!SignedPayload::verify(&wire, &pubkey));
308 }
309
310 #[test]
311 fn test_decode_verified() {
312 let identity = DeviceIdentity::generate();
313 let marker = 0xAF;
314 let payload = b"verified content";
315
316 let wire = SignedPayload::encode(marker, payload, &identity);
317 let pubkey = identity.public_key();
318
319 let decoded = SignedPayload::decode_verified(&wire, &pubkey).unwrap();
320 assert_eq!(decoded.marker, marker);
321 assert_eq!(decoded.payload, payload);
322 }
323
324 #[test]
325 fn test_decode_verified_fails_bad_sig() {
326 let identity1 = DeviceIdentity::generate();
327 let identity2 = DeviceIdentity::generate();
328
329 let wire = SignedPayload::encode(0xAF, b"test", &identity1);
330 let wrong_pubkey = identity2.public_key();
331
332 assert!(SignedPayload::decode_verified(&wire, &wrong_pubkey).is_none());
333 }
334
335 #[test]
336 fn test_empty_payload() {
337 let identity = DeviceIdentity::generate();
338 let marker = 0x00;
339 let payload: &[u8] = &[];
340
341 let wire = SignedPayload::encode(marker, payload, &identity);
342 assert_eq!(wire.len(), MIN_WIRE_SIZE);
343
344 let decoded = SignedPayload::decode(&wire).unwrap();
345 assert_eq!(decoded.marker, marker);
346 assert!(decoded.payload.is_empty());
347
348 assert!(SignedPayload::verify(&wire, &identity.public_key()));
349 }
350
351 #[test]
352 fn test_peek_marker() {
353 let identity = DeviceIdentity::generate();
354 let wire = SignedPayload::encode(0xAB, b"test", &identity);
355
356 assert_eq!(SignedPayload::peek_marker(&wire), Some(0xAB));
357 assert_eq!(SignedPayload::peek_marker(&[]), None);
358 }
359
360 #[test]
361 fn test_extract_signature() {
362 let identity = DeviceIdentity::generate();
363 let wire = SignedPayload::encode(0xAF, b"test", &identity);
364
365 let sig = SignedPayload::extract_signature(&wire).unwrap();
366 assert_eq!(sig.len(), 64);
367
368 assert!(SignedPayload::extract_signature(&[0x01; 10]).is_none());
370 }
371
372 #[test]
373 fn test_encode_with_signature() {
374 let identity = DeviceIdentity::generate();
375 let marker = 0xAF;
376 let payload = b"external sig";
377
378 let mut to_sign = Vec::new();
380 to_sign.push(marker);
381 to_sign.extend_from_slice(payload);
382 let signature = identity.sign(&to_sign);
383
384 let wire = SignedPayload::encode_with_signature(marker, payload, &signature);
386
387 assert!(SignedPayload::verify(&wire, &identity.public_key()));
389 }
390
391 #[test]
392 fn test_wire_size_calculation() {
393 assert_eq!(SignedPayload::wire_size(0), 65);
394 assert_eq!(SignedPayload::wire_size(21), 86); assert_eq!(SignedPayload::wire_size(100), 165);
396
397 assert_eq!(SignedPayload::payload_size(65), 0);
398 assert_eq!(SignedPayload::payload_size(86), 21);
399 assert_eq!(SignedPayload::payload_size(165), 100);
400 }
401
402 #[test]
403 fn test_canned_message_size() {
404 let payload_size = 1 + 4 + 4 + 8 + 4; assert_eq!(SignedPayload::wire_size(payload_size), 86);
408 }
409}