use panweave_security::ccm;
use panweave_security::cipher::BlockCipher;
use panweave_types::{ExtendedAddress, Key128};
pub const HEADER_LEN: usize = 4;
pub const MIC_LEN: usize = 4;
pub const UNIQUE_ADDRESS_LEN: usize = 34;
pub const SECURITY_CONTROL: u8 = 0x05;
pub type Uuid = [u8; 16];
pub const BASE_UUID: Uuid = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB,
];
pub const fn expand_uuid16(uuid: u16) -> Uuid {
let mut u = BASE_UUID;
u[2] = (uuid >> 8) as u8;
u[3] = (uuid & 0xff) as u8;
u
}
pub fn unique_address(service: &Uuid, characteristic: &Uuid) -> [u8; UNIQUE_ADDRESS_LEN] {
let mut a = [0u8; UNIQUE_ADDRESS_LEN];
a[..16].copy_from_slice(service);
a[16] = 0;
a[17..33].copy_from_slice(characteristic);
a[33] = 0;
a
}
pub fn nonce(source: ExtendedAddress, counter: u32) -> [u8; 13] {
let mut n = [0u8; 13];
n[..8].copy_from_slice(&source.0.to_le_bytes());
n[8..12].copy_from_slice(&counter.to_le_bytes());
n[12] = SECURITY_CONTROL;
n
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SecureError {
Stale,
AuthFailed,
Malformed,
CounterExhausted,
}
#[derive(Clone, Debug)]
pub struct SecureChannel {
key: Key128,
local: ExtendedAddress,
remote: ExtendedAddress,
outgoing: u32,
incoming: u32,
}
impl SecureChannel {
pub const fn new(key: Key128, local: ExtendedAddress, remote: ExtendedAddress) -> Self {
SecureChannel {
key,
local,
remote,
outgoing: 0,
incoming: 0,
}
}
pub const fn incoming(&self) -> u32 {
self.incoming
}
pub const fn outgoing(&self) -> u32 {
self.outgoing
}
pub fn protect<C: BlockCipher>(
&mut self,
unique_address: &[u8; UNIQUE_ADDRESS_LEN],
plaintext: &[u8],
out: &mut [u8],
) -> Result<usize, SecureError> {
if self.outgoing == u32::MAX {
return Err(SecureError::CounterExhausted);
}
let total = HEADER_LEN + plaintext.len() + MIC_LEN;
if out.len() < total {
return Err(SecureError::Malformed);
}
let counter = self.outgoing + 1;
let n = nonce(self.local, counter);
let cipher = C::new(&self.key);
let (head, rest) = out.split_at_mut(HEADER_LEN);
head.copy_from_slice(&counter.to_le_bytes());
let (body, tail) = rest.split_at_mut(plaintext.len());
body.copy_from_slice(plaintext);
let mic = tail.get_mut(..MIC_LEN).ok_or(SecureError::Malformed)?;
ccm::encrypt_in_place(&cipher, &n, unique_address, body, mic)
.map_err(|_| SecureError::Malformed)?;
self.outgoing = counter;
Ok(total)
}
pub fn unprotect<'a, C: BlockCipher>(
&mut self,
unique_address: &[u8; UNIQUE_ADDRESS_LEN],
frame: &'a mut [u8],
) -> Result<&'a [u8], SecureError> {
if frame.len() < HEADER_LEN + MIC_LEN {
return Err(SecureError::Malformed);
}
let mut c = [0u8; 4];
c.copy_from_slice(frame.get(..HEADER_LEN).ok_or(SecureError::Malformed)?);
let counter = u32::from_le_bytes(c);
if counter <= self.incoming {
return Err(SecureError::Stale);
}
let n = nonce(self.remote, counter);
let cipher = C::new(&self.key);
let body_len = frame.len() - HEADER_LEN - MIC_LEN;
let (_, rest) = frame.split_at_mut(HEADER_LEN);
let (body, mic) = rest.split_at_mut(body_len);
let mut tag = [0u8; MIC_LEN];
tag.copy_from_slice(mic.get(..MIC_LEN).ok_or(SecureError::Malformed)?);
ccm::decrypt_in_place(&cipher, &n, unique_address, body, &tag)
.map_err(|_| SecureError::AuthFailed)?;
self.incoming = counter;
Ok(body)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::gatt;
use panweave_security::cipher::SoftwareAes;
#[test]
fn annex_b4_join_network_write() {
let zvd = ExtendedAddress(0x001F_EE00_0000_0001);
let zdd = ExtendedAddress(0x001F_EE00_0000_0002);
let key = Key128::from_bytes([
0xa2, 0xe2, 0xb6, 0x1b, 0xf1, 0xa8, 0x05, 0x81, 0xd0, 0x7f, 0x1f, 0x06, 0x72, 0xd1,
0xd6, 0x24,
]);
let plaintext: [u8; 42] = [
0x07, 0x00, 0x03, 0x00, 0x07, 0xd9, 0x1f, 0x00, 0x00, 0x00, 0xae, 0x1f, 0x00, 0x01,
0x01, 0x26, 0x90, 0x02, 0x03, 0x00, 0x00, 0x01, 0x00, 0x03, 0x0f, 0x5c, 0xd2, 0xf8,
0xbe, 0xd4, 0xea, 0x50, 0x56, 0xac, 0x02, 0xa8, 0xee, 0x84, 0x1a, 0xa0, 0x86, 0x06,
];
let plaintext_full: [u8; 45] = {
let mut p = [0u8; 45];
p[..42].copy_from_slice(&plaintext);
p[42..].copy_from_slice(&[0x01, 0xcd, 0xab]);
p
};
let aad = unique_address(
&gatt::COMMISSIONING_SERVICE,
&gatt::commissioning::JOIN_NETWORK,
);
assert_eq!(
&aad[..17],
&[
0x00, 0x00, 0xff, 0xf7, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b,
0x34, 0xfb, 0x00
]
);
assert_eq!(
&aad[17..],
&[
0x70, 0x72, 0x37, 0x7d, 0x00, 0x02, 0x42, 0x1c, 0xb1, 0x63, 0x49, 0x1c, 0x27, 0x33,
0x3a, 0x61, 0x00
]
);
assert_eq!(
nonce(zvd, 2),
[
0x01, 0x00, 0x00, 0x00, 0x00, 0xee, 0x1f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x05
]
);
let mut tx = SecureChannel::new(key.clone(), zvd, zdd);
tx.outgoing = 1;
let mut out = [0u8; 64];
let n = tx
.protect::<SoftwareAes>(&aad, &plaintext_full, &mut out)
.unwrap();
assert_eq!(n, 4 + 45 + 4);
assert_eq!(&out[..4], &[0x02, 0x00, 0x00, 0x00]);
assert_eq!(
&out[4..49],
&[
0x23, 0xb7, 0x11, 0xa6, 0x91, 0x6a, 0xba, 0x32, 0x82, 0xb3, 0x7a, 0x7c, 0xb4, 0x4f,
0x30, 0x18, 0x7c, 0x43, 0xc2, 0x45, 0xd3, 0xbd, 0x6f, 0xf1, 0xaf, 0xe5, 0x9d, 0x0b,
0x3a, 0x9b, 0x0f, 0xda, 0xc5, 0x26, 0xd3, 0x63, 0x3d, 0xae, 0xdf, 0x73, 0x5d, 0x78,
0x35, 0xfe, 0x9f
]
);
assert_eq!(&out[49..53], &[0x3f, 0x12, 0xbd, 0xf8]);
let mut rx = SecureChannel::new(key, zdd, zvd);
let mut frame = out;
let plain = rx.unprotect::<SoftwareAes>(&aad, &mut frame[..n]).unwrap();
assert_eq!(plain, &plaintext_full);
assert_eq!(rx.incoming(), 2);
let mut frame = out;
assert_eq!(
rx.unprotect::<SoftwareAes>(&aad, &mut frame[..n]),
Err(SecureError::Stale)
);
let mut rx = SecureChannel::new(
Key128::from_bytes([
0xa2, 0xe2, 0xb6, 0x1b, 0xf1, 0xa8, 0x05, 0x81, 0xd0, 0x7f, 0x1f, 0x06, 0x72, 0xd1,
0xd6, 0x24,
]),
zdd,
zvd,
);
let mut frame = out;
frame[50] ^= 1;
assert_eq!(
rx.unprotect::<SoftwareAes>(&aad, &mut frame[..n]),
Err(SecureError::AuthFailed)
);
assert_eq!(rx.incoming(), 0);
}
}