Skip to main content

CaseInitiator

Struct CaseInitiator 

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

Initiator-side CASE state machine (new-session and resumption paths).

Drives the Sigma1 / Sigma2 / Sigma3 handshake (or the faster Sigma1 / Sigma2_Resume resumption path) from the initiator’s perspective. Sans-IO: the caller feeds raw bytes in via handle_sigma2 or handle_sigma2_resume and reads raw bytes out via start and next_message.

§Construction

New-session path:

  • CaseInitiator::new — production constructor; uses the OS CSPRNG.
  • new_using_rng (crate-internal) — deterministic constructor for tests.

Resumption path (M4.2):

§Driving the new-session handshake

  1. Call start → get Sigma1 bytes; send them.
  2. Receive Sigma2 bytes from the peer.
  3. Call handle_sigma2 with those bytes.
  4. Call next_message → get Sigma3 bytes; send them.
  5. After the peer confirms with a StatusReport: Success, call finish to retrieve CaseSessionOutput.

§Driving the resumption handshake

  1. Call start → get Sigma1 bytes (with resumption fields); send them.
  2. Receive the response from the peer:

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

Implementations§

Source§

impl CaseInitiator

Source

pub fn new( credentials: CaseCredentials, trusted_roots: TrustedRoots, peer_node_id: u64, peer_fabric_id: u64, initiator_session_id: u16, now: MatterTime, ) -> Result<Self>

Construct an initiator using the OS CSPRNG (new-session path).

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

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

For the resumption path, use new_with_resumption.

now is the wall-clock instant against which the peer’s operational certificate chain is checked for temporal validity during Sigma2. 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 new_with_resumption( credentials: CaseCredentials, trusted_roots: TrustedRoots, peer_node_id: u64, peer_fabric_id: u64, record: ResumptionRecord, initiator_session_id: u16, now: MatterTime, ) -> Result<Self>

Construct an initiator with a prior-session ResumptionRecord, using the OS CSPRNG.

When start is called, the Sigma1 message will include resumption_id (tag 6) and initiator_resume_mic (tag 7). The responder may reply with Sigma2_Resume (call handle_sigma2_resume) or fall back to a regular Sigma2 (call handle_sigma2).

initiator_session_id is the non-zero secured-session id we advertise in Sigma1 (tag 2) for the peer to address us by, exactly as in new.

now is the wall-clock instant against which the peer’s operational certificate chain is checked for temporal validity during Sigma2 (used only on the non-resumption fallback path). See new.

§Errors

Returns Error::EphemeralKeyGenerationFailed if the OS RNG fails.

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.

On the resumption path (after start() was called with a resumption record), returns Sigma2Resume to indicate that the peer may send either Sigma2_Resume (accepted) or a plain Sigma2 (declined fallback). The returned value is advisory — the caller must inspect the actual inbound message type and route to the appropriate handle_* method.

Source

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

Produce the Sigma1 message bytes and advance to AwaitingSigma2.

On the resumption path (constructed with new_with_resumption), the emitted Sigma1 will include resumption_id (tag 6) and initiator_resume_mic (tag 7), signalling to the responder that it may send Sigma2_Resume instead of Sigma2.

§Errors
Source

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

Process the inbound Sigma2 message, verify the peer’s credentials, and produce Sigma3.

After this call succeeds, call next_message to retrieve the Sigma3 bytes that must be sent to the responder.

§Sigma2 processing steps
  1. Parse Sigma2 TLV.
  2. ECDH shared secret from our ephemeral secret + peer’s ephemeral public key.
  3. Derive S2K via HKDF (see module doc for salt composition).
  4. AES-128-CCM decrypt the encrypted blob using S2K and the NCASE_Sigma2N nonce.
  5. Parse TBEData2 = { responderNoc, responderIcac?, signature, resumptionId }.
  6. Validate the peer’s NOC chain against trusted_roots.
  7. Check that the NOC’s NodeId and FabricId match expectations.
  8. Verify the peer’s ECDSA signature over TBSData2.
  9. Build TBSData3, sign with our NOC’s private key.
  10. Encode TBEData3, encrypt with S3K and NCASE_Sigma3N nonce.
  11. Derive the final session keys.
§Errors
Source

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

Process the inbound Sigma2_Resume message and complete the resumption handshake.

May only be called after start when the initiator was constructed with new_with_resumption (i.e., the sent Sigma1 carried resumption fields).

No Sigma3_Resume or Sigma3 to send. After this call succeeds the handshake is complete from the initiator’s side. Call finish directly to obtain the CaseSessionOutput.

§Resumption session-key layout

Pinned from matter.js NodeSession.create (isResumption = true branch):

keys = HKDF(ikm  = shared_secret,
            salt = initiatorRandom || OLD_resumption_id,
            info = "SessionResumptionKeys",
            len  = 48)
keys[0..16]  → r2i_key          (responder-to-initiator)
keys[16..32] → i2r_key          (initiator-to-responder)
keys[32..48] → attestation_challenge

Note the reversed byte assignment vs the new-session path (where keys[0..16] is i2r and keys[16..32] is r2i).

§Errors
Source

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

Retrieve the next outbound message (Sigma3) and advance to Complete.

Must be called after a successful handle_sigma2.

§Errors
Source

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

Finalise the session and retrieve the derived CaseSessionOutput.

May only be called after next_message has emitted Sigma3 (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.