use std::sync::Arc;
use matter_cert::MatterCertificate;
use matter_commissioning::FabricRecord;
use matter_crypto::{RingSigner, Signer};
use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct GroupKeySetConfig {
pub key_set_id: u16,
pub epoch_key: [u8; 16],
pub epoch_start_time: u64,
}
impl GroupKeySetConfig {
#[must_use]
pub fn new(key_set_id: u16, epoch_key: [u8; 16], epoch_start_time: u64) -> Self {
Self {
key_set_id,
epoch_key,
epoch_start_time,
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct IcacIdentity {
pub cert: MatterCertificate,
pub pkcs8: Vec<u8>,
}
impl std::fmt::Debug for IcacIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IcacIdentity")
.field("cert", &"<MatterCertificate>")
.field("pkcs8", &"<redacted PKCS#8>")
.finish()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct DeviceEntry {
pub node_id: u64,
pub peer_noc_public_key: [u8; 65],
pub resumption_record: Option<Vec<u8>>,
pub last_known_addr: Option<String>,
pub vendor_id: Option<u16>,
pub product_id: Option<u16>,
pub label: Option<String>,
}
impl std::fmt::Debug for DeviceEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DeviceEntry")
.field("node_id", &self.node_id)
.field("peer_noc_public_key", &self.peer_noc_public_key)
.field(
"resumption_record",
&self
.resumption_record
.as_ref()
.map(|_| "<redacted; CASE secret>"),
)
.field("last_known_addr", &self.last_known_addr)
.field("vendor_id", &self.vendor_id)
.field("product_id", &self.product_id)
.field("label", &self.label)
.finish()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct CommissionerIdentity {
pub node_id: u64,
pub operational_pkcs8: Vec<u8>,
pub noc: MatterCertificate,
}
impl std::fmt::Debug for CommissionerIdentity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CommissionerIdentity")
.field("node_id", &self.node_id)
.field("operational_pkcs8", &"<redacted PKCS#8>")
.field("noc", &"<MatterCertificate>")
.finish()
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct FabricEntry {
pub fabric_id: u64,
pub ipk: [u8; 16],
pub rcac_cert: MatterCertificate,
pub rcac_pkcs8: Vec<u8>,
pub commissioner: CommissionerIdentity,
pub devices: Vec<DeviceEntry>,
pub group_keys: Vec<GroupKeySetConfig>,
pub outbound_group_counter: u32,
pub icd_clients: Vec<crate::icd::IcdRegistration>,
pub icac: Option<IcacIdentity>,
}
impl std::fmt::Debug for FabricEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FabricEntry")
.field("fabric_id", &self.fabric_id)
.field("ipk", &"<redacted; 16 bytes>")
.field("rcac_cert", &"<MatterCertificate>")
.field("rcac_pkcs8", &"<redacted PKCS#8>")
.field("commissioner", &self.commissioner)
.field("devices", &self.devices)
.field(
"group_keys",
&format!("<{} key sets>", self.group_keys.len()),
)
.field("outbound_group_counter", &self.outbound_group_counter)
.field(
"icd_clients",
&format!("<{} registrations>", self.icd_clients.len()),
)
.field("icac", &self.icac)
.finish()
}
}
impl FabricEntry {
pub fn rcac_signer(&self) -> Result<RingSigner, Error> {
RingSigner::from_pkcs8(&self.rcac_pkcs8).map_err(|e| Error::Signer(e.to_string()))
}
pub fn commissioner_signer(&self) -> Result<RingSigner, Error> {
RingSigner::from_pkcs8(&self.commissioner.operational_pkcs8)
.map_err(|e| Error::Signer(e.to_string()))
}
pub fn to_fabric_record(&self) -> Result<FabricRecord, Error> {
let signer = self.rcac_signer()?;
let root_public_key = signer.public_key().clone();
let (icac_signer, icac_cert) = match &self.icac {
Some(icac) => {
let signer = RingSigner::from_pkcs8(&icac.pkcs8)
.map_err(|e| Error::Signer(e.to_string()))?;
(
Some(Arc::new(signer) as Arc<dyn Signer>),
Some(icac.cert.clone()),
)
}
None => (None, None),
};
Ok(FabricRecord {
fabric_id: self.fabric_id,
root_public_key,
root_signer: Arc::new(signer) as Arc<dyn Signer>,
root_cert: self.rcac_cert.clone(),
icac_signer,
icac_cert,
identity_protection_key: self.ipk,
})
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct ControllerState {
pub fabrics: Vec<FabricEntry>,
}
impl ControllerState {
#[must_use]
pub fn new(fabrics: Vec<FabricEntry>) -> Self {
Self { fabrics }
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn controller_state_new_builds_from_fabrics() {
let state = ControllerState::new(Vec::new());
assert!(state.fabrics.is_empty());
}
#[test]
fn reconstructed_signer_signs_and_verifies() {
let (signer, pkcs8) = RingSigner::generate().expect("generate");
let entry_key = pkcs8.clone();
let reloaded = RingSigner::from_pkcs8(&entry_key).expect("reload");
assert_eq!(
signer.public_key().as_bytes(),
reloaded.public_key().as_bytes()
);
let msg = b"controller identity";
let sig_bytes = reloaded.sign_p256_sha256(msg).expect("sign");
let sig = matter_cert::Signature::new(sig_bytes);
reloaded
.public_key()
.verify(msg, &sig)
.expect("reloaded signature verifies");
}
}