use core::num::NonZeroU8;
use crate::cert::gen::{CertGenerator, CertType, IssuerDN, SubjectDN, Validity};
use crate::cert::x509::csr::CsrRef;
use crate::cert::CertRef;
use crate::crypto::{CanonPkcPublicKey, CanonPkcSecretKeyRef, Crypto, PublicKey, SigningSecretKey};
use crate::error::{Error, ErrorCode};
use crate::tlv::TLVElement;
use crate::Matter;
pub struct NocGenerator<'a> {
signing_privkey: CanonPkcSecretKeyRef<'a>,
fabric_id: u64,
rcac_id: u64,
icac_id: Option<u64>,
buf: &'a mut [u8],
}
impl<'a> NocGenerator<'a> {
pub fn new(
matter: &Matter<'_>,
signing_privkey: CanonPkcSecretKeyRef<'a>,
fab_idx: NonZeroU8,
buf: &'a mut [u8],
) -> Result<Self, Error> {
matter.with_state(|state| {
let fabric = state.fabrics.fabric(fab_idx)?;
Self::create(signing_privkey, fabric.root_ca(), fabric.icac(), buf)
})
}
pub fn create(
signing_privkey: CanonPkcSecretKeyRef<'a>,
rcac: &[u8],
icac: &[u8],
buf: &'a mut [u8],
) -> Result<Self, Error> {
let rcac = CertRef::new(TLVElement::new(rcac));
let fabric_id = rcac.get_fabric_id()?;
let rcac_id = rcac.get_ca_id()?;
let icac_id = (!icac.is_empty())
.then(|| {
let icac = CertRef::new(TLVElement::new(icac));
if icac.get_fabric_id()? != fabric_id {
return Err(Error::from(ErrorCode::InvalidData));
}
icac.get_ca_id()
})
.transpose()?;
Ok(Self {
signing_privkey,
fabric_id,
rcac_id,
icac_id,
buf,
})
}
pub fn generate<C: Crypto>(
&mut self,
crypto: C,
csr: &[u8],
node_id: u64,
cat_ids: &[u32],
validity: Validity,
) -> Result<&[u8], Error> {
if cat_ids.len() > 3 {
return Err(ErrorCode::InvalidData.into());
}
for &cat_id in cat_ids {
if (cat_id >> 16) as u16 == 0 {
return Err(ErrorCode::InvalidData.into());
}
}
let csr_ref = CsrRef::new(csr)?;
csr_ref.verify(&crypto)?;
let device_pubkey = csr_ref.pubkey()?;
let serial_bytes_vec = Self::encode_serial_asn1(node_id);
let serial_bytes = serial_bytes_vec.as_slice();
let (issuer_ca_id, is_rcac) = match self.icac_id {
Some(icac_id) => (icac_id, false),
None => (self.rcac_id, true),
};
let signing_privkey = crypto.secret_key(self.signing_privkey)?;
let mut pubkey_canon = CanonPkcPublicKey::new();
signing_privkey.pub_key()?.write_canon(&mut pubkey_canon)?;
let cert_len = CertGenerator::new(self.buf).generate(
&crypto,
CertType::Noc,
serial_bytes,
validity,
SubjectDN {
node_id: Some(node_id),
fabric_id: Some(self.fabric_id),
cat_ids,
ca_id: None,
},
IssuerDN {
ca_id: Some(issuer_ca_id),
fabric_id: Some(self.fabric_id),
is_rcac,
},
device_pubkey,
Some(pubkey_canon.reference()),
&signing_privkey,
)?;
Ok(&self.buf[..cert_len])
}
pub fn fabric_id(&self) -> u64 {
self.fabric_id
}
pub fn icac_id(&self) -> Option<u64> {
self.icac_id
}
pub fn signing_secret_key(&self) -> CanonPkcSecretKeyRef<'_> {
self.signing_privkey
}
fn encode_serial_asn1(serial: u64) -> heapless::Vec<u8, 9> {
let serial_bytes_full = serial.to_be_bytes();
let start = serial_bytes_full
.iter()
.position(|&b| b != 0)
.unwrap_or(serial_bytes_full.len() - 1);
let stripped = &serial_bytes_full[start..];
let mut vec = heapless::Vec::<u8, 9>::new();
if !stripped.is_empty() && (stripped[0] & 0x80) != 0 {
vec.push(0).unwrap();
}
vec.extend_from_slice(stripped).unwrap();
vec
}
}
#[cfg(test)]
mod tests {
use crate::cert::gen::VALID_FOREVER;
use crate::cert::{CertRef, MAX_CERT_TLV_AND_ASN1_LEN};
use crate::crypto::test_only_crypto;
use crate::onboard::cac::{IcacGenerator, RcacGenerator};
use crate::tlv::TLVElement;
use super::*;
const GOOD_CSR: &[u8] = &[
0x30, 0x81, 0xca, 0x30, 0x70, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06,
0x03, 0x55, 0x04, 0x0a, 0x0c, 0x03, 0x43, 0x53, 0x52, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07,
0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03,
0x01, 0x07, 0x03, 0x42, 0x00, 0x04, 0xa3, 0xbe, 0xa1, 0xf5, 0x42, 0x01, 0x07, 0x3c, 0x4b,
0x75, 0x85, 0xd8, 0xe2, 0x98, 0xac, 0x2f, 0xf6, 0x98, 0xdb, 0xd9, 0x5b, 0xe0, 0x7e, 0xc1,
0x04, 0xd5, 0x73, 0xc5, 0xb0, 0x90, 0x77, 0x27, 0x00, 0x1e, 0x22, 0xc7, 0x89, 0x5e, 0x4d,
0x75, 0x07, 0x89, 0x82, 0x0f, 0x49, 0xb6, 0x59, 0xd5, 0xc5, 0x15, 0x7d, 0x93, 0xe6, 0x80,
0x5c, 0x70, 0x89, 0x0a, 0x43, 0x10, 0x3d, 0xeb, 0x3d, 0x4a, 0xa0, 0x00, 0x30, 0x0c, 0x06,
0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x05, 0x00, 0x03, 0x48, 0x00, 0x30,
0x45, 0x02, 0x20, 0x1d, 0x86, 0x21, 0xb4, 0xc2, 0xe1, 0xa9, 0xf3, 0xbc, 0xc8, 0x7c, 0xda,
0xb4, 0xb9, 0xc6, 0x8c, 0xd0, 0xe4, 0x9a, 0x9c, 0xef, 0x02, 0x93, 0x98, 0x27, 0x7e, 0x81,
0x21, 0x5d, 0x20, 0x9d, 0x32, 0x02, 0x21, 0x00, 0x8b, 0x6b, 0x49, 0xb6, 0x7d, 0x3e, 0x67,
0x9e, 0xb1, 0x22, 0xd3, 0x63, 0x82, 0x40, 0x4f, 0x49, 0xa4, 0xdc, 0x17, 0x35, 0xac, 0x4b,
0x7a, 0xbf, 0x52, 0x05, 0x58, 0x68, 0xe0, 0xaa, 0xd2, 0x8e,
];
fn node_id_from(noc: &[u8]) -> Result<u64, Error> {
CertRef::new(TLVElement::new(noc)).get_node_id()
}
fn fabric_id_from(noc: &[u8]) -> Result<u64, Error> {
CertRef::new(TLVElement::new(noc)).get_fabric_id()
}
#[test]
fn rcac_direct_mode_noc_carries_correct_ids() {
let crypto = test_only_crypto();
let fabric_id = 0xABCDEF123456u64;
let mut cert_buf1 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
let (rcac_priv, rcac) = rcac_gen
.generate(&crypto, fabric_id, VALID_FOREVER)
.unwrap();
let mut cert_buf2 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut gen =
NocGenerator::create(rcac_priv.reference(), rcac, &[], &mut cert_buf2).unwrap();
assert!(gen.icac_id().is_none());
assert_eq!(gen.fabric_id(), fabric_id);
let noc = gen
.generate(&crypto, GOOD_CSR, 0x42, &[], VALID_FOREVER)
.unwrap();
assert_eq!(node_id_from(noc).unwrap(), 0x42);
assert_eq!(fabric_id_from(noc).unwrap(), fabric_id);
}
#[test]
fn icac_tier_mode_noc_carries_correct_ids() {
let crypto = test_only_crypto();
let fabric_id = 0xABCDEF123456u64;
let mut cert_buf1 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
let (rcac_priv, rcac) = rcac_gen
.generate(&crypto, fabric_id, VALID_FOREVER)
.unwrap();
let mut cert_buf2 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut icac_gen = IcacGenerator::new(&mut cert_buf2);
let (icac_priv, icac) = icac_gen
.generate(&crypto, rcac_priv.reference(), rcac, VALID_FOREVER)
.unwrap();
drop(rcac_priv);
let mut cert_buf3 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut gen =
NocGenerator::create(icac_priv.reference(), rcac, icac, &mut cert_buf3).unwrap();
assert!(gen.icac_id().is_some());
assert_eq!(gen.fabric_id(), fabric_id);
let noc = gen
.generate(&crypto, GOOD_CSR, 0x42, &[], VALID_FOREVER)
.unwrap();
assert_eq!(node_id_from(noc).unwrap(), 0x42);
assert_eq!(fabric_id_from(noc).unwrap(), fabric_id);
}
#[test]
fn invalid_csr_rejected() {
let crypto = test_only_crypto();
let mut cert_buf1 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
let (rcac_priv, rcac) = rcac_gen.generate(&crypto, 0x1u64, VALID_FOREVER).unwrap();
let mut cert_buf2 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut gen =
NocGenerator::create(rcac_priv.reference(), rcac, &[], &mut cert_buf2).unwrap();
assert!(gen
.generate(&crypto, &[0x01, 0x02, 0x03], 1, &[], VALID_FOREVER)
.is_err());
}
#[test]
fn too_many_cat_ids_rejected() {
let crypto = test_only_crypto();
let mut cert_buf1 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
let (rcac_priv, rcac) = rcac_gen.generate(&crypto, 0x1u64, VALID_FOREVER).unwrap();
let mut cert_buf2 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut gen =
NocGenerator::create(rcac_priv.reference(), rcac, &[], &mut cert_buf2).unwrap();
let too_many = &[1u32, 2, 3, 4];
assert!(gen
.generate(&crypto, GOOD_CSR, 1, too_many, VALID_FOREVER)
.is_err());
}
#[test]
fn icac_with_mismatched_fabric_id_rejected() {
let crypto = test_only_crypto();
let mut cert_buf1 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
let (_, rcac_a) = rcac_gen.generate(&crypto, 0xAA, VALID_FOREVER).unwrap();
let mut cert_buf2 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut icac_gen = IcacGenerator::new(&mut cert_buf2);
let (icac_priv_b, icac_b) = {
let mut cert_buf3 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
let mut rcac_gen_b = RcacGenerator::new(&mut cert_buf3);
let (rcac_priv_b, rcac_b) = rcac_gen_b.generate(&crypto, 0xBB, VALID_FOREVER).unwrap();
icac_gen
.generate(&crypto, rcac_priv_b.reference(), rcac_b, VALID_FOREVER)
.unwrap()
};
let mut cert_buf4 = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
assert!(
NocGenerator::create(icac_priv_b.reference(), rcac_a, icac_b, &mut cert_buf4).is_err()
);
}
}