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):
CaseInitiator::new_with_resumption— production constructor with a prior-sessionResumptionRecord.new_with_resumption_using_rng(crate-internal) — deterministic variant.
§Driving the new-session handshake
- Call
start→ get Sigma1 bytes; send them. - Receive Sigma2 bytes from the peer.
- Call
handle_sigma2with those bytes. - Call
next_message→ get Sigma3 bytes; send them. - After the peer confirms with a
StatusReport: Success, callfinishto retrieveCaseSessionOutput.
§Driving the resumption handshake
- Call
start→ get Sigma1 bytes (with resumption fields); send them. - Receive the response from the peer:
- If the peer accepts resumption: call
handle_sigma2_resume. Then callfinishdirectly (no Sigma3 to send). - If the peer declines (sends a regular Sigma2): call
handle_sigma2normally, thennext_messageandfinish.
- If the peer accepts resumption: call
Use expected_inbound at any point to query
which message the machine is currently waiting to receive.
Implementations§
Source§impl CaseInitiator
impl CaseInitiator
Sourcepub fn new(
credentials: CaseCredentials,
trusted_roots: TrustedRoots,
peer_node_id: u64,
peer_fabric_id: u64,
initiator_session_id: u16,
now: MatterTime,
) -> Result<Self>
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).
Sourcepub 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>
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.
Sourcepub fn expected_inbound(&self) -> Option<CaseMessageKind>
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.
Sourcepub fn start(&mut self) -> Result<Vec<u8>>
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
Error::UnexpectedCaseMessageif called from the wrong state.Error::Codecon TLV encoding failure.Error::EphemeralKeyGenerationFailedif MIC computation fails (only possible if AES-CCM internal state is inconsistent — not expected in practice).
Sourcepub fn handle_sigma2(&mut self, bytes: &[u8]) -> Result<()>
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
- Parse Sigma2 TLV.
- ECDH shared secret from our ephemeral secret + peer’s ephemeral public key.
- Derive S2K via HKDF (see module doc for salt composition).
- AES-128-CCM decrypt the encrypted blob using S2K and the
NCASE_Sigma2Nnonce. - Parse
TBEData2={ responderNoc, responderIcac?, signature, resumptionId }. - Validate the peer’s NOC chain against
trusted_roots. - Check that the NOC’s
NodeIdandFabricIdmatch expectations. - Verify the peer’s ECDSA signature over
TBSData2. - Build
TBSData3, sign with our NOC’s private key. - Encode
TBEData3, encrypt with S3K andNCASE_Sigma3Nnonce. - Derive the final session keys.
§Errors
Error::UnexpectedCaseMessageif called from the wrong state.Error::Codecon TLV decode / encode failure.Error::InvalidParameterif the peer’s ephemeral public key is not a valid P-256 point.Error::EncryptedBlobDecryptionFailedif the encrypted blob fails AEAD verification.Error::InvalidPeerNocChainif chain validation fails.Error::FabricIdMismatch/Error::PeerNodeIdMismatchif the peer’s identity doesn’t match expectations.Error::PeerSignatureInvalidif the peer’s ECDSA signature fails.Error::SigningFailedif our own signing operation fails.Error::EphemeralKeyGenerationFailedon HKDF failure.
Sourcepub fn handle_sigma2_resume(&mut self, bytes: &[u8]) -> Result<()>
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_challengeNote the reversed byte assignment vs the new-session path
(where keys[0..16] is i2r and keys[16..32] is r2i).
§Errors
Error::UnexpectedCaseMessageif called from the wrong state or if the initiator never attempted resumption.Error::Codecon TLV decode failure.Error::ResumptionMacMismatchif thesigma2_resume_micin the message does not verify.Error::EphemeralKeyGenerationFailedon HKDF failure.
Sourcepub fn next_message(&mut self) -> Result<Vec<u8>>
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
Error::UnexpectedCaseMessageif called from the wrong state.
Sourcepub fn finish(self) -> Result<CaseSessionOutput>
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
Error::HandshakeIncompleteif called before all handshake phases have completed.