pub(crate) mod initiator;
pub(crate) mod messages;
pub(crate) mod responder;
pub(crate) mod sigma;
pub(crate) mod signer;
use crate::case::signer::CaseSigner;
use matter_cert::{MatterCertificate, MatterTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CaseMessageKind {
Sigma1,
Sigma2,
Sigma3,
Sigma2Resume,
Sigma3Resume,
}
pub struct CaseCredentials {
pub noc: MatterCertificate,
pub icac: Option<MatterCertificate>,
pub signer: Box<dyn CaseSigner>,
pub fabric_id: u64,
pub node_id: u64,
pub ipk: [u8; 16],
pub rcac_public_key: [u8; 65],
}
impl core::fmt::Debug for CaseCredentials {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CaseCredentials")
.field("noc", &self.noc)
.field("icac", &self.icac)
.field("signer", &self.signer)
.field("fabric_id", &self.fabric_id)
.field("node_id", &self.node_id)
.field("ipk", &"<redacted>")
.field("rcac_public_key", &self.rcac_public_key)
.finish()
}
}
impl Drop for CaseCredentials {
fn drop(&mut self) {
use zeroize::Zeroize;
self.ipk.zeroize();
}
}
#[derive(Debug, Clone)]
pub struct CaseSessionOutput {
pub keys: CaseSessionKeys,
pub peer: PeerInfo,
pub local: LocalInfo,
pub resumption_record: Option<ResumptionRecord>,
}
#[derive(Clone, zeroize::ZeroizeOnDrop)]
pub struct CaseSessionKeys {
pub i2r_key: [u8; 16],
pub r2i_key: [u8; 16],
pub attestation_challenge: [u8; 16],
}
impl core::fmt::Debug for CaseSessionKeys {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("CaseSessionKeys").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PeerInfo {
pub node_id: u64,
pub fabric_id: u64,
pub noc: MatterCertificate,
pub session_id: u16,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LocalInfo {
pub node_id: u64,
pub fabric_id: u64,
pub session_id: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ResumptionId(pub [u8; 16]);
#[derive(Clone, zeroize::ZeroizeOnDrop)]
pub struct ResumptionRecord {
#[zeroize(skip)]
pub id: ResumptionId,
pub shared_secret: [u8; 32],
#[zeroize(skip)]
pub peer: PeerInfo,
#[zeroize(skip)]
pub expires_at: Option<MatterTime>,
}
impl core::fmt::Debug for ResumptionRecord {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ResumptionRecord")
.field("id", &self.id)
.field("shared_secret", &"<redacted>")
.field("peer", &self.peer)
.field("expires_at", &self.expires_at)
.finish()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Sigma1Outcome {
NewSession,
ResumptionRequested {
id: ResumptionId,
},
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod secret_hygiene_tests {
use super::*;
use matter_cert::test_support::{build_unsigned, TestCertFields};
use matter_cert::{
DistinguishedName, DnAttribute, Extensions, MatterTime, PublicKey, Signature,
};
fn assert_zeroize_on_drop<T: zeroize::ZeroizeOnDrop>() {}
#[test]
fn secret_key_types_are_zeroize_on_drop() {
assert_zeroize_on_drop::<CaseSessionKeys>();
assert_zeroize_on_drop::<ResumptionRecord>();
assert_zeroize_on_drop::<crate::pase::PaseSessionKeys>();
}
fn dummy_cert() -> MatterCertificate {
let subject = DistinguishedName::new(vec![
DnAttribute::FabricId(0x5678),
DnAttribute::NodeId(0x1234),
]);
let issuer = DistinguishedName::new(vec![DnAttribute::RcacId(1)]);
build_unsigned(TestCertFields {
serial: vec![1],
issuer,
not_before: MatterTime::from_unix_secs(0),
not_after: MatterTime::NO_EXPIRY,
subject,
public_key: PublicKey::new([0x04u8; 65]).unwrap(),
extensions: Extensions::default(),
signature: Signature::new([0u8; 64]),
})
}
#[test]
fn case_session_keys_debug_redacts_key_bytes() {
let keys = CaseSessionKeys {
i2r_key: [0xAA; 16],
r2i_key: [0xBB; 16],
attestation_challenge: [0xCC; 16],
};
let s = format!("{keys:?}");
assert!(
!s.contains(&format!("{:?}", [0xAAu8; 16])),
"i2r_key leaked: {s}"
);
assert!(
!s.contains(&format!("{:?}", [0xBBu8; 16])),
"r2i_key leaked: {s}"
);
assert!(
!s.contains(&format!("{:?}", [0xCCu8; 16])),
"attestation_challenge leaked: {s}"
);
assert!(s.contains("CaseSessionKeys"));
}
#[test]
fn resumption_record_debug_redacts_shared_secret() {
let record = ResumptionRecord {
id: ResumptionId([0x11; 16]),
shared_secret: [0xDD; 32],
peer: PeerInfo {
node_id: 0x1234,
fabric_id: 0x5678,
noc: dummy_cert(),
session_id: 1,
},
expires_at: None,
};
let s = format!("{record:?}");
assert!(
!s.contains(&format!("{:?}", [0xDDu8; 32])),
"shared_secret leaked: {s}"
);
assert!(s.contains("<redacted>"));
assert!(s.contains("ResumptionRecord"));
}
#[test]
fn case_credentials_debug_redacts_ipk() {
use crate::case::signer::RingSigner;
let (signer, _) = RingSigner::generate().unwrap();
let creds = CaseCredentials {
noc: dummy_cert(),
icac: None,
signer: Box::new(signer),
fabric_id: 0x5678,
node_id: 0x1234,
ipk: [0xEE; 16],
rcac_public_key: [0x04; 65],
};
let s = format!("{creds:?}");
assert!(
!s.contains(&format!("{:?}", [0xEEu8; 16])),
"ipk leaked: {s}"
);
assert!(s.contains("<redacted>"));
assert!(s.contains("CaseCredentials"));
}
}