use ring::digest;
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
use crate::builder::UnsignedCertificate;
use crate::certificate::MatterCertificate;
use crate::error::{Error, Result};
use crate::extensions::{BasicConstraints, Extensions, KeyIdentifier, KeyUsage};
use crate::name::{DistinguishedName, DnAttribute};
use crate::public_key::PublicKey;
use crate::time::MatterTime;
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct RcacParams {
pub rcac_id: u64,
pub public_key: PublicKey,
pub serial: Vec<u8>,
pub not_before: MatterTime,
pub not_after: MatterTime,
pub path_len: Option<u8>,
}
impl RcacParams {
#[must_use]
pub fn new(
rcac_id: u64,
public_key: PublicKey,
serial: Vec<u8>,
not_before: MatterTime,
not_after: MatterTime,
path_len: Option<u8>,
) -> Self {
Self {
rcac_id,
public_key,
serial,
not_before,
not_after,
path_len,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct IcacParams {
pub icac_id: u64,
pub issuer: DistinguishedName,
pub issuer_skid: KeyIdentifier,
pub public_key: PublicKey,
pub serial: Vec<u8>,
pub not_before: MatterTime,
pub not_after: MatterTime,
}
impl IcacParams {
#[must_use]
pub fn new(
icac_id: u64,
issuer: DistinguishedName,
issuer_skid: KeyIdentifier,
public_key: PublicKey,
serial: Vec<u8>,
not_before: MatterTime,
not_after: MatterTime,
) -> Self {
Self {
icac_id,
issuer,
issuer_skid,
public_key,
serial,
not_before,
not_after,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct NocParams {
pub fabric_id: u64,
pub node_id: u64,
pub case_authenticated_tags: Vec<u32>,
pub issuer: DistinguishedName,
pub issuer_skid: KeyIdentifier,
pub public_key: PublicKey,
pub serial: Vec<u8>,
pub not_before: MatterTime,
pub not_after: MatterTime,
}
impl NocParams {
#[must_use]
#[allow(clippy::too_many_arguments)] pub fn new(
fabric_id: u64,
node_id: u64,
case_authenticated_tags: Vec<u32>,
issuer: DistinguishedName,
issuer_skid: KeyIdentifier,
public_key: PublicKey,
serial: Vec<u8>,
not_before: MatterTime,
not_after: MatterTime,
) -> Self {
Self {
fabric_id,
node_id,
case_authenticated_tags,
issuer,
issuer_skid,
public_key,
serial,
not_before,
not_after,
}
}
}
const EKU_CLIENT_AUTH: u32 = 2;
const EKU_SERVER_AUTH: u32 = 1;
fn skid_from_spki(pk: &PublicKey) -> KeyIdentifier {
let hash = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &pk.as_bytes()[1..]);
let mut out = [0u8; 20];
out.copy_from_slice(hash.as_ref());
KeyIdentifier(out)
}
pub fn rcac(params: RcacParams) -> Result<UnsignedCertificate> {
let subject = DistinguishedName::new(vec![DnAttribute::RcacId(params.rcac_id)]);
let issuer = subject.clone();
let skid = skid_from_spki(¶ms.public_key);
let extensions = Extensions::builder()
.basic_constraints(Some(BasicConstraints::new(true, params.path_len)))
.key_usage(Some(KeyUsage::KEY_CERT_SIGN | KeyUsage::CRL_SIGN))
.subject_key_identifier(Some(skid))
.authority_key_identifier(Some(skid))
.build();
MatterCertificate::builder()
.serial(params.serial)
.issuer(issuer)
.subject(subject)
.validity(params.not_before, params.not_after)
.public_key(params.public_key)
.extensions(extensions)
.build_unsigned()
}
pub fn icac(params: IcacParams) -> Result<UnsignedCertificate> {
let subject = DistinguishedName::new(vec![DnAttribute::IcacId(params.icac_id)]);
let skid = skid_from_spki(¶ms.public_key);
let extensions = Extensions::builder()
.basic_constraints(Some(BasicConstraints::new(true, Some(0))))
.key_usage(Some(KeyUsage::KEY_CERT_SIGN | KeyUsage::CRL_SIGN))
.subject_key_identifier(Some(skid))
.authority_key_identifier(Some(params.issuer_skid))
.build();
MatterCertificate::builder()
.serial(params.serial)
.issuer(params.issuer)
.subject(subject)
.validity(params.not_before, params.not_after)
.public_key(params.public_key)
.extensions(extensions)
.build_unsigned()
}
pub fn noc(params: NocParams) -> Result<UnsignedCertificate> {
let mut subject_attrs: Vec<DnAttribute> =
Vec::with_capacity(2 + params.case_authenticated_tags.len());
subject_attrs.push(DnAttribute::FabricId(params.fabric_id));
subject_attrs.push(DnAttribute::NodeId(params.node_id));
for cat in ¶ms.case_authenticated_tags {
subject_attrs.push(DnAttribute::CaseAuthenticatedTag(*cat));
}
let subject = DistinguishedName::new(subject_attrs);
let skid = skid_from_spki(¶ms.public_key);
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(skid))
.authority_key_identifier(Some(params.issuer_skid))
.build();
MatterCertificate::builder()
.serial(params.serial)
.issuer(params.issuer)
.subject(subject)
.validity(params.not_before, params.not_after)
.public_key(params.public_key)
.extensions(extensions)
.build_unsigned()
}
pub fn sign_with_ring(
unsigned: UnsignedCertificate,
issuer_pkcs8: &[u8],
) -> Result<MatterCertificate> {
let tbs = unsigned.tbs_der()?;
let rng = SystemRandom::new();
let key_pair = EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, issuer_pkcs8, &rng)
.map_err(|_| Error::SigningFailed("issuer PKCS#8 key rejected by ring"))?;
let sig = key_pair
.sign(&rng, &tbs)
.map_err(|_| Error::SigningFailed("ring ECDSA signing failed"))?;
let sig_bytes = sig.as_ref();
if sig_bytes.len() != 64 {
return Err(Error::WrongSignatureLength(sig_bytes.len()));
}
let mut sig_arr = [0u8; 64];
sig_arr.copy_from_slice(sig_bytes);
Ok(unsigned.assemble(sig_arr))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use crate::{MatterTime, PublicKey};
fn spki() -> PublicKey {
PublicKey::new([0x04; 65]).unwrap()
}
#[test]
fn rcac_has_the_expected_profile() {
let unsigned = rcac(RcacParams {
rcac_id: 1,
public_key: spki(),
serial: vec![0x01],
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::NO_EXPIRY,
path_len: Some(1),
})
.unwrap();
let ext = unsigned.extensions();
let bc = ext.basic_constraints.unwrap();
assert!(bc.is_ca && bc.path_len_constraint == Some(1));
assert_eq!(
ext.key_usage,
Some(KeyUsage::KEY_CERT_SIGN | KeyUsage::CRL_SIGN)
);
assert!(ext.subject_key_identifier.is_some());
assert_eq!(ext.authority_key_identifier, ext.subject_key_identifier);
assert_eq!(unsigned.subject().rcac_id(), Some(1));
assert_eq!(unsigned.issuer().rcac_id(), Some(1));
}
#[test]
fn icac_has_the_expected_profile() {
let issuer_dn = DistinguishedName::new(vec![DnAttribute::RcacId(1)]);
let issuer_skid = skid_from_spki(&spki());
let unsigned = icac(IcacParams {
icac_id: 2,
issuer: issuer_dn.clone(),
issuer_skid,
public_key: spki(),
serial: vec![0x02],
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::NO_EXPIRY,
})
.unwrap();
let ext = unsigned.extensions();
let bc = ext.basic_constraints.unwrap();
assert!(bc.is_ca && bc.path_len_constraint == Some(0));
assert_eq!(
ext.key_usage,
Some(KeyUsage::KEY_CERT_SIGN | KeyUsage::CRL_SIGN)
);
assert!(ext.subject_key_identifier.is_some());
assert_eq!(ext.authority_key_identifier, Some(issuer_skid));
assert_eq!(unsigned.subject().icac_id(), Some(2));
assert_eq!(unsigned.issuer().rcac_id(), Some(1));
}
#[test]
fn noc_has_the_expected_profile() {
let issuer_dn = DistinguishedName::new(vec![DnAttribute::IcacId(2)]);
let issuer_skid = skid_from_spki(&spki());
let unsigned = noc(NocParams {
fabric_id: 7,
node_id: 0xDEAD_BEEF_CAFE_BABE,
case_authenticated_tags: vec![0x0001_0002, 0x0003_0004],
issuer: issuer_dn.clone(),
issuer_skid,
public_key: spki(),
serial: vec![0x03],
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::NO_EXPIRY,
})
.unwrap();
let ext = unsigned.extensions();
let bc = ext.basic_constraints.unwrap();
assert!(!bc.is_ca);
assert_eq!(ext.key_usage, Some(KeyUsage::DIGITAL_SIGNATURE));
assert_eq!(ext.extended_key_usage, Some(vec![2, 1]));
assert!(ext.subject_key_identifier.is_some());
assert_eq!(ext.authority_key_identifier, Some(issuer_skid));
assert_eq!(
unsigned.subject().iter().cloned().collect::<Vec<_>>(),
vec![
DnAttribute::FabricId(7),
DnAttribute::NodeId(0xDEAD_BEEF_CAFE_BABE),
DnAttribute::CaseAuthenticatedTag(0x0001_0002),
DnAttribute::CaseAuthenticatedTag(0x0003_0004),
]
);
assert_eq!(unsigned.issuer(), &issuer_dn);
}
#[test]
fn skid_uses_matter_64byte_convention() {
let pk = spki();
let unsigned = noc(NocParams {
fabric_id: 1,
node_id: 2,
case_authenticated_tags: vec![],
issuer: DistinguishedName::new(vec![DnAttribute::RcacId(1)]),
issuer_skid: skid_from_spki(&pk),
public_key: pk.clone(),
serial: vec![0x04],
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::NO_EXPIRY,
})
.unwrap();
let expected = {
let hash = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, &pk.as_bytes()[1..]);
let mut arr = [0u8; 20];
arr.copy_from_slice(hash.as_ref());
KeyIdentifier(arr)
};
assert_eq!(unsigned.extensions().subject_key_identifier, Some(expected));
assert_eq!(expected, skid_from_spki(&pk));
let full = {
let hash = digest::digest(&digest::SHA1_FOR_LEGACY_USE_ONLY, pk.as_bytes());
let mut arr = [0u8; 20];
arr.copy_from_slice(hash.as_ref());
KeyIdentifier(arr)
};
assert_ne!(expected, full);
}
}