use p256::elliptic_curve::sec1::ToEncodedPoint;
use p256::{NonZeroScalar, PublicKey as P256PublicKey, SecretKey};
use ring::digest::{Context, SHA256};
use ring::hkdf;
use ring::rand::SecureRandom;
use subtle::ConstantTimeEq;
use matter_cert::MatterCertificate;
use matter_codec::{Tag, TlvWriter};
use crate::aead;
use crate::error::{Error, Result};
#[allow(unused_imports)] pub(crate) use crate::aead::{AEAD_KEY_LEN, AEAD_NONCE_LEN, AEAD_TAG_LEN};
pub(crate) const HKDF_INFO_SIGMA2: &[u8] = b"Sigma2";
pub(crate) const HKDF_INFO_SIGMA3: &[u8] = b"Sigma3";
#[allow(dead_code)] pub(crate) const HKDF_INFO_SIGMA1_RESUME: &[u8] = b"Sigma1_Resume";
#[allow(dead_code)] pub(crate) const HKDF_INFO_SIGMA2_RESUME: &[u8] = b"Sigma2_Resume";
#[allow(dead_code)] pub(crate) const HKDF_INFO_RESUMPTION_SESSION_KEYS: &[u8] = b"SessionResumptionKeys";
pub(crate) const NONCE_TBE_DATA2: &[u8; AEAD_NONCE_LEN] = b"NCASE_Sigma2N";
pub(crate) const NONCE_TBE_DATA3: &[u8; AEAD_NONCE_LEN] = b"NCASE_Sigma3N";
#[allow(dead_code)] pub(crate) const NONCE_RESUME1_MIC: &[u8; AEAD_NONCE_LEN] = b"NCASE_SigmaS1";
#[allow(dead_code)] pub(crate) const NONCE_RESUME2_MIC: &[u8; AEAD_NONCE_LEN] = b"NCASE_SigmaS2";
pub(crate) fn generate_ephemeral_keypair(rng: &dyn SecureRandom) -> Result<(SecretKey, [u8; 65])> {
for _ in 0..16 {
let mut bytes = [0u8; 32];
rng.fill(&mut bytes)
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
let scalar_opt = NonZeroScalar::from_repr(bytes.into());
if let Some(scalar) = Option::<NonZeroScalar>::from(scalar_opt) {
let sk = SecretKey::new(scalar.into());
let pk = sk.public_key();
let encoded = pk.to_encoded_point(false); let mut pub_bytes = [0u8; 65];
pub_bytes.copy_from_slice(encoded.as_bytes());
return Ok((sk, pub_bytes));
}
}
Err(Error::EphemeralKeyGenerationFailed)
}
pub(crate) fn ecdh_shared_secret(
my_secret: &SecretKey,
peer_pub_bytes: &[u8; 65],
) -> Result<[u8; 32]> {
let peer_pub =
P256PublicKey::from_sec1_bytes(peer_pub_bytes).map_err(|_| Error::InvalidParameter)?;
let shared = p256::ecdh::diffie_hellman(my_secret.to_nonzero_scalar(), peer_pub.as_affine());
let bytes = shared.raw_secret_bytes();
let mut out = [0u8; 32];
out.copy_from_slice(bytes.as_ref());
Ok(out)
}
#[allow(dead_code)] pub(crate) fn hkdf_expand(prk: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, &[]);
let prk_obj = salt.extract(prk);
let info_arr = [info];
let okm = prk_obj
.expand(&info_arr, OutLen(out.len()))
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
okm.fill(out)
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
Ok(())
}
pub(crate) fn hkdf_derive(ikm: &[u8], salt: &[u8], info: &[u8], out: &mut [u8]) -> Result<()> {
let salt_obj = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
let prk = salt_obj.extract(ikm);
let info_arr = [info];
let okm = prk
.expand(&info_arr, OutLen(out.len()))
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
okm.fill(out)
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
Ok(())
}
struct OutLen(usize);
impl hkdf::KeyType for OutLen {
fn len(&self) -> usize {
self.0
}
}
pub(crate) fn transcript_hash(messages: &[&[u8]]) -> [u8; 32] {
let mut ctx = Context::new(&SHA256);
for msg in messages {
ctx.update(msg);
}
let d = ctx.finish();
let mut out = [0u8; 32];
out.copy_from_slice(d.as_ref());
out
}
pub(crate) fn aead_encrypt(
key: &[u8; AEAD_KEY_LEN],
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>> {
aead::encrypt(key, nonce, aad, plaintext)
}
pub(crate) fn aead_decrypt(
key: &[u8; AEAD_KEY_LEN],
nonce: &[u8; AEAD_NONCE_LEN],
aad: &[u8],
ciphertext: &[u8],
) -> Result<Vec<u8>> {
aead::decrypt(key, nonce, aad, ciphertext)
}
#[allow(dead_code)] pub(crate) fn verify_mac(expected: &[u8; 16], received: &[u8; 16]) -> Result<()> {
if expected.ct_eq(received).unwrap_u8() == 1 {
Ok(())
} else {
Err(Error::ResumptionMacMismatch)
}
}
#[allow(dead_code)]
fn derive_sigma1_resume_key(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
resumption_id: &[u8; 16],
) -> Result<[u8; AEAD_KEY_LEN]> {
let mut salt = [0u8; 48];
salt[..32].copy_from_slice(initiator_random);
salt[32..].copy_from_slice(resumption_id);
let mut key = [0u8; AEAD_KEY_LEN];
hkdf_derive(shared_secret, &salt, HKDF_INFO_SIGMA1_RESUME, &mut key)?;
Ok(key)
}
#[allow(dead_code)]
fn derive_sigma2_resume_key(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
new_resumption_id: &[u8; 16],
) -> Result<[u8; AEAD_KEY_LEN]> {
let mut salt = [0u8; 48];
salt[..32].copy_from_slice(initiator_random);
salt[32..].copy_from_slice(new_resumption_id);
let mut key = [0u8; AEAD_KEY_LEN];
hkdf_derive(shared_secret, &salt, HKDF_INFO_SIGMA2_RESUME, &mut key)?;
Ok(key)
}
#[allow(dead_code)] pub(crate) fn compute_sigma1_resume_mic(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
resumption_id: &[u8; 16],
) -> Result<[u8; 16]> {
let key = derive_sigma1_resume_key(shared_secret, initiator_random, resumption_id)?;
let tag_bytes = aead_encrypt(&key, NONCE_RESUME1_MIC, &[], &[])?;
let tag: [u8; 16] = tag_bytes
.try_into()
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
Ok(tag)
}
#[allow(dead_code)] pub(crate) fn verify_sigma1_resume_mic(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
resumption_id: &[u8; 16],
received_mic: &[u8; 16],
) -> Result<()> {
let expected = compute_sigma1_resume_mic(shared_secret, initiator_random, resumption_id)?;
verify_mac(&expected, received_mic)
}
#[allow(dead_code)] pub(crate) fn compute_sigma2_resume_mic(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
new_resumption_id: &[u8; 16],
) -> Result<[u8; 16]> {
let key = derive_sigma2_resume_key(shared_secret, initiator_random, new_resumption_id)?;
let tag_bytes = aead_encrypt(&key, NONCE_RESUME2_MIC, &[], &[])?;
let tag: [u8; 16] = tag_bytes
.try_into()
.map_err(|_| Error::EphemeralKeyGenerationFailed)?;
Ok(tag)
}
#[allow(dead_code)] pub(crate) fn verify_sigma2_resume_mic(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
new_resumption_id: &[u8; 16],
received_mic: &[u8; 16],
) -> Result<()> {
let expected = compute_sigma2_resume_mic(shared_secret, initiator_random, new_resumption_id)?;
verify_mac(&expected, received_mic)
}
#[allow(dead_code)] pub(crate) fn derive_resume_session_keys(
shared_secret: &[u8; 32],
initiator_random: &[u8; 32],
old_resumption_id: &[u8; 16],
) -> Result<[u8; 48]> {
let mut salt = [0u8; 48];
salt[..32].copy_from_slice(initiator_random);
salt[32..].copy_from_slice(old_resumption_id);
let mut out = [0u8; 48];
hkdf_derive(
shared_secret,
&salt,
HKDF_INFO_RESUMPTION_SESSION_KEYS,
&mut out,
)?;
Ok(out)
}
pub(crate) fn compute_dest_id(
ipk: &[u8; 16],
rcac_public_key: &[u8; 65],
fabric_id: u64,
node_id: u64,
initiator_random: &[u8; 32],
) -> [u8; 32] {
use ring::hmac;
let key = hmac::Key::new(hmac::HMAC_SHA256, ipk);
let mut salt: Vec<u8> = Vec::with_capacity(113);
salt.extend_from_slice(initiator_random);
salt.extend_from_slice(rcac_public_key);
salt.extend_from_slice(&fabric_id.to_le_bytes());
salt.extend_from_slice(&node_id.to_le_bytes());
let tag = hmac::sign(&key, &salt);
let mut out = [0u8; 32];
out.copy_from_slice(tag.as_ref());
out
}
pub(crate) fn encode_tbs_data(
responder_noc_tlv: &[u8],
responder_icac_tlv: Option<&[u8]>,
responder_public_key: &[u8; 65],
initiator_public_key: &[u8; 65],
) -> Result<Vec<u8>> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)?;
w.put_bytes(Tag::Context(1), responder_noc_tlv)?;
if let Some(icac) = responder_icac_tlv {
w.put_bytes(Tag::Context(2), icac)?;
}
w.put_bytes(Tag::Context(3), responder_public_key)?;
w.put_bytes(Tag::Context(4), initiator_public_key)?;
w.end_container()?;
Ok(buf)
}
pub(crate) fn encode_tbedata2(
noc_tlv: &[u8],
icac_tlv: Option<&[u8]>,
signature: &[u8; 64],
resumption_id: &[u8; 16],
) -> Result<Vec<u8>> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)?;
w.put_bytes(Tag::Context(1), noc_tlv)?;
if let Some(icac) = icac_tlv {
w.put_bytes(Tag::Context(2), icac)?;
}
w.put_bytes(Tag::Context(3), signature)?;
w.put_bytes(Tag::Context(4), resumption_id)?;
w.end_container()?;
Ok(buf)
}
pub(crate) struct TbeData2 {
pub peer_noc: MatterCertificate,
pub peer_icac: Option<MatterCertificate>,
pub peer_noc_tlv: Vec<u8>,
pub peer_icac_tlv: Option<Vec<u8>>,
pub peer_signature: Vec<u8>,
pub resumption_id: [u8; 16],
}
pub(crate) fn decode_tbedata2(plaintext: &[u8]) -> Result<TbeData2> {
use matter_codec::{ContainerKind, Element, Tag as MTag, TlvReader, Value};
let mut reader = TlvReader::new(plaintext);
match reader.next()? {
Some(Element::ContainerStart {
tag: MTag::Anonymous,
kind: ContainerKind::Structure,
}) => {}
_ => return Err(Error::InvalidParameter),
}
let mut noc_bytes: Option<Vec<u8>> = None;
let mut icac_bytes: Option<Vec<u8>> = None;
let mut signature: Option<Vec<u8>> = None;
let mut resumption_id: Option<[u8; 16]> = None;
loop {
match reader.next()? {
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: MTag::Context(1),
value: Value::Bytes(b),
}) => {
noc_bytes = Some(b);
}
Some(Element::Scalar {
tag: MTag::Context(2),
value: Value::Bytes(b),
}) => {
icac_bytes = Some(b);
}
Some(Element::Scalar {
tag: MTag::Context(3),
value: Value::Bytes(b),
}) => {
signature = Some(b);
}
Some(Element::Scalar {
tag: MTag::Context(4),
value: Value::Bytes(b),
}) => {
let arr: [u8; 16] = b.try_into().map_err(|_| Error::InvalidParameter)?;
resumption_id = Some(arr);
}
None | Some(_) => return Err(Error::InvalidParameter),
}
}
let noc_b = noc_bytes.ok_or(Error::InvalidParameter)?;
let sig = signature.ok_or(Error::InvalidParameter)?;
let rid = resumption_id.ok_or(Error::InvalidParameter)?;
let peer_noc = MatterCertificate::from_tlv(&noc_b).map_err(Error::InvalidPeerNocChain)?;
let peer_icac = match &icac_bytes {
Some(b) => Some(MatterCertificate::from_tlv(b).map_err(Error::InvalidPeerNocChain)?),
None => None,
};
Ok(TbeData2 {
peer_noc,
peer_icac,
peer_noc_tlv: noc_b,
peer_icac_tlv: icac_bytes,
peer_signature: sig,
resumption_id: rid,
})
}
pub(crate) fn encode_tbedata3(
noc_tlv: &[u8],
icac_tlv: Option<&[u8]>,
signature: &[u8; 64],
) -> Result<Vec<u8>> {
let mut buf = Vec::new();
let mut w = TlvWriter::new(&mut buf);
w.start_structure(Tag::Anonymous)?;
w.put_bytes(Tag::Context(1), noc_tlv)?;
if let Some(icac) = icac_tlv {
w.put_bytes(Tag::Context(2), icac)?;
}
w.put_bytes(Tag::Context(3), signature)?;
w.end_container()?;
Ok(buf)
}
#[allow(clippy::struct_field_names)]
pub(crate) struct TbeData3 {
pub peer_noc: MatterCertificate,
pub peer_icac: Option<MatterCertificate>,
pub peer_noc_tlv: Vec<u8>,
pub peer_icac_tlv: Option<Vec<u8>>,
pub peer_signature: Vec<u8>,
}
pub(crate) fn decode_tbedata3(plaintext: &[u8]) -> Result<TbeData3> {
use matter_codec::{ContainerKind, Element, Tag as MTag, TlvReader, Value};
let mut reader = TlvReader::new(plaintext);
match reader.next()? {
Some(Element::ContainerStart {
tag: MTag::Anonymous,
kind: ContainerKind::Structure,
}) => {}
_ => return Err(Error::InvalidParameter),
}
let mut noc_bytes: Option<Vec<u8>> = None;
let mut icac_bytes: Option<Vec<u8>> = None;
let mut signature: Option<Vec<u8>> = None;
loop {
match reader.next()? {
Some(Element::ContainerEnd) => break,
Some(Element::Scalar {
tag: MTag::Context(1),
value: Value::Bytes(b),
}) => {
noc_bytes = Some(b);
}
Some(Element::Scalar {
tag: MTag::Context(2),
value: Value::Bytes(b),
}) => {
icac_bytes = Some(b);
}
Some(Element::Scalar {
tag: MTag::Context(3),
value: Value::Bytes(b),
}) => {
signature = Some(b);
}
None | Some(_) => return Err(Error::InvalidParameter),
}
}
let noc_b = noc_bytes.ok_or(Error::InvalidParameter)?;
let sig = signature.ok_or(Error::InvalidParameter)?;
let peer_noc = MatterCertificate::from_tlv(&noc_b).map_err(Error::InvalidPeerNocChain)?;
let peer_icac = match &icac_bytes {
Some(b) => Some(MatterCertificate::from_tlv(b).map_err(Error::InvalidPeerNocChain)?),
None => None,
};
Ok(TbeData3 {
peer_noc,
peer_icac,
peer_noc_tlv: noc_b,
peer_icac_tlv: icac_bytes,
peer_signature: sig,
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used)] mod tests {
use super::*;
use ring::rand::SystemRandom;
#[test]
fn nonce_constants_are_13_bytes() {
assert_eq!(NONCE_TBE_DATA2.len(), AEAD_NONCE_LEN);
assert_eq!(NONCE_TBE_DATA3.len(), AEAD_NONCE_LEN);
assert_eq!(NONCE_RESUME1_MIC.len(), AEAD_NONCE_LEN);
assert_eq!(NONCE_RESUME2_MIC.len(), AEAD_NONCE_LEN);
}
#[test]
fn ephemeral_keypair_generates_valid_p256_point() {
let rng = SystemRandom::new();
let (_sk, pub_bytes) = generate_ephemeral_keypair(&rng).unwrap();
assert_eq!(pub_bytes[0], 0x04, "SEC1 uncompressed prefix must be 0x04");
assert_eq!(
pub_bytes.len(),
65,
"SEC1 uncompressed P-256 point is 65 bytes"
);
}
#[test]
fn ecdh_is_symmetric() {
let rng = SystemRandom::new();
let (sk_a, pub_a) = generate_ephemeral_keypair(&rng).unwrap();
let (sk_b, pub_b) = generate_ephemeral_keypair(&rng).unwrap();
let shared_a = ecdh_shared_secret(&sk_a, &pub_b).unwrap();
let shared_b = ecdh_shared_secret(&sk_b, &pub_a).unwrap();
assert_eq!(shared_a, shared_b, "ECDH shared secret must be symmetric");
}
#[test]
fn hkdf_expand_deterministic_and_input_sensitive() {
let prk = [0x42u8; 32];
let mut out_a = [0u8; 16];
let mut out_b = [0u8; 16];
let mut out_c = [0u8; 16];
hkdf_expand(&prk, HKDF_INFO_SIGMA2, &mut out_a).unwrap();
hkdf_expand(&prk, HKDF_INFO_SIGMA2, &mut out_b).unwrap();
hkdf_expand(&prk, HKDF_INFO_SIGMA3, &mut out_c).unwrap();
assert_eq!(out_a, out_b, "HKDF must be deterministic");
assert_ne!(
out_a, out_c,
"Different info labels must produce different keys"
);
}
#[test]
fn transcript_hash_deterministic_and_input_sensitive() {
let h1 = transcript_hash(&[b"sigma1", b"sigma2"]);
let h2 = transcript_hash(&[b"sigma1", b"sigma2"]);
assert_eq!(h1, h2, "Transcript hash must be deterministic");
let h3 = transcript_hash(&[b"sigma1", b"sigma3"]);
assert_ne!(h1, h3, "Different messages must produce different hashes");
}
#[test]
fn transcript_hash_single_message_matches_sha256() {
let msg = b"test message for sigma";
let ours = transcript_hash(&[msg.as_slice()]);
let d = ring::digest::digest(&ring::digest::SHA256, msg);
assert_eq!(
ours,
d.as_ref(),
"transcript_hash of one message must equal SHA-256(message)"
);
}
#[test]
fn aead_round_trip() {
let key = [0x11u8; AEAD_KEY_LEN];
let nonce = *NONCE_TBE_DATA2;
let aad = b"associated data";
let plaintext = b"the quick brown fox jumps over the lazy dog";
let ciphertext = aead_encrypt(&key, &nonce, aad, plaintext).unwrap();
assert_eq!(ciphertext.len(), plaintext.len() + AEAD_TAG_LEN);
let decrypted = aead_decrypt(&key, &nonce, aad, &ciphertext).unwrap();
assert_eq!(
decrypted, plaintext,
"Decrypted output must match original plaintext"
);
}
#[test]
fn aead_tampered_ciphertext_rejected() {
let key = [0x11u8; AEAD_KEY_LEN];
let nonce = *NONCE_TBE_DATA3;
let aad = b"";
let mut ciphertext = aead_encrypt(&key, &nonce, aad, b"plaintext").unwrap();
ciphertext[0] ^= 1;
assert!(
matches!(
aead_decrypt(&key, &nonce, aad, &ciphertext),
Err(Error::EncryptedBlobDecryptionFailed)
),
"Tampered ciphertext must fail decryption"
);
}
#[test]
fn aead_tampered_tag_rejected() {
let key = [0x11u8; AEAD_KEY_LEN];
let nonce = *NONCE_TBE_DATA2;
let aad = b"some aad";
let mut ciphertext = aead_encrypt(&key, &nonce, aad, b"plaintext content").unwrap();
let tag_start = ciphertext.len() - AEAD_TAG_LEN;
ciphertext[tag_start] ^= 1;
assert!(
matches!(
aead_decrypt(&key, &nonce, aad, &ciphertext),
Err(Error::EncryptedBlobDecryptionFailed)
),
"Tampered tag must fail decryption"
);
}
#[test]
fn verify_mac_accepts_match() {
let a = [0x11u8; 16];
let b = [0x11u8; 16];
verify_mac(&a, &b).unwrap();
}
#[test]
fn verify_mac_rejects_mismatch() {
let a = [0x11u8; 16];
let mut b = a;
b[0] ^= 1;
assert!(
matches!(verify_mac(&a, &b), Err(Error::ResumptionMacMismatch)),
"Single-byte difference must fail MAC verify"
);
}
#[test]
fn sigma1_resume_mic_round_trip() {
let secret = [0x01u8; 32];
let random = [0x02u8; 32];
let rid = [0x03u8; 16];
let mic = compute_sigma1_resume_mic(&secret, &random, &rid).unwrap();
verify_sigma1_resume_mic(&secret, &random, &rid, &mic).unwrap();
}
#[test]
fn sigma1_resume_mic_rejects_wrong_secret() {
let secret_a = [0x01u8; 32];
let secret_b = [0xFFu8; 32]; let random = [0x02u8; 32];
let rid = [0x03u8; 16];
let mic = compute_sigma1_resume_mic(&secret_a, &random, &rid).unwrap();
assert!(
matches!(
verify_sigma1_resume_mic(&secret_b, &random, &rid, &mic),
Err(Error::ResumptionMacMismatch)
),
"Wrong shared_secret must cause MIC mismatch"
);
}
#[test]
fn sigma1_resume_mic_rejects_wrong_resumption_id() {
let secret = [0x01u8; 32];
let random = [0x02u8; 32];
let rid_a = [0x03u8; 16];
let rid_b = [0x04u8; 16];
let mic = compute_sigma1_resume_mic(&secret, &random, &rid_a).unwrap();
assert!(
matches!(
verify_sigma1_resume_mic(&secret, &random, &rid_b, &mic),
Err(Error::ResumptionMacMismatch)
),
"Wrong resumption_id must cause MIC mismatch"
);
}
#[test]
fn sigma2_resume_mic_round_trip() {
let secret = [0x10u8; 32];
let random = [0x20u8; 32];
let new_rid = [0x30u8; 16];
let mic = compute_sigma2_resume_mic(&secret, &random, &new_rid).unwrap();
verify_sigma2_resume_mic(&secret, &random, &new_rid, &mic).unwrap();
}
#[test]
fn sigma2_resume_mic_rejects_wrong_secret() {
let secret_a = [0x10u8; 32];
let secret_b = [0xABu8; 32];
let random = [0x20u8; 32];
let new_rid = [0x30u8; 16];
let mic = compute_sigma2_resume_mic(&secret_a, &random, &new_rid).unwrap();
assert!(
matches!(
verify_sigma2_resume_mic(&secret_b, &random, &new_rid, &mic),
Err(Error::ResumptionMacMismatch)
),
"Wrong shared_secret must cause Sigma2 MIC mismatch"
);
}
#[test]
fn sigma1_and_sigma2_resume_mics_differ() {
let secret = [0x42u8; 32];
let random = [0x43u8; 32];
let rid = [0x44u8; 16];
let mic1 = compute_sigma1_resume_mic(&secret, &random, &rid).unwrap();
let mic2 = compute_sigma2_resume_mic(&secret, &random, &rid).unwrap();
assert_ne!(
mic1, mic2,
"Sigma1 and Sigma2 resume MICs must differ (different keys and nonces)"
);
}
#[test]
fn derive_resume_session_keys_deterministic() {
let secret = [0x55u8; 32];
let random = [0x66u8; 32];
let rid = [0x77u8; 16];
let keys_a = derive_resume_session_keys(&secret, &random, &rid).unwrap();
let keys_b = derive_resume_session_keys(&secret, &random, &rid).unwrap();
assert_eq!(
keys_a, keys_b,
"Same inputs must produce identical key material"
);
}
#[test]
fn derive_resume_session_keys_input_sensitive() {
let secret = [0x55u8; 32];
let random_a = [0x66u8; 32];
let mut random_b = random_a;
random_b[0] ^= 1;
let rid = [0x77u8; 16];
let keys_a = derive_resume_session_keys(&secret, &random_a, &rid).unwrap();
let keys_b = derive_resume_session_keys(&secret, &random_b, &rid).unwrap();
assert_ne!(
keys_a, keys_b,
"Different initiator_random must produce different keys"
);
}
#[test]
fn derive_resume_session_keys_produces_48_bytes() {
let secret = [0x88u8; 32];
let random = [0x99u8; 32];
let rid = [0xAAu8; 16];
let keys = derive_resume_session_keys(&secret, &random, &rid).unwrap();
assert_eq!(keys.len(), 48);
let r2i = &keys[0..16];
let i2r = &keys[16..32];
let att = &keys[32..48];
assert_ne!(r2i, i2r, "r2i and i2r keys must differ");
assert_ne!(r2i, att, "r2i and attestation keys must differ");
assert_ne!(i2r, att, "i2r and attestation keys must differ");
}
}