Skip to main content

CaseResponder

Struct CaseResponder 

Source
pub struct CaseResponder { /* private fields */ }
Expand description

Responder-side CASE state machine (new-session path).

Handles the Sigma1 / Sigma2 / Sigma3 handshake from the responder’s (device’s) perspective. Sans-IO: the caller feeds raw bytes in via handle_sigma1 and handle_sigma3, and reads raw bytes out via next_message.

§Construction

  • CaseResponder::new — production constructor; uses the OS CSPRNG.
  • new_using_rng (crate-internal) — deterministic constructor for tests; accepts an injectable ring::rand::SecureRandom.

§Driving the handshake

  1. Receive Sigma1 bytes from the peer.
  2. Call handle_sigma1 with those bytes.
    • Returns Sigma1Outcome::NewSession for a fresh session (M4.1).
    • Returns Err if the dest_id doesn’t match our fabric identity.
  3. Call next_message → get Sigma2 bytes; send them.
  4. Receive Sigma3 bytes from the peer.
  5. Call handle_sigma3 with those bytes.
  6. Send a StatusReport: Success to the initiator.
  7. Call finish to retrieve CaseSessionOutput.

Use expected_inbound at any point to query which message the machine is currently waiting to receive.

Implementations§

Source§

impl CaseResponder

Source

pub fn new( credentials: CaseCredentials, trusted_roots: TrustedRoots, responder_session_id: u16, now: MatterTime, ) -> Result<Self>

Construct a responder using the OS CSPRNG.

Pre-samples the ephemeral keypair and 32-byte responder random so that handle_sigma1 cannot fail due to randomness.

responder_session_id is the non-zero secured-session id this responder advertises in Sigma2 (tag 2) for the peer to address us by; it is recorded as CaseSessionOutput.local.session_id once the handshake completes.

now is the wall-clock instant against which the initiator’s operational certificate chain is checked for temporal validity during Sigma3. This crate never reads the system clock; the caller (controller layer) must supply the real time.

§Errors

Returns Error::EphemeralKeyGenerationFailed if the OS RNG fails (extremely unlikely in practice).

Source

pub fn expected_inbound(&self) -> Option<CaseMessageKind>

Returns the CASE message kind the machine is currently waiting to receive, or None if the machine is in an outbound-only state, has completed, or has been poisoned.

Source

pub fn handle_sigma1(&mut self, bytes: &[u8]) -> Result<Sigma1Outcome>

Process the inbound Sigma1 message.

Verifies that the dest_id in Sigma1 matches the responder’s fabric identity.

New-session path: If Sigma1 carries no resumption fields, computes the ECDH shared secret, builds and encrypts TBEData2, signs TBSData2 with our NOC key, encodes the Sigma2 message, advances to ReadyToSendSigma2, and returns Sigma1Outcome::NewSession.

Resumption path: If Sigma1 carries both resumption_id (tag 6) and initiator_resume_mic (tag 7), transitions to AwaitingResumptionDecision and returns Sigma1Outcome::ResumptionRequested. The caller must then look up the ResumptionRecord and call either accept_resumption or reject_resumption.

§Errors
Source

pub fn accept_resumption(&mut self, record: ResumptionRecord) -> Result<()>

Accept a resumption attempt: verify the initiator’s MIC, derive session keys, build the Sigma2_Resume message, and advance to ReadyToSendSigma2Resume.

Must be called after handle_sigma1 returns Sigma1Outcome::ResumptionRequested with the caller-supplied ResumptionRecord that matches id in the outcome.

§Resumption session-key layout

Pinned from matter.js NodeSession.create (isResumption = true branch, responder isInitiator = false):

keys = HKDF(ikm  = shared_secret,
            salt = initiatorRandom || OLD_resumption_id,
            info = "SessionResumptionKeys",
            len  = 48)
// Responder (isInitiator=false) key assignment:
keys[0..16]  → r2i_key          (responder encrypts to initiator)
keys[16..32] → i2r_key          (responder decrypts from initiator)
keys[32..48] → attestation_challenge

This layout is the same byte positions as the initiator uses, but the semantic labels align with the responder’s direction (see matter.js NodeSession.ts isInitiator=false branch).

§Errors
Source

pub fn reject_resumption(&mut self) -> Result<()>

Decline a resumption attempt and fall back to the new-session path.

Must be called after handle_sigma1 returns Sigma1Outcome::ResumptionRequested. After this call, the state machine is in the same state as it would be after a regular Sigma1 (new-session path). The next call to next_message will return Sigma2 bytes (not Sigma2_Resume).

§Errors
Source

pub fn next_message(&mut self) -> Result<Vec<u8>>

Retrieve the next outbound message and advance the state machine.

New-session path: Returns the Sigma2 bytes and advances to AwaitingSigma3. Must be called after a successful handle_sigma1 that returned Sigma1Outcome::NewSession, or after reject_resumption.

Resumption path: Returns the Sigma2_Resume bytes and advances directly to Complete. Must be called after a successful accept_resumption. There is no Sigma3_Resume — the handshake completes after Sigma2_Resume is sent (confirmed from matter.js).

§Errors
Source

pub fn handle_sigma3(&mut self, bytes: &[u8]) -> Result<()>

Process the inbound Sigma3 message, verify the initiator’s credentials, and derive the final session keys.

§Sigma3 processing steps
  1. Derive S3K via HKDF (same salt construction as initiator, mirrored).
  2. AES-128-CCM decrypt the encrypted blob using S3K and the NCASE_Sigma3N nonce.
  3. Parse TBEData3 = { initiatorNoc, initiatorIcac?, signature }.
  4. Validate the initiator’s NOC chain against trusted_roots.
  5. Extract initiator NodeId + FabricId from NOC subject.
  6. Verify FabricId matches our credentials.
  7. Verify the initiator’s ECDSA signature over TBSData3.
  8. Derive final session keys; assign i2r/r2i with responder convention.
§Key assignment convention (responder, isInitiator=false in matter.js)
decryptKey  = keys[0..16]   (responder decrypts initiator traffic = i2r)
encryptKey  = keys[16..32]  (responder encrypts to initiator = r2i)
attestationChallenge = keys[32..48]

Pinned from NodeSession.ts, isInitiator=false branch.

§Errors
Source

pub fn finish(self) -> Result<CaseSessionOutput>

Finalise the session and retrieve the derived CaseSessionOutput.

May only be called after handle_sigma3 has completed (i.e., the state machine is in the Complete state).

§Errors

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.