use core::num::NonZeroU8;
use crate::cert::gen::Validity;
use crate::crypto::{Crypto, RngCore, AEAD_CANON_KEY_LEN};
use crate::dm::clusters::gen_comm::{CommissioningErrorEnum, GeneralCommissioningClient};
use crate::dm::clusters::noc::{NodeOperationalCertStatusEnum, OperationalCredentialsClient};
use crate::dm::endpoints::ROOT_ENDPOINT_ID;
use crate::dm::NodeId;
use crate::error::{Error, ErrorCode};
use crate::onboard::noc::NocGenerator;
use crate::sc::case::CaseInitiator;
use crate::tlv::{FromTLV, OctetStr, TLVElement};
use crate::transport::exchange::Exchange;
use crate::transport::network::Address;
use crate::Matter;
pub mod cac;
pub mod noc;
const NOCSR_TAG_CSR: u8 = 1;
const NOCSR_TAG_NONCE: u8 = 2;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CommissionOptions {
pub fail_safe_secs: u16,
pub allow_test_attestation: bool,
}
impl CommissionOptions {
pub const fn new() -> Self {
Self {
fail_safe_secs: 60,
allow_test_attestation: false,
}
}
}
impl Default for CommissionOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CommissionResult {
pub fabric_index: NonZeroU8,
pub device_node_id: NodeId,
}
pub struct Commissioner<'a, C: Crypto> {
matter: &'a Matter<'a>,
crypto: C,
fab_idx: NonZeroU8,
noc_generator: &'a mut NocGenerator<'a>,
buf: &'a mut [u8],
}
impl<'a, C: Crypto> Commissioner<'a, C> {
pub const fn new(
matter: &'a Matter<'a>,
crypto: C,
fab_idx: NonZeroU8,
noc_generator: &'a mut NocGenerator<'a>,
buf: &'a mut [u8],
) -> Self {
Self {
matter,
crypto,
fab_idx,
noc_generator,
buf,
}
}
pub const fn fab_idx(&self) -> NonZeroU8 {
self.fab_idx
}
pub async fn commission(
&mut self,
peer_addr: Address,
passcode: u32,
opts: &CommissionOptions,
device_node_id: NodeId,
validity: Validity,
) -> Result<CommissionResult, Error> {
self.arm_fail_safe(peer_addr, passcode, opts.fail_safe_secs)
.await?;
self.verify_device_attestation(opts).await?;
let mut csr_nonce = [0u8; 32];
self.crypto.rand()?.fill_bytes(&mut csr_nonce);
let matter = self.matter;
let crypto = &self.crypto;
let fab_idx = self.fab_idx;
let noc_generator = &mut *self.noc_generator;
let buf = &mut *self.buf;
let noc = Self::csr_request(matter, crypto, peer_addr, passcode, &csr_nonce, |csr_der| {
noc_generator.generate(crypto, csr_der, device_node_id, &[], validity)
})
.await?;
let rcac_len = matter.with_state(|state| {
let fabric = state.fabrics.fabric(fab_idx)?;
let rcac = fabric.root_ca();
if rcac.len() > buf.len() {
return Err(Error::from(ErrorCode::BufferTooSmall));
}
buf[..rcac.len()].copy_from_slice(rcac);
Ok::<_, Error>(rcac.len())
})?;
Self::add_trusted_root_certificate(matter, crypto, peer_addr, passcode, &buf[..rcac_len])
.await?;
let mut ipk_bytes = [0u8; AEAD_CANON_KEY_LEN];
let (icac_len, admin_node_id, admin_vendor_id) = matter.with_state(|state| {
let fabric = state.fabrics.fabric(fab_idx)?;
let icac = fabric.icac();
if icac.len() > buf.len() {
return Err(Error::from(ErrorCode::BufferTooSmall));
}
buf[..icac.len()].copy_from_slice(icac);
ipk_bytes.copy_from_slice(fabric.ipk().epoch_key().access());
Ok::<_, Error>((icac.len(), fabric.node_id(), fabric.vendor_id()))
})?;
let fabric_index = Self::add_noc(
matter,
crypto,
peer_addr,
passcode,
noc,
&buf[..icac_len],
&ipk_bytes,
admin_node_id,
admin_vendor_id,
)
.await?;
Ok(CommissionResult {
fabric_index,
device_node_id,
})
}
pub async fn complete_via_case(
&mut self,
peer_addr: Address,
phase1: &CommissionResult,
) -> Result<(), Error> {
let fab_idx = self.fab_idx;
{
let mut exchange =
Exchange::initiate_unsecured(self.matter, &self.crypto, peer_addr).await?;
CaseInitiator::initiate(&mut exchange, &self.crypto, fab_idx, phase1.device_node_id)
.await?;
}
self.commissioning_complete(fab_idx, phase1.device_node_id)
.await
}
pub(crate) async fn arm_fail_safe(
&self,
peer_addr: Address,
passcode: u32,
expiry_seconds: u16,
) -> Result<(), Error> {
let exchange =
Exchange::initiate_pase(self.matter, &self.crypto, peer_addr, passcode).await?;
let handle = exchange
.general_commissioning()
.arm_fail_safe(ROOT_ENDPOINT_ID, |req| {
req.expiry_length_seconds(expiry_seconds)?
.breadcrumb(0)?
.end()
})
.await?;
let code = handle.response()?.error_code()?;
handle.complete().await?;
if code != CommissioningErrorEnum::OK {
return Err(ErrorCode::Failure.into());
}
Ok(())
}
pub(crate) async fn csr_request<'m, F, R>(
matter: &'m Matter<'m>,
crypto: &C,
peer_addr: Address,
passcode: u32,
csr_nonce: &[u8; 32],
use_csr: F,
) -> Result<R, Error>
where
F: FnOnce(&[u8]) -> Result<R, Error>,
{
let exchange = Exchange::initiate_pase(matter, crypto, peer_addr, passcode).await?;
let handle = exchange
.operational_credentials()
.csr_request(ROOT_ENDPOINT_ID, |req| {
req.csr_nonce(OctetStr::new(csr_nonce))?
.is_for_update_noc(None)?
.end()
})
.await?;
let result = {
let resp = handle.response()?;
let nocsr_bytes = resp.nocsr_elements()?;
let root = TLVElement::new(nocsr_bytes.0).structure()?;
let csr_tlv = OctetStr::from_tlv(&root.ctx(NOCSR_TAG_CSR)?)?;
let nonce_echo = OctetStr::from_tlv(&root.ctx(NOCSR_TAG_NONCE)?)?;
if nonce_echo.0 != csr_nonce {
return Err(ErrorCode::Failure.into());
}
use_csr(csr_tlv.0)?
};
handle.complete().await?;
Ok(result)
}
pub(crate) async fn add_trusted_root_certificate<'m>(
matter: &'m Matter<'m>,
crypto: &C,
peer_addr: Address,
passcode: u32,
rcac_tlv: &[u8],
) -> Result<(), Error> {
let exchange = Exchange::initiate_pase(matter, crypto, peer_addr, passcode).await?;
exchange
.operational_credentials()
.add_trusted_root_certificate(ROOT_ENDPOINT_ID, |req| {
req.root_ca_certificate(OctetStr::new(rcac_tlv))?.end()
})
.await
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn add_noc<'m>(
matter: &'m Matter<'m>,
crypto: &C,
peer_addr: Address,
passcode: u32,
noc: &[u8],
icac: &[u8],
ipk: &[u8],
admin_case_subject: u64,
admin_vendor_id: u16,
) -> Result<NonZeroU8, Error> {
let exchange = Exchange::initiate_pase(matter, crypto, peer_addr, passcode).await?;
let handle = exchange
.operational_credentials()
.add_noc(ROOT_ENDPOINT_ID, |req| {
req.noc_value(OctetStr::new(noc))?
.icac_value(if icac.is_empty() {
None
} else {
Some(OctetStr::new(icac))
})?
.ipk_value(OctetStr::new(ipk))?
.case_admin_subject(admin_case_subject)?
.admin_vendor_id(admin_vendor_id)?
.end()
})
.await?;
let (status, fabric_index) = {
let resp = handle.response()?;
(resp.status_code()?, resp.fabric_index()?)
};
handle.complete().await?;
if status != NodeOperationalCertStatusEnum::OK {
return Err(ErrorCode::Failure.into());
}
fabric_index
.and_then(NonZeroU8::new)
.ok_or_else(|| ErrorCode::InvalidData.into())
}
pub(crate) async fn commissioning_complete(
&self,
fab_idx: NonZeroU8,
peer_node_id: NodeId,
) -> Result<(), Error> {
let exchange = Exchange::initiate(self.matter, &self.crypto, fab_idx, peer_node_id).await?;
let handle = exchange
.general_commissioning()
.commissioning_complete(ROOT_ENDPOINT_ID)
.await?;
let code = handle.response()?.error_code()?;
handle.complete().await?;
if code != CommissioningErrorEnum::OK {
return Err(ErrorCode::Failure.into());
}
Ok(())
}
async fn verify_device_attestation(&self, opts: &CommissionOptions) -> Result<(), Error> {
if opts.allow_test_attestation {
return Ok(());
}
Err(ErrorCode::Failure.into())
}
}