use ring::hkdf;
use crate::error::{Error, Result};
const COMPRESSED_FABRIC_INFO: &[u8] = b"CompressedFabric";
struct Len8;
impl hkdf::KeyType for Len8 {
fn len(&self) -> usize {
8
}
}
pub fn derive_compressed_fabric_id(root_public_key: &[u8; 65], fabric_id: u64) -> Result<[u8; 8]> {
let salt_bytes = fabric_id.to_be_bytes();
let ikm = &root_public_key[1..];
let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &salt_bytes).extract(ikm);
let okm = prk
.expand(&[COMPRESSED_FABRIC_INFO], Len8)
.map_err(|_| Error::KeyDerivationFailed)?;
let mut out = [0u8; 8];
okm.fill(&mut out).map_err(|_| Error::KeyDerivationFailed)?;
Ok(out)
}
const GROUP_KEY_INFO: &[u8] = b"GroupKey v1.0";
const GROUP_SESSION_ID_INFO: &[u8] = b"GroupKeyHash";
struct Len2;
impl hkdf::KeyType for Len2 {
fn len(&self) -> usize {
2
}
}
struct Len16;
impl hkdf::KeyType for Len16 {
fn len(&self) -> usize {
16
}
}
pub fn derive_operational_ipk(
epoch_key: &[u8; 16],
compressed_fabric_id: &[u8; 8],
) -> Result<[u8; 16]> {
let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, compressed_fabric_id).extract(epoch_key);
let okm = prk
.expand(&[GROUP_KEY_INFO], Len16)
.map_err(|_| Error::KeyDerivationFailed)?;
let mut out = [0u8; 16];
okm.fill(&mut out).map_err(|_| Error::KeyDerivationFailed)?;
Ok(out)
}
pub fn derive_group_session_id(operational_group_key: &[u8; 16]) -> Result<u16> {
let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(operational_group_key);
let okm = prk
.expand(&[GROUP_SESSION_ID_INFO], Len2)
.map_err(|_| Error::KeyDerivationFailed)?;
let mut out = [0u8; 2];
okm.fill(&mut out).map_err(|_| Error::KeyDerivationFailed)?;
Ok(u16::from_be_bytes(out))
}
const GROUP_PRIVACY_KEY_INFO: &[u8] = b"PrivacyKey";
pub fn derive_group_privacy_key(operational_group_key: &[u8; 16]) -> Result<[u8; 16]> {
let prk = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]).extract(operational_group_key);
let okm = prk
.expand(&[GROUP_PRIVACY_KEY_INFO], Len16)
.map_err(|_| Error::KeyDerivationFailed)?;
let mut out = [0u8; 16];
okm.fill(&mut out).map_err(|_| Error::KeyDerivationFailed)?;
Ok(out)
}
#[must_use]
pub fn group_multicast_ipv6(fabric_id: u64, group_id: u16) -> std::net::Ipv6Addr {
let fabric_bytes = fabric_id.to_be_bytes();
let mut b = [0u8; 16];
b[0] = 0xff;
b[1] = 0x35; b[2] = 0x00;
b[3] = 0x40; b[4] = 0xfd; b[5..13].copy_from_slice(&fabric_bytes); b[13] = 0x00;
b[14..16].copy_from_slice(&group_id.to_be_bytes());
std::net::Ipv6Addr::from(b)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
use super::*;
#[test]
fn compressed_fabric_id_matches_spec_vector() {
let mut root_pub = [0u8; 65];
let hex = "044a9f42b1ca4840d37292bbc7f6a7e11e22200c976fc900dbc98a7a383a641cb8254a2e56d4e295a847943b4e3897c4a773e930277b4d9fbede8a052686bfacfa";
for (i, byte) in root_pub.iter_mut().enumerate() {
*byte = u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).unwrap();
}
let fabric_id: u64 = 0x2906_C908_D115_D362;
let got = derive_compressed_fabric_id(&root_pub, fabric_id).unwrap();
assert_eq!(got, [0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30]);
}
#[test]
fn group_session_id_matches_vector() {
let op_key_1: [u8; 16] = [
0x1f, 0x19, 0xed, 0x3c, 0xef, 0x8a, 0x21, 0x1b, 0xaf, 0x30, 0x6f, 0xae, 0xee, 0xe7,
0xaa, 0xc6,
];
assert_eq!(derive_group_session_id(&op_key_1).unwrap(), 0x6c80u16);
let op_key_2: [u8; 16] = [
0xaa, 0x97, 0x9a, 0x48, 0xbd, 0x8c, 0xdf, 0x29, 0x3a, 0x07, 0x09, 0xb9, 0xc1, 0xeb,
0x19, 0x30,
];
assert_eq!(derive_group_session_id(&op_key_2).unwrap(), 0x0c48u16);
let op_key_anchor: [u8; 16] = [
0xda, 0xee, 0x42, 0x4f, 0x43, 0x46, 0x77, 0xaf, 0xb5, 0x94, 0x97, 0x06, 0x57, 0x2b,
0x4c, 0xcb,
];
assert_eq!(derive_group_session_id(&op_key_anchor).unwrap(), 0xb13bu16);
}
#[test]
fn operational_ipk_matches_matter_js() {
let epoch_key: [u8; 16] = [
0x23, 0x5b, 0xf7, 0xe6, 0x28, 0x23, 0xd3, 0x58, 0xdc, 0xf7, 0x46, 0xbf, 0xa7, 0x54,
0x1c, 0xf2,
];
let cfid: [u8; 8] = [0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30];
let got = derive_operational_ipk(&epoch_key, &cfid).unwrap();
let expected: [u8; 16] = [
0xda, 0xee, 0x42, 0x4f, 0x43, 0x46, 0x77, 0xaf, 0xb5, 0x94, 0x97, 0x06, 0x57, 0x2b,
0x4c, 0xcb,
];
assert_eq!(got, expected);
}
#[test]
fn group_multicast_ipv6_matches_vector() {
use std::net::Ipv6Addr;
let got = group_multicast_ipv6(0xa1a2_a4a8_b1b2_b4b8_u64, 0xe10f);
let expected: Ipv6Addr = "ff35:0040:fda1:a2a4:a8b1:b2b4:b800:e10f".parse().unwrap();
assert_eq!(got, expected);
let got2 = group_multicast_ipv6(0x2906_C908_D115_D362u64, 0x0007);
let expected2: Ipv6Addr = "ff35:0040:fd29:06c9:08d1:15d3:6200:0007".parse().unwrap();
assert_eq!(got2, expected2);
}
#[test]
fn group_privacy_key_matches_chip_vectors() {
let op_key_1: [u8; 16] = [
0x1f, 0x19, 0xed, 0x3c, 0xef, 0x8a, 0x21, 0x1b, 0xaf, 0x30, 0x6f, 0xae, 0xee, 0xe7,
0xaa, 0xc6,
];
let expected_1: [u8; 16] = [
0xb8, 0x27, 0x9f, 0x89, 0x62, 0x1e, 0xd3, 0x27, 0xa9, 0xc3, 0x9f, 0x6a, 0x27, 0x24,
0x73, 0x58,
];
assert_eq!(derive_group_privacy_key(&op_key_1).unwrap(), expected_1);
let op_key_2: [u8; 16] = [
0xaa, 0x97, 0x9a, 0x48, 0xbd, 0x8c, 0xdf, 0x29, 0x3a, 0x07, 0x09, 0xb9, 0xc1, 0xeb,
0x19, 0x30,
];
let expected_2: [u8; 16] = [
0xf7, 0x25, 0x70, 0xc3, 0xc0, 0x89, 0xa0, 0xfe, 0x28, 0x75, 0x83, 0x57, 0xaf, 0xff,
0xb8, 0xd2,
];
assert_eq!(derive_group_privacy_key(&op_key_2).unwrap(), expected_2);
let op_key_3: [u8; 16] = [
0xa6, 0xf5, 0x30, 0x6b, 0xaf, 0x6d, 0x05, 0x0a, 0xf2, 0x3b, 0xa4, 0xbd, 0x6b, 0x9d,
0xd9, 0x60,
];
let expected_3: [u8; 16] = [
0x01, 0xf8, 0xd1, 0x92, 0x71, 0x26, 0xf1, 0x94, 0x08, 0x25, 0x72, 0xd4, 0x9b, 0x1f,
0xdc, 0x73,
];
assert_eq!(derive_group_privacy_key(&op_key_3).unwrap(), expected_3);
}
}