#![forbid(unsafe_code)]
use matter_cert::{
BasicConstraints, DistinguishedName, DnAttribute, Extensions, KeyIdentifier, KeyUsage,
MatterCertificate, MatterTime,
};
use ring::digest;
use crate::noc::csr::VerifiedCsr;
use crate::noc::error::{NocError, NocRng};
use crate::noc::fabric::FabricRecord;
const EKU_CLIENT_AUTH: u32 = 2;
const EKU_SERVER_AUTH: u32 = 1;
pub fn issue_noc(
fabric: &FabricRecord,
verified_csr: &VerifiedCsr,
node_id: u64,
case_authenticated_tags: &[u32],
validity: (MatterTime, MatterTime),
rng: &dyn NocRng,
) -> Result<MatterCertificate, NocError> {
let mut subject_attrs: Vec<DnAttribute> = Vec::with_capacity(2 + case_authenticated_tags.len());
subject_attrs.push(DnAttribute::FabricId(fabric.fabric_id));
subject_attrs.push(DnAttribute::NodeId(node_id));
for cat in case_authenticated_tags {
subject_attrs.push(DnAttribute::CaseAuthenticatedTag(*cat));
}
let subject = DistinguishedName::new(subject_attrs);
let ski_bytes = digest::digest(
&digest::SHA1_FOR_LEGACY_USE_ONLY,
&verified_csr.public_key.as_bytes()[1..],
);
let mut ski_arr = [0u8; 20];
ski_arr.copy_from_slice(ski_bytes.as_ref());
let root_ski = fabric
.root_cert
.extensions()
.subject_key_identifier
.ok_or_else(|| {
NocError::CertBuild(matter_cert::Error::MissingField(4))
})?;
let extensions = Extensions::builder()
.basic_constraints(Some(BasicConstraints::new(false, None)))
.key_usage(Some(KeyUsage::DIGITAL_SIGNATURE))
.extended_key_usage(Some(vec![EKU_CLIENT_AUTH, EKU_SERVER_AUTH]))
.subject_key_identifier(Some(KeyIdentifier(ski_arr)))
.authority_key_identifier(Some(root_ski))
.build();
let mut serial = vec![0u8; 19];
rng.fill(&mut serial)?;
serial[0] &= 0x7F;
let unsigned = MatterCertificate::builder()
.serial(serial)
.issuer(fabric.root_cert.subject().clone())
.subject(subject)
.validity(validity.0, validity.1)
.public_key(verified_csr.public_key.clone())
.extensions(extensions)
.build_unsigned()
.map_err(NocError::CertBuild)?;
let tbs = unsigned.tbs_der().map_err(NocError::CertBuild)?;
let sig = fabric
.root_signer
.sign_p256_sha256(&tbs)
.map_err(NocError::SigningFailed)?;
Ok(unsigned.assemble(sig))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use crate::noc::error::SystemNocRng;
use matter_cert::PublicKey;
use matter_crypto::{RingSigner, Signer};
use std::sync::Arc;
fn sample_verified_csr() -> VerifiedCsr {
let (signer, _) = RingSigner::generate().unwrap();
VerifiedCsr {
public_key: PublicKey::from_slice(signer.public_key().as_bytes()).unwrap(),
}
}
fn sample_fabric() -> FabricRecord {
let (signer, _) = RingSigner::generate().unwrap();
let signer: Arc<dyn Signer> = Arc::new(signer);
FabricRecord::new_root_only(
0x0000_0000_0000_0001,
signer,
MatterTime::from_unix_secs(1_700_000_000),
MatterTime::NO_EXPIRY,
7,
&SystemNocRng,
)
.unwrap()
}
#[derive(Debug)]
struct AllOnesRng;
impl crate::noc::NocRng for AllOnesRng {
fn fill(&self, dest: &mut [u8]) -> Result<(), crate::noc::NocError> {
dest.fill(0xFF);
Ok(())
}
}
#[test]
fn noc_serial_top_bit_is_clear() {
let fabric = sample_fabric();
let verified = sample_verified_csr();
let noc = issue_noc(
&fabric,
&verified,
0xDEAD_BEEF_CAFE_BABE,
&[],
(
MatterTime::from_unix_secs(1_700_000_000),
MatterTime::NO_EXPIRY,
),
&AllOnesRng,
)
.unwrap();
assert_eq!(
noc.serial()[0] & 0x80,
0,
"generated NOC serial must have the top bit clear"
);
}
#[test]
fn issue_noc_produces_self_consistent_certificate() {
let fabric = sample_fabric();
let verified = sample_verified_csr();
let noc = issue_noc(
&fabric,
&verified,
0xDEAD_BEEF_CAFE_BABE,
&[0x0001_0002],
(
MatterTime::from_unix_secs(1_700_000_000),
MatterTime::NO_EXPIRY,
),
&SystemNocRng,
)
.unwrap();
assert_eq!(noc.subject().fabric_id(), Some(0x0000_0000_0000_0001));
assert_eq!(noc.subject().node_id(), Some(0xDEAD_BEEF_CAFE_BABE));
assert_eq!(noc.issuer(), fabric.root_cert.subject());
noc.verify_signed_by(&fabric.root_public_key).unwrap();
}
}