#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
use matter_cert::operational::{
icac, noc, rcac, sign_with_ring, IcacParams, NocParams, RcacParams,
};
use matter_cert::test_support::{build_unsigned, with_signature, TestCertFields};
use matter_cert::{
BasicConstraints, DistinguishedName, DnAttribute, Extensions, KeyIdentifier, KeyUsage,
MatterCertificate, MatterTime, PublicKey, Signature, TrustAnchor, TrustedRoots,
};
use matter_crypto::{
CaseCredentials, CaseInitiator, CaseResponder, CaseSigner, RingSigner, Sigma1Outcome,
};
const TEST_FABRIC_ID: u64 = 0x4242_4242_4242_4242;
const INITIATOR_NODE_ID: u64 = 0xDEAD_BEEF_CAFE_F00D;
const RESPONDER_NODE_ID: u64 = 0xBABE_FEED_1234_5678;
const IPK: [u8; 16] = [0x77; 16];
const TEST_SKI: [u8; 20] = [0x01; 20];
const NOC_SKI: [u8; 20] = [0x02; 20];
fn build_test_rcac() -> (MatterCertificate, RingSigner, TrustedRoots, [u8; 65]) {
let (rcac_signer, _pkcs8) = RingSigner::generate().expect("rcac signer");
let rcac_pub = *rcac_signer.public_key().as_bytes();
let rcac_dn = DistinguishedName::new(vec![DnAttribute::RcacId(1)]);
let extensions = Extensions::builder()
.basic_constraints(Some(BasicConstraints::new(true, Some(1))))
.key_usage(Some(KeyUsage::KEY_CERT_SIGN))
.subject_key_identifier(Some(KeyIdentifier(TEST_SKI)))
.authority_key_identifier(Some(KeyIdentifier(TEST_SKI)))
.build();
let fields = TestCertFields {
serial: vec![0x01],
issuer: rcac_dn.clone(),
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::from_unix_secs(2_500_000_000),
subject: rcac_dn,
public_key: PublicKey::new(rcac_pub).expect("rcac pub key"),
extensions,
signature: Signature::new([0u8; 64]),
};
let unsigned = build_unsigned(fields);
let tbs = unsigned.to_x509_tbs_der().expect("rcac tbs");
let sig_bytes = rcac_signer.sign_p256_sha256(&tbs).expect("rcac sign self");
let rcac = with_signature(&unsigned, Signature::new(sig_bytes));
let mut roots = TrustedRoots::new();
roots.add(TrustAnchor::from_root_cert(&rcac));
(rcac, rcac_signer, roots, rcac_pub)
}
fn build_test_noc(
rcac_signer: &RingSigner,
fabric_id: u64,
node_id: u64,
) -> (MatterCertificate, RingSigner) {
let (noc_signer, _) = RingSigner::generate().expect("noc signer");
let noc_pub = *noc_signer.public_key().as_bytes();
let subject_dn = DistinguishedName::new(vec![
DnAttribute::FabricId(fabric_id),
DnAttribute::NodeId(node_id),
]);
let issuer_dn = DistinguishedName::new(vec![DnAttribute::RcacId(1)]);
let extensions = Extensions::builder()
.basic_constraints(Some(BasicConstraints::new(false, None)))
.key_usage(Some(KeyUsage::DIGITAL_SIGNATURE))
.subject_key_identifier(Some(KeyIdentifier(NOC_SKI)))
.authority_key_identifier(Some(KeyIdentifier(TEST_SKI)))
.build();
let fields = TestCertFields {
serial: vec![0x02],
issuer: issuer_dn,
not_before: MatterTime::from_unix_secs(1_700_000_000),
not_after: MatterTime::from_unix_secs(2_500_000_000),
subject: subject_dn,
public_key: PublicKey::new(noc_pub).expect("noc pub key"),
extensions,
signature: Signature::new([0u8; 64]),
};
let unsigned = build_unsigned(fields);
let tbs = unsigned.to_x509_tbs_der().expect("noc tbs");
let sig_bytes = rcac_signer.sign_p256_sha256(&tbs).expect("rcac sign noc");
let noc = with_signature(&unsigned, Signature::new(sig_bytes));
(noc, noc_signer)
}
fn build_credentials(
noc: MatterCertificate,
signer: RingSigner,
fabric_id: u64,
node_id: u64,
ipk: [u8; 16],
rcac_public_key: [u8; 65],
) -> CaseCredentials {
CaseCredentials {
noc,
icac: None,
signer: Box::new(signer),
fabric_id,
node_id,
ipk,
rcac_public_key,
}
}
#[test]
#[allow(clippy::too_many_lines)] fn case_roundtrip_new_session() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator construction");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder construction");
let sigma1 = initiator.start().expect("sigma1");
let outcome = responder.handle_sigma1(&sigma1).expect("handle sigma1");
assert!(
matches!(outcome, Sigma1Outcome::NewSession),
"expected NewSession outcome from Sigma1"
);
let sigma2 = responder.next_message().expect("sigma2");
initiator.handle_sigma2(&sigma2).expect("handle sigma2");
let sigma3 = initiator.next_message().expect("sigma3");
responder.handle_sigma3(&sigma3).expect("handle sigma3");
let init_output = initiator.finish().expect("initiator finish");
let resp_output = responder.finish().expect("responder finish");
assert_eq!(
init_output.keys.i2r_key, resp_output.keys.i2r_key,
"i2r_key must match on both sides"
);
assert_eq!(
init_output.keys.r2i_key, resp_output.keys.r2i_key,
"r2i_key must match on both sides"
);
assert_eq!(
init_output.keys.attestation_challenge, resp_output.keys.attestation_challenge,
"attestation_challenge must match on both sides"
);
assert_eq!(
init_output.peer.node_id, RESPONDER_NODE_ID,
"initiator must see responder's node_id"
);
assert_eq!(
init_output.peer.fabric_id, TEST_FABRIC_ID,
"initiator must see correct fabric_id for peer"
);
assert_eq!(
resp_output.peer.node_id, INITIATOR_NODE_ID,
"responder must see initiator's node_id"
);
assert_eq!(
resp_output.peer.fabric_id, TEST_FABRIC_ID,
"responder must see correct fabric_id for peer"
);
assert_eq!(
init_output.local.node_id, INITIATOR_NODE_ID,
"initiator's local node_id must be correct"
);
assert_eq!(
init_output.local.fabric_id, TEST_FABRIC_ID,
"initiator's local fabric_id must be correct"
);
assert_eq!(
resp_output.local.node_id, RESPONDER_NODE_ID,
"responder's local node_id must be correct"
);
assert_eq!(
resp_output.local.fabric_id, TEST_FABRIC_ID,
"responder's local fabric_id must be correct"
);
let init_record = init_output
.resumption_record
.expect("initiator record after full handshake");
let resp_record = resp_output
.resumption_record
.expect("responder record after full handshake");
assert_eq!(
init_record.id, resp_record.id,
"both sides must store the same resumption id"
);
assert_eq!(
init_record.shared_secret, resp_record.shared_secret,
"both sides must store the same shared secret"
);
}
#[allow(clippy::similar_names)]
#[allow(clippy::type_complexity)]
fn build_operational_rcac_icac_chain() -> (
MatterCertificate,
MatterCertificate,
Vec<u8>,
TrustedRoots,
[u8; 65],
) {
let not_before = MatterTime::from_unix_secs(1_700_000_000);
let not_after = MatterTime::NO_EXPIRY;
let (rcac_signer, rcac_pkcs8) = RingSigner::generate().expect("rcac signer");
let rcac_pub_key = rcac_signer.public_key().clone();
let unsigned_rcac = rcac(RcacParams::new(
1,
rcac_pub_key,
vec![0x01],
not_before,
not_after,
Some(1),
))
.expect("build rcac");
let rcac_cert = sign_with_ring(unsigned_rcac, &rcac_pkcs8).expect("sign rcac");
let rcac_pub: [u8; 65] = *rcac_cert.public_key().as_bytes();
let rcac_subject = rcac_cert.subject().clone();
let rcac_skid = rcac_cert
.extensions()
.subject_key_identifier
.expect("rcac ski");
let (icac_signer, icac_pkcs8) = RingSigner::generate().expect("icac signer");
let icac_pub_key = icac_signer.public_key().clone();
let unsigned_icac = icac(IcacParams::new(
2,
rcac_subject,
rcac_skid,
icac_pub_key,
vec![0x02],
not_before,
not_after,
))
.expect("build icac");
let icac_cert = sign_with_ring(unsigned_icac, &rcac_pkcs8).expect("sign icac");
let mut trusted_roots = TrustedRoots::new();
trusted_roots.add(TrustAnchor::from_root_cert(&rcac_cert));
(rcac_cert, icac_cert, icac_pkcs8, trusted_roots, rcac_pub)
}
fn build_operational_noc_under_icac(
icac_cert: &MatterCertificate,
icac_pkcs8: &[u8],
fabric_id: u64,
node_id: u64,
serial: Vec<u8>,
) -> (MatterCertificate, RingSigner) {
let not_before = MatterTime::from_unix_secs(1_700_000_000);
let not_after = MatterTime::NO_EXPIRY;
let icac_subject = icac_cert.subject().clone();
let icac_skid = icac_cert
.extensions()
.subject_key_identifier
.expect("icac ski");
let (noc_signer, _noc_pkcs8) = RingSigner::generate().expect("noc signer");
let noc_pub_key = noc_signer.public_key().clone();
let unsigned_noc = noc(NocParams::new(
fabric_id,
node_id,
vec![],
icac_subject,
icac_skid,
noc_pub_key,
serial,
not_before,
not_after,
))
.expect("build noc");
let noc_cert = sign_with_ring(unsigned_noc, icac_pkcs8).expect("sign noc under icac");
(noc_cert, noc_signer)
}
#[test]
#[allow(clippy::too_many_lines)] fn case_establishes_over_three_tier_chain() {
let (rcac_cert, icac_cert, icac_pkcs8, trusted_roots, rcac_pub) =
build_operational_rcac_icac_chain();
let (initiator_noc, initiator_signer) = build_operational_noc_under_icac(
&icac_cert,
&icac_pkcs8,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
vec![0x03],
);
let (responder_noc, responder_signer) = build_operational_noc_under_icac(
&icac_cert,
&icac_pkcs8,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
vec![0x04],
);
assert_eq!(
initiator_noc.issuer(),
icac_cert.subject(),
"NOC issuer must be the ICAC subject"
);
assert_ne!(
initiator_noc.issuer(),
rcac_cert.subject(),
"NOC issuer must NOT be the RCAC subject in a 3-tier chain"
);
let initiator_creds = CaseCredentials {
noc: initiator_noc,
icac: Some(icac_cert.clone()),
signer: Box::new(initiator_signer),
fabric_id: TEST_FABRIC_ID,
node_id: INITIATOR_NODE_ID,
ipk: IPK,
rcac_public_key: rcac_pub,
};
let responder_creds = CaseCredentials {
noc: responder_noc,
icac: Some(icac_cert),
signer: Box::new(responder_signer),
fabric_id: TEST_FABRIC_ID,
node_id: RESPONDER_NODE_ID,
ipk: IPK,
rcac_public_key: rcac_pub,
};
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator construction");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder construction");
let sigma1 = initiator.start().expect("sigma1");
let outcome = responder.handle_sigma1(&sigma1).expect("handle sigma1");
assert!(
matches!(outcome, Sigma1Outcome::NewSession),
"expected NewSession outcome from Sigma1"
);
let sigma2 = responder.next_message().expect("sigma2");
initiator.handle_sigma2(&sigma2).expect("handle sigma2");
let sigma3 = initiator.next_message().expect("sigma3");
responder.handle_sigma3(&sigma3).expect("handle sigma3");
let init_output = initiator.finish().expect("initiator finish");
let resp_output = responder.finish().expect("responder finish");
assert_eq!(
init_output.keys.i2r_key, resp_output.keys.i2r_key,
"i2r_key must match on both sides"
);
assert_eq!(
init_output.keys.r2i_key, resp_output.keys.r2i_key,
"r2i_key must match on both sides"
);
assert_eq!(
init_output.keys.attestation_challenge, resp_output.keys.attestation_challenge,
"attestation_challenge must match on both sides"
);
assert_eq!(
init_output.peer.node_id, RESPONDER_NODE_ID,
"initiator must see responder's node_id"
);
assert_eq!(
init_output.peer.fabric_id, TEST_FABRIC_ID,
"initiator must see correct fabric_id for peer"
);
assert_eq!(
resp_output.peer.node_id, INITIATOR_NODE_ID,
"responder must see initiator's node_id"
);
assert_eq!(
resp_output.peer.fabric_id, TEST_FABRIC_ID,
"responder must see correct fabric_id for peer"
);
}
#[test]
#[allow(clippy::too_many_lines)] fn full_handshake_records_flip_roles_for_resumption() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (a_noc, a_signer) = build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (b_noc, b_signer) = build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let (a_noc2, a_signer2) = build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (b_noc2, b_signer2) = build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let now = MatterTime::from_unix_secs(2_000_000_000);
let a_creds = build_credentials(
a_noc,
a_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let b_creds = build_credentials(
b_noc,
b_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator1 = CaseInitiator::new(
a_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
now,
)
.expect("initiator1");
let mut responder1 =
CaseResponder::new(b_creds, trusted_roots.clone(), 0x0002, now).expect("responder1");
let sigma1 = initiator1.start().expect("sigma1");
assert!(matches!(
responder1.handle_sigma1(&sigma1).expect("handle sigma1"),
Sigma1Outcome::NewSession
));
let sigma2 = responder1.next_message().expect("sigma2");
initiator1.handle_sigma2(&sigma2).expect("handle sigma2");
let sigma3 = initiator1.next_message().expect("sigma3");
responder1.handle_sigma3(&sigma3).expect("handle sigma3");
let a_record = initiator1
.finish()
.expect("finish1 initiator")
.resumption_record
.expect("A record");
let b_record = responder1
.finish()
.expect("finish1 responder")
.resumption_record
.expect("B record");
let a_creds2 = build_credentials(
a_noc2,
a_signer2,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let b_creds2 = build_credentials(
b_noc2,
b_signer2,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator2 = CaseInitiator::new_with_resumption(
b_creds2,
trusted_roots.clone(),
INITIATOR_NODE_ID,
TEST_FABRIC_ID,
b_record,
0x0001,
now,
)
.expect("initiator2 (B resuming)");
let mut responder2 =
CaseResponder::new(a_creds2, trusted_roots, 0x0004, now).expect("responder2 (A)");
let sigma1r = initiator2.start().expect("sigma1 (resume)");
let outcome = responder2.handle_sigma1(&sigma1r).expect("handle sigma1r");
let presented = match outcome {
Sigma1Outcome::ResumptionRequested { id } => id,
Sigma1Outcome::NewSession => panic!("expected ResumptionRequested"),
};
assert_eq!(
presented, a_record.id,
"B must present the id A stored from session 1"
);
responder2
.accept_resumption(a_record)
.expect("A accepts resumption with its stored record");
let sigma2_resume = responder2.next_message().expect("sigma2_resume");
initiator2
.handle_sigma2_resume(&sigma2_resume)
.expect("B handles sigma2_resume");
let b_out = initiator2.finish().expect("finish2 initiator");
let a_out = responder2.finish().expect("finish2 responder");
assert_eq!(
b_out.keys.i2r_key, a_out.keys.i2r_key,
"resumed i2r keys must agree"
);
assert_eq!(
b_out.keys.r2i_key, a_out.keys.r2i_key,
"resumed r2i keys must agree"
);
let b_next = b_out.resumption_record.expect("B rotated record");
let a_next = a_out.resumption_record.expect("A rotated record");
assert_eq!(b_next.id, a_next.id, "rotated ids must agree");
assert_eq!(
b_next.shared_secret, a_next.shared_secret,
"secret carries over unchanged"
);
}
const WRONG_FABRIC: u64 = 0x9999_9999_9999_9999;
#[test]
fn case_roundtrip_wrong_fabric_returns_fabric_mismatch() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, WRONG_FABRIC, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
WRONG_FABRIC,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let result = responder
.handle_sigma1(&initiator.start().unwrap())
.and_then(|_| {
let sigma2 = responder.next_message()?;
initiator.handle_sigma2(&sigma2)?;
Ok(())
});
assert!(
result.is_err(),
"handshake must fail when fabric IDs mismatch"
);
}
fn drive_new_session_handshake() -> (
matter_crypto::CaseSessionOutput,
matter_crypto::CaseSessionOutput,
) {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder");
let sigma1 = initiator.start().expect("sigma1");
let outcome = responder.handle_sigma1(&sigma1).expect("handle sigma1");
assert!(
matches!(outcome, Sigma1Outcome::NewSession),
"expected NewSession from fresh handshake"
);
let sigma2 = responder.next_message().expect("sigma2");
initiator.handle_sigma2(&sigma2).expect("handle sigma2");
let sigma3 = initiator.next_message().expect("sigma3");
responder.handle_sigma3(&sigma3).expect("handle sigma3");
let init_out = initiator.finish().expect("initiator finish");
let resp_out = responder.finish().expect("responder finish");
(init_out, resp_out)
}
#[test]
fn sigma2_peer_signature_mismatch_is_rejected() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, _responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let (wrong_signer, _) = RingSigner::generate().expect("wrong signer");
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
wrong_signer, TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder");
let sigma1 = initiator.start().expect("sigma1");
responder.handle_sigma1(&sigma1).expect("handle sigma1");
let sigma2 = responder.next_message().expect("sigma2");
let err = initiator
.handle_sigma2(&sigma2)
.expect_err("a Sigma2 whose inner signature does not match the NOC must be rejected");
assert!(
matches!(err, matter_crypto::Error::PeerSignatureInvalid),
"expected PeerSignatureInvalid (AEAD passed; only ECDSA-verify catches this), got {err:?}"
);
}
#[test]
fn sigma3_peer_signature_mismatch_is_rejected() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, _initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let (wrong_signer, _) = RingSigner::generate().expect("wrong signer");
let initiator_creds = build_credentials(
initiator_noc,
wrong_signer, TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder");
let sigma1 = initiator.start().expect("sigma1");
responder.handle_sigma1(&sigma1).expect("handle sigma1");
let sigma2 = responder.next_message().expect("sigma2");
initiator.handle_sigma2(&sigma2).expect("handle sigma2");
let sigma3 = initiator.next_message().expect("sigma3");
let err = responder
.handle_sigma3(&sigma3)
.expect_err("a Sigma3 whose inner signature does not match the NOC must be rejected");
assert!(
matches!(err, matter_crypto::Error::PeerSignatureInvalid),
"expected PeerSignatureInvalid (AEAD passed; only ECDSA-verify catches this), got {err:?}"
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn case_resumption_roundtrip_accepted() {
let (initiator_out_1, _responder_out_1) = drive_new_session_handshake();
let resumption_id = matter_crypto::ResumptionId([0x42; 16]);
let shared_secret = [0x77u8; 32];
let (_rcac2, rcac_signer2, trusted_roots2, rcac_pub2) = build_test_rcac();
let (initiator_noc2, initiator_signer2) =
build_test_noc(&rcac_signer2, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc2, responder_signer2) =
build_test_noc(&rcac_signer2, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_peer_info = matter_crypto::PeerInfo {
node_id: RESPONDER_NODE_ID,
fabric_id: TEST_FABRIC_ID,
noc: responder_noc2.clone(),
session_id: 0,
};
let record_for_initiator = matter_crypto::ResumptionRecord {
id: resumption_id,
shared_secret,
peer: initiator_peer_info,
expires_at: None,
};
let responder_peer_info = matter_crypto::PeerInfo {
node_id: INITIATOR_NODE_ID,
fabric_id: TEST_FABRIC_ID,
noc: initiator_noc2.clone(),
session_id: 0,
};
let record_for_responder = matter_crypto::ResumptionRecord {
id: resumption_id, shared_secret,
peer: responder_peer_info,
expires_at: None,
};
let initiator_creds2 = build_credentials(
initiator_noc2,
initiator_signer2,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub2,
);
let responder_creds2 = build_credentials(
responder_noc2,
responder_signer2,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub2,
);
let mut initiator2 = CaseInitiator::new_with_resumption(
initiator_creds2,
trusted_roots2.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
record_for_initiator,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator2 with resumption");
let mut responder2 = CaseResponder::new(
responder_creds2,
trusted_roots2,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder2");
let sigma1_r = initiator2.start().expect("sigma1 (resumption)");
let outcome2 = responder2
.handle_sigma1(&sigma1_r)
.expect("handle sigma1 (resumption)");
let presented_id = match outcome2 {
Sigma1Outcome::ResumptionRequested { id } => id,
Sigma1Outcome::NewSession => panic!("expected ResumptionRequested, got NewSession"),
};
assert_eq!(
presented_id, resumption_id,
"responder must see the resumption_id the initiator embedded in Sigma1"
);
responder2
.accept_resumption(record_for_responder)
.expect("accept_resumption");
let sigma2_resume = responder2.next_message().expect("sigma2_resume");
initiator2
.handle_sigma2_resume(&sigma2_resume)
.expect("handle_sigma2_resume");
let init_out2 = initiator2.finish().expect("initiator2 finish");
let resp_out2 = responder2.finish().expect("responder2 finish");
assert_eq!(
init_out2.keys.i2r_key, resp_out2.keys.i2r_key,
"both sides must derive matching i2r_key via resumption"
);
assert_eq!(
init_out2.keys.r2i_key, resp_out2.keys.r2i_key,
"both sides must derive matching r2i_key via resumption"
);
assert_eq!(
init_out2.keys.attestation_challenge, resp_out2.keys.attestation_challenge,
"both sides must agree on attestation_challenge via resumption"
);
assert_ne!(
init_out2.keys.i2r_key, initiator_out_1.keys.i2r_key,
"resumed session keys must differ from original new-session keys"
);
assert_eq!(
init_out2.peer.node_id, RESPONDER_NODE_ID,
"initiator sees responder's node_id after resumption"
);
assert_eq!(
resp_out2.peer.node_id, INITIATOR_NODE_ID,
"responder sees initiator's node_id after resumption"
);
let init_next = init_out2
.resumption_record
.expect("initiator gets a fresh resumption record");
let resp_next = resp_out2
.resumption_record
.expect("responder gets a fresh resumption record");
assert_eq!(
init_next.id, resp_next.id,
"both sides agree on the new resumption_id for the next session"
);
assert_ne!(
init_next.id, resumption_id,
"new resumption_id differs from the one we just used (responder generates fresh)"
);
}
#[test]
fn case_resumption_declined_falls_back_to_new_session() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc.clone(),
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let bogus_peer_info = matter_crypto::PeerInfo {
node_id: RESPONDER_NODE_ID,
fabric_id: TEST_FABRIC_ID,
noc: responder_noc,
session_id: 0,
};
let bogus_record = matter_crypto::ResumptionRecord {
id: matter_crypto::ResumptionId([0x99; 16]),
shared_secret: [0xCC; 32],
peer: bogus_peer_info,
expires_at: None,
};
let mut initiator = CaseInitiator::new_with_resumption(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
bogus_record,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator with resumption");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder");
let sigma1 = initiator.start().expect("sigma1");
let outcome = responder.handle_sigma1(&sigma1).expect("handle sigma1");
assert!(
matches!(outcome, Sigma1Outcome::ResumptionRequested { .. }),
"responder must surface ResumptionRequested when Sigma1 has resumption fields"
);
responder.reject_resumption().expect("reject_resumption");
let sigma2 = responder.next_message().expect("sigma2 (fallback)");
initiator
.handle_sigma2(&sigma2)
.expect("handle sigma2 (fallback)");
let sigma3 = initiator.next_message().expect("sigma3 (fallback)");
responder
.handle_sigma3(&sigma3)
.expect("handle sigma3 (fallback)");
let init_out = initiator.finish().expect("initiator finish (fallback)");
let resp_out = responder.finish().expect("responder finish (fallback)");
assert_eq!(
init_out.keys.i2r_key, resp_out.keys.i2r_key,
"i2r_key must match after fallback to new-session"
);
assert_eq!(
init_out.keys.r2i_key, resp_out.keys.r2i_key,
"r2i_key must match after fallback to new-session"
);
assert_eq!(
init_out.keys.attestation_challenge, resp_out.keys.attestation_challenge,
"attestation_challenge must match after fallback to new-session"
);
}
#[test]
fn case_resumption_wrong_id_returns_invalid_parameter() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc.clone(),
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc.clone(),
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let presented_id = matter_crypto::ResumptionId([0x42; 16]);
let initiator_record = matter_crypto::ResumptionRecord {
id: presented_id,
shared_secret: [0x77; 32],
peer: matter_crypto::PeerInfo {
node_id: RESPONDER_NODE_ID,
fabric_id: TEST_FABRIC_ID,
noc: responder_noc,
session_id: 0,
},
expires_at: None,
};
let responder_record_wrong_id = matter_crypto::ResumptionRecord {
id: matter_crypto::ResumptionId([0x99; 16]), shared_secret: [0x77; 32],
peer: matter_crypto::PeerInfo {
node_id: INITIATOR_NODE_ID,
fabric_id: TEST_FABRIC_ID,
noc: initiator_noc,
session_id: 0,
},
expires_at: None,
};
let mut initiator = CaseInitiator::new_with_resumption(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
initiator_record,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("initiator with resumption");
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x0002,
MatterTime::from_unix_secs(2_000_000_000),
)
.expect("responder");
let sigma1 = initiator.start().expect("sigma1");
let outcome = responder.handle_sigma1(&sigma1).expect("handle sigma1");
assert!(
matches!(outcome, Sigma1Outcome::ResumptionRequested { .. }),
"expected ResumptionRequested"
);
let err = responder
.accept_resumption(responder_record_wrong_id)
.expect_err("accept_resumption with wrong id must fail");
assert!(
matches!(err, matter_crypto::Error::InvalidParameter),
"expected InvalidParameter, got {err:?}"
);
}
#[test]
fn case_roundtrip_threads_session_ids() {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = CaseCredentials {
noc: initiator_noc,
icac: None,
signer: Box::new(initiator_signer),
fabric_id: TEST_FABRIC_ID,
node_id: INITIATOR_NODE_ID,
ipk: IPK,
rcac_public_key: rcac_pub,
};
let responder_creds = CaseCredentials {
noc: responder_noc,
icac: None,
signer: Box::new(responder_signer),
fabric_id: TEST_FABRIC_ID,
node_id: RESPONDER_NODE_ID,
ipk: IPK,
rcac_public_key: rcac_pub,
};
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x00C1,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let mut responder = CaseResponder::new(
responder_creds,
trusted_roots,
0x00D2,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let sigma1 = initiator.start().unwrap();
let outcome = responder.handle_sigma1(&sigma1).unwrap();
assert!(
matches!(outcome, Sigma1Outcome::NewSession),
"expected NewSession"
);
let sigma2 = responder.next_message().unwrap();
initiator.handle_sigma2(&sigma2).unwrap();
let sigma3 = initiator.next_message().unwrap();
responder.handle_sigma3(&sigma3).unwrap();
let init_out = initiator.finish().unwrap();
let resp_out = responder.finish().unwrap();
assert_eq!(
init_out.local.session_id, 0x00C1,
"initiator local session_id"
);
assert_eq!(
init_out.peer.session_id, 0x00D2,
"initiator peer session_id"
);
assert_eq!(
resp_out.local.session_id, 0x00D2,
"responder local session_id"
);
assert_eq!(
resp_out.peer.session_id, 0x00C1,
"responder peer session_id"
);
}
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig {
cases: 8,
..ProptestConfig::default()
})]
#[test]
fn case_random_node_ids_roundtrip(
initiator_node_id in 1u64..=0x0000_FFFF_FFFF_FFFFu64,
responder_node_id in 1u64..=0x0000_FFFF_FFFF_FFFFu64,
) {
prop_assume!(initiator_node_id != responder_node_id);
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, initiator_node_id);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, responder_node_id);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
initiator_node_id,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
TEST_FABRIC_ID,
responder_node_id,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
responder_node_id,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let mut responder = CaseResponder::new(responder_creds, trusted_roots, 0x0002, MatterTime::from_unix_secs(2_000_000_000)).unwrap();
let sigma1 = initiator.start().unwrap();
responder.handle_sigma1(&sigma1).unwrap();
let sigma2 = responder.next_message().unwrap();
initiator.handle_sigma2(&sigma2).unwrap();
let sigma3 = initiator.next_message().unwrap();
responder.handle_sigma3(&sigma3).unwrap();
let init_out = initiator.finish().unwrap();
let resp_out = responder.finish().unwrap();
prop_assert_eq!(init_out.keys.i2r_key, resp_out.keys.i2r_key);
prop_assert_eq!(init_out.keys.r2i_key, resp_out.keys.r2i_key);
prop_assert_eq!(init_out.peer.node_id, responder_node_id);
prop_assert_eq!(resp_out.peer.node_id, initiator_node_id);
}
#[test]
fn case_random_byte_flip_in_sigma2_never_panics(
flip_offset in any::<usize>(),
flip_bit in 0u8..8u8,
) {
let (_rcac, rcac_signer, trusted_roots, rcac_pub) = build_test_rcac();
let (initiator_noc, initiator_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, INITIATOR_NODE_ID);
let (responder_noc, responder_signer) =
build_test_noc(&rcac_signer, TEST_FABRIC_ID, RESPONDER_NODE_ID);
let initiator_creds = build_credentials(
initiator_noc,
initiator_signer,
TEST_FABRIC_ID,
INITIATOR_NODE_ID,
IPK,
rcac_pub,
);
let responder_creds = build_credentials(
responder_noc,
responder_signer,
TEST_FABRIC_ID,
RESPONDER_NODE_ID,
IPK,
rcac_pub,
);
let mut initiator = CaseInitiator::new(
initiator_creds,
trusted_roots.clone(),
RESPONDER_NODE_ID,
TEST_FABRIC_ID,
0x0001,
MatterTime::from_unix_secs(2_000_000_000),
)
.unwrap();
let mut responder = CaseResponder::new(responder_creds, trusted_roots, 0x0002, MatterTime::from_unix_secs(2_000_000_000)).unwrap();
let sigma1 = initiator.start().unwrap();
responder.handle_sigma1(&sigma1).unwrap();
let mut sigma2 = responder.next_message().unwrap();
if !sigma2.is_empty() {
let off = flip_offset % sigma2.len();
sigma2[off] ^= 1 << flip_bit;
}
let _ = initiator.handle_sigma2(&sigma2);
}
}