1use core::{mem::MaybeUninit, num::NonZeroU8};
19
20#[cfg(feature = "case-resumption")]
21use rand_core::RngCore;
22
23#[cfg(feature = "case-resumption")]
24use super::casep::{
25 compute_resume_mic, compute_resumption_session_keys, derive_resume_key, verify_resume_mic,
26 ResumeKeyKind, CASE_RANDOM_LEN, CASE_RESUMPTION_ID_LEN, RESUME1_MIC_NONCE, RESUME2_MIC_NONCE,
27};
28use super::casep::{CaseP, CaseRandom, CaseResumptionId, CaseSessionKeys};
29#[cfg(feature = "case-resumption")]
30use super::resumption::ResumableSession;
31use super::CASE_LARGE_BUF_SIZE;
32use crate::alloc;
33use crate::cert::CertRef;
34#[cfg(feature = "case-resumption")]
35use crate::crypto::{CanonAeadKey, AEAD_TAG_LEN};
36use crate::crypto::{CanonPkcSignature, CanonPkcSignatureRef, Crypto, Hash, AEAD_CANON_KEY_LEN};
37use crate::error::Error;
38#[cfg(feature = "case-resumption")]
39use crate::error::ErrorCode;
40use crate::sc::{
41 check_opcode, complete_with_status, expect_opcode, sc_write, OpCode, SCStatusCodes,
42 SessionParameters,
43};
44#[cfg(feature = "case-resumption")]
45use crate::sc::{GeneralCode, StatusReport};
46use crate::tlv::{get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, ToTLV};
47use crate::transport::exchange::Exchange;
48use crate::transport::session::{NocCatIds, ReservedSession, SessionMode};
49use crate::utils::init::{init, Init, InitMaybeUninit};
50#[cfg(feature = "case-resumption")]
51use crate::utils::storage::ReadBuf;
52
53#[derive(FromTLV, Debug)]
55#[cfg_attr(feature = "defmt", derive(defmt::Format))]
56#[tlvargs(start = 1, lifetime = "'a")]
57struct Sigma1Req<'a> {
58 initiator_random: OctetStr<'a>,
60 initiator_sessid: u16,
62 dest_id: OctetStr<'a>,
64 peer_pub_key: OctetStr<'a>,
66 session_parameters: Option<SessionParameters>,
68 resumption_id: Option<OctetStr<'a>>,
70 initiator_resume_mic: Option<OctetStr<'a>>,
72}
73
74#[derive(FromTLV, Debug)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77#[tlvargs(start = 1, lifetime = "'a")]
78struct Sigma3Decrypt<'a> {
79 initiator_noc: OctetStr<'a>,
81 initiator_icac: Option<OctetStr<'a>>,
83 signature: OctetStr<'a>,
85}
86
87pub struct CaseResponder<'a, C: Crypto> {
89 crypto: &'a C,
90 casep: CaseP<'a, C>,
92}
93
94impl<'a, C: Crypto> CaseResponder<'a, C> {
95 #[inline(always)]
97 pub const fn new(crypto: &'a C) -> Self {
98 Self {
99 crypto,
100 casep: CaseP::new(),
101 }
102 }
103
104 pub fn init(crypto: &'a C) -> impl Init<Self> {
106 init!(Self {
107 crypto,
108 casep <- CaseP::init(),
109 })
110 }
111
112 pub async fn handle(&mut self, mut exchange: Exchange<'_>) -> Result<(), Error> {
117 let mut session = ReservedSession::reserve(exchange.matter(), self.crypto).await?;
118
119 #[cfg(feature = "case-resumption")]
131 if self
132 .try_handle_sigma1_resume(&mut exchange, &mut session)
133 .await?
134 {
135 return Ok(());
136 }
137
138 self.handle_casesigma1(&mut exchange, &mut session).await?;
139
140 exchange.recv_fetch().await?;
141
142 self.handle_casesigma3(&mut exchange, session).await?;
143
144 exchange.acknowledge().await?;
145
146 Ok(())
147 }
148
149 async fn handle_casesigma1(
157 &mut self,
158 exchange: &mut Exchange<'_>,
159 session: &mut ReservedSession<'_>,
160 ) -> Result<(), Error> {
161 check_opcode(exchange, OpCode::CASESigma1)?;
162
163 let req = Sigma1Req::from_tlv(&get_root_node_struct(exchange.rx()?.payload())?)?;
164
165 if req.resumption_id.is_some() != req.initiator_resume_mic.is_some() {
170 error!("Sigma1 has mismatched resumptionID/initiatorResumeMIC presence; rejecting");
171 complete_with_status(exchange, SCStatusCodes::InvalidParameter, &[]).await?;
172
173 return Ok(());
174 }
175
176 let local_fabric_idx = exchange.with_state(|state| {
177 Ok(state
178 .fabrics
179 .get_by_dest_id(self.crypto, req.initiator_random.0, req.dest_id.0)
180 .map(|fabric| fabric.fab_idx()))
181 })?;
182
183 if local_fabric_idx.is_none() {
184 error!("Fabric Index mismatch");
185 complete_with_status(exchange, SCStatusCodes::NoSharedTrustRoots, &[]).await?;
186
187 return Ok(());
188 }
189
190 let local_sessid = exchange.with_state(|state| Ok(state.sessions.get_next_sess_id()))?;
191
192 let mut our_random = MaybeUninit::<CaseRandom>::uninit(); let our_random = our_random.init_with(CaseRandom::init());
194
195 let mut resumption_id = MaybeUninit::<CaseResumptionId>::uninit(); let resumption_id = resumption_id.init_with(CaseResumptionId::init());
197
198 let mut tt_hash = MaybeUninit::<Hash>::uninit(); let tt_hash = tt_hash.init_with(Hash::init());
200
201 self.casep.start(
202 self.crypto,
203 req.initiator_sessid,
204 local_sessid,
205 unwrap!(local_fabric_idx).get(),
206 req.peer_pub_key.0.try_into()?,
207 exchange.rx()?.payload(),
208 our_random,
209 resumption_id,
210 tt_hash,
211 )?;
212
213 if let Some(params) = req.session_parameters.as_ref() {
220 exchange.with_state(|state| {
221 exchange
222 .id()
223 .session(&mut state.sessions)
224 .set_peer_session_params(params);
225 Ok(())
226 })?;
227 session.set_peer_session_params(params)?;
228 }
229
230 trace!(
231 "Destination ID matched to fabric index {}",
232 self.casep.local_fabric_idx()
233 );
234
235 let mut signature = MaybeUninit::<CanonPkcSignature>::uninit(); let signature = signature.init_with(CanonPkcSignature::init());
241 let mut signature_generated = false;
242 let mut tt_updated = false;
243 exchange
244 .send_with(|exchange, tw| {
245 exchange.with_state(|state| {
246 let fabric = NonZeroU8::new(self.casep.local_fabric_idx())
247 .and_then(|fabric_idx| state.fabrics.get(fabric_idx));
248
249 let Some(fabric) = fabric else {
250 return sc_write(tw, SCStatusCodes::NoSharedTrustRoots, &[]);
251 };
252
253 if !signature_generated {
254 let sign_buf = tw.empty_as_mut_slice();
257
258 self.casep.compute_sigma2_signature(
259 self.crypto,
260 fabric,
261 sign_buf,
262 signature,
263 )?;
264 signature_generated = true;
265 }
266
267 tw.start_struct(&TLVTag::Anonymous)?;
268 tw.str(&TLVTag::Context(1), our_random.access())?;
269 tw.u16(&TLVTag::Context(2), local_sessid)?;
270 tw.str(&TLVTag::Context(3), self.casep.our_pub_key().access())?;
271
272 tw.str_cb(&TLVTag::Context(4), |buf| {
273 self.casep.sigma2_encrypt(
274 self.crypto,
275 fabric,
276 our_random.reference(),
277 tt_hash.reference(),
278 signature.reference(),
279 resumption_id.reference(),
280 buf,
281 )
282 })?;
283
284 let session_params = crate::sc::SessionParameters {
286 max_paths_per_invoke: Some(
287 exchange.matter().dev_det().max_paths_per_invoke,
288 ),
289 ..Default::default()
290 };
291 session_params.to_tlv(&TLVTag::Context(5), &mut *tw)?;
292
293 tw.end_container()?;
294
295 if !tt_updated {
296 self.casep.update_tt(tw.as_slice())?;
297 tt_updated = true;
298 }
299
300 Ok(Some(OpCode::CASESigma2.into()))
301 })
302 })
303 .await
304 }
305
306 async fn handle_casesigma3(
312 &mut self,
313 exchange: &mut Exchange<'_>,
314 mut session: ReservedSession<'_>,
315 ) -> Result<(), Error> {
316 expect_opcode(exchange, OpCode::CASESigma3).await?;
317
318 let status = exchange.with_state(|state| {
319 let sess = exchange.id().session(&mut state.sessions);
320
321 let fabric = NonZeroU8::new(self.casep.local_fabric_idx())
322 .and_then(|fabric_idx| state.fabrics.get(fabric_idx));
323 if let Some(fabric) = fabric {
324 let req = match get_root_node_struct(exchange.rx()?.payload()) {
331 Ok(req) => req,
332 Err(e) => {
333 error!("Sigma3 outer TLV parse failed: {}", e);
334 return Ok(SCStatusCodes::InvalidParameter);
335 }
336 };
337 let encrypted = match req.structure().and_then(|s| s.ctx(1)).and_then(|c| c.str()) {
338 Ok(s) => s,
339 Err(e) => {
340 error!("Sigma3 encrypted field parse failed: {}", e);
341 return Ok(SCStatusCodes::InvalidParameter);
342 }
343 };
344
345 let mut decrypted = alloc!([0; CASE_LARGE_BUF_SIZE]); if encrypted.len() > decrypted.len() {
347 error!(
348 "Encrypted Sigma3 data too large ({} bytes)",
349 encrypted.len()
350 );
351 return Ok(SCStatusCodes::InvalidParameter);
352 }
353
354 let decrypted = &mut decrypted[..encrypted.len()];
355 decrypted.copy_from_slice(encrypted);
356
357 let len =
358 match self
359 .casep
360 .sigma3_decrypt(self.crypto, fabric.ipk().op_key(), decrypted)
361 {
362 Ok(len) => len,
363 Err(e) => {
364 error!("Sigma3 AEAD decrypt failed: {}", e);
365 return Ok(SCStatusCodes::InvalidParameter);
366 }
367 };
368 let decrypted = &decrypted[..len];
369 let decrypted_req: Sigma3Decrypt<'_> = match get_root_node_struct(decrypted)
370 .and_then(|n| Sigma3Decrypt::from_tlv(&n))
371 {
372 Ok(req) => req,
373 Err(e) => {
374 error!("Sigma3 decrypted TLV parse failed: {}", e);
375 return Ok(SCStatusCodes::InvalidParameter);
376 }
377 };
378
379 let initiator_noc = CertRef::new(TLVElement::new(decrypted_req.initiator_noc.0));
380 let initiator_icac = decrypted_req
381 .initiator_icac
382 .map(|icac| CertRef::new(TLVElement::new(icac.0)));
383
384 let mut buf = alloc!([0; CASE_LARGE_BUF_SIZE]); let buf = &mut buf[..];
386 if let Err(e) = self.casep.validate_certs(
387 self.crypto,
388 state.rtc.utc_time(),
389 fabric,
390 &initiator_noc,
391 initiator_icac.as_ref(),
392 buf,
393 ) {
394 error!("Certificate Chain doesn't match: {}", e);
395 Ok(SCStatusCodes::InvalidParameter)
396 } else if let Err(e) = self.casep.validate_peer_tbs_signature(
397 self.crypto,
398 decrypted_req.initiator_noc.0,
399 decrypted_req.initiator_icac.map(|a| a.0),
400 &initiator_noc,
401 CanonPkcSignatureRef::try_new(decrypted_req.signature.0)?,
402 buf,
403 ) {
404 error!("Sigma3 Signature doesn't match: {}", e);
405 Ok(SCStatusCodes::InvalidParameter)
406 } else {
407 let mut peer_catids: NocCatIds = Default::default();
409 initiator_noc.get_cat_ids(&mut peer_catids)?;
410 self.casep.update_tt(exchange.rx()?.payload())?;
411
412 let mut session_keys = MaybeUninit::<CaseSessionKeys>::uninit(); let session_keys = session_keys.init_with(CaseSessionKeys::init());
414 self.casep.compute_session_keys(
415 self.crypto,
416 fabric.ipk().op_key(),
417 session_keys,
418 )?;
419
420 let peer_addr = sess.get_peer_addr();
421
422 let (dec_key, remaining) = session_keys
423 .reference()
424 .split::<AEAD_CANON_KEY_LEN, { AEAD_CANON_KEY_LEN * 2 }>();
425 let (enc_key, att_challenge) =
426 remaining.split::<AEAD_CANON_KEY_LEN, AEAD_CANON_KEY_LEN>();
427
428 session.update_with_state(
429 state,
430 fabric.node_id(),
431 initiator_noc.get_node_id()?,
432 self.casep.peer_sessid(),
433 self.casep.local_sessid(),
434 peer_addr,
435 SessionMode::Case {
436 fab_idx: unwrap!(NonZeroU8::new(self.casep.local_fabric_idx())),
438 cat_ids: peer_catids,
439 },
440 Some(dec_key),
441 Some(enc_key),
442 Some(att_challenge),
443 Some(self.casep.shared_secret()),
444 )?;
445
446 #[cfg(feature = "case-resumption")]
454 state.resumption.insert_or_update(ResumableSession {
455 fab_idx: unwrap!(NonZeroU8::new(self.casep.local_fabric_idx())),
457 peer_nodeid: initiator_noc.get_node_id()?,
458 peer_cat_ids: peer_catids,
459 resumption_id: super::casep::CaseResumptionId::new_from_ref(
460 self.casep.resumption_id(),
461 ),
462 shared_secret: crate::crypto::CanonPkcSharedSecret::new_from_ref(
463 self.casep.shared_secret(),
464 ),
465 });
466
467 Ok(SCStatusCodes::SessionEstablishmentSuccess)
468 }
469 } else {
470 Ok(SCStatusCodes::NoSharedTrustRoots)
471 }
472 })?;
473
474 if matches!(status, SCStatusCodes::SessionEstablishmentSuccess) {
475 session.complete();
483
484 #[cfg(feature = "case-resumption")]
487 exchange.matter().transport().notify_resumption_dirty();
488 }
489
490 complete_with_status(exchange, status, &[]).await
491 }
492
493 #[cfg(feature = "case-resumption")]
511 async fn try_handle_sigma1_resume(
512 &mut self,
513 exchange: &mut Exchange<'_>,
514 session: &mut ReservedSession<'_>,
515 ) -> Result<bool, Error> {
516 check_opcode(exchange, OpCode::CASESigma1)?;
517
518 let (init_random, init_sessid, incoming_rid, incoming_mic, peer_params) = {
525 let payload = exchange.rx()?.payload();
526 let req = Sigma1Req::from_tlv(&get_root_node_struct(payload)?)?;
527
528 let (Some(rid), Some(mic)) = (
533 req.resumption_id.as_ref(),
534 req.initiator_resume_mic.as_ref(),
535 ) else {
536 return Ok(false);
537 };
538
539 if rid.0.len() != CASE_RESUMPTION_ID_LEN || mic.0.len() != AEAD_TAG_LEN {
540 return Ok(false);
542 }
543
544 let random_bytes: &[u8; CASE_RANDOM_LEN] = req
545 .initiator_random
546 .0
547 .try_into()
548 .map_err(|_| ErrorCode::InvalidData)?;
549 let mut init_random = CaseRandom::new();
550 init_random.load_from_array(random_bytes);
551
552 let rid_bytes: &[u8; CASE_RESUMPTION_ID_LEN] =
553 rid.0.try_into().map_err(|_| ErrorCode::InvalidData)?;
554 let mut incoming_rid = CaseResumptionId::new();
555 incoming_rid.load_from_array(rid_bytes);
556
557 let mut incoming_mic = [0u8; AEAD_TAG_LEN];
558 incoming_mic.copy_from_slice(mic.0);
559
560 (
561 init_random,
562 req.initiator_sessid,
563 incoming_rid,
564 incoming_mic,
565 req.session_parameters.clone(),
566 )
567 };
568
569 let record = exchange.with_state(|state| {
571 Ok::<_, Error>(
572 state
573 .resumption
574 .find_by_resumption_id(incoming_rid.reference().access())
575 .cloned(),
576 )
577 })?;
578
579 let Some(record) = record else {
580 debug!(
581 "CASE Sigma1 resumption: no cached record for the requested resumption id; \
582 falling back to full handshake"
583 );
584 return Ok(false);
585 };
586
587 let mut s1rk = CanonAeadKey::new();
589 derive_resume_key(
590 self.crypto,
591 ResumeKeyKind::S1rk,
592 record.shared_secret.reference(),
593 init_random.reference(),
594 record.resumption_id.reference(),
595 &mut s1rk,
596 )?;
597
598 if verify_resume_mic(
599 self.crypto,
600 s1rk.reference(),
601 RESUME1_MIC_NONCE,
602 &incoming_mic,
603 )
604 .is_err()
605 {
606 debug!(
607 "CASE Sigma1 resumption: Resume1MIC verify failed for peer node id \
608 0x{:x} on fabric {}; falling back to full handshake",
609 record.peer_nodeid,
610 record.fab_idx.get()
611 );
612 return Ok(false);
613 }
614
615 let mut new_rid = CaseResumptionId::new();
617 self.crypto.rand()?.fill_bytes(new_rid.access_mut());
618
619 let mut s2rk = CanonAeadKey::new();
620 derive_resume_key(
621 self.crypto,
622 ResumeKeyKind::S2rk,
623 record.shared_secret.reference(),
624 init_random.reference(),
625 new_rid.reference(),
626 &mut s2rk,
627 )?;
628
629 let mut resume2_mic = [0u8; AEAD_TAG_LEN];
630 compute_resume_mic(
631 self.crypto,
632 s2rk.reference(),
633 RESUME2_MIC_NONCE,
634 &mut resume2_mic,
635 )?;
636
637 let local_sessid = exchange.with_state(|state| Ok(state.sessions.get_next_sess_id()))?;
639
640 if let Some(ref params) = peer_params {
646 exchange.with_state(|state| {
647 exchange
648 .id()
649 .session(&mut state.sessions)
650 .set_peer_session_params(params);
651 Ok::<_, Error>(())
652 })?;
653 session.set_peer_session_params(params)?;
654 }
655
656 let responder_session_params = SessionParameters {
658 max_paths_per_invoke: Some(exchange.matter().dev_det().max_paths_per_invoke),
659 ..Default::default()
660 };
661 let new_rid_bytes: [u8; CASE_RESUMPTION_ID_LEN] = *new_rid.reference().access();
662
663 exchange
664 .send_with(|_, tw| {
665 tw.start_struct(&TLVTag::Anonymous)?;
666 tw.str(&TLVTag::Context(1), &new_rid_bytes)?;
667 tw.str(&TLVTag::Context(2), &resume2_mic)?;
668 tw.u16(&TLVTag::Context(3), local_sessid)?;
669 responder_session_params.to_tlv(&TLVTag::Context(4), &mut *tw)?;
670 tw.end_container()?;
671
672 Ok(Some(OpCode::CASESigma2Resume.into()))
673 })
674 .await?;
675
676 let mut session_keys = MaybeUninit::<CaseSessionKeys>::uninit();
678 let session_keys = session_keys.init_with(CaseSessionKeys::init());
679 compute_resumption_session_keys(
683 self.crypto,
684 record.shared_secret.reference(),
685 init_random.reference(),
686 record.resumption_id.reference(),
687 session_keys,
688 )?;
689
690 let (dec_key, remaining) = session_keys
693 .reference()
694 .split::<AEAD_CANON_KEY_LEN, { AEAD_CANON_KEY_LEN * 2 }>();
695 let (enc_key, att_challenge) = remaining.split::<AEAD_CANON_KEY_LEN, AEAD_CANON_KEY_LEN>();
696
697 exchange.with_state(|state| {
699 let local_nodeid = state
700 .fabrics
701 .get(record.fab_idx)
702 .map(|f| f.node_id())
703 .ok_or(ErrorCode::Invalid)?;
704 let peer_addr = exchange.id().session(&mut state.sessions).get_peer_addr();
705
706 session.update_with_state(
707 state,
708 local_nodeid,
709 record.peer_nodeid,
710 init_sessid,
711 local_sessid,
712 peer_addr,
713 SessionMode::Case {
714 fab_idx: record.fab_idx,
715 cat_ids: record.peer_cat_ids,
716 },
717 Some(dec_key),
718 Some(enc_key),
719 Some(att_challenge),
720 Some(record.shared_secret.reference()),
721 )
722 })?;
723
724 exchange.recv_fetch().await?;
726
727 let ok = {
728 let rx = exchange.rx()?;
729 let meta = rx.meta();
730 if meta.proto_opcode != OpCode::StatusReport as u8 {
731 warn!(
732 "CASE resumption: expected StatusReport after Sigma2_Resume, got {}",
733 meta.proto_opcode
734 );
735 false
736 } else {
737 let mut rb = ReadBuf::new(rx.payload());
738 match StatusReport::read(&mut rb) {
739 Ok(status)
740 if status.general_code == GeneralCode::Success
741 && status.proto_code
742 == SCStatusCodes::SessionEstablishmentSuccess as u16 =>
743 {
744 true
745 }
746 Ok(status) => {
747 warn!(
748 "CASE resumption: SigmaFinished failed: general={:?}, proto_code={}",
749 status.general_code, status.proto_code
750 );
751 false
752 }
753 Err(e) => {
754 warn!("CASE resumption: failed to parse SigmaFinished: {}", e);
755 false
756 }
757 }
758 }
759 };
760
761 if !ok {
762 exchange.acknowledge().await?;
768 return Ok(true);
769 }
770
771 session.complete();
776 exchange.acknowledge().await?;
777
778 exchange.with_state(|state| {
784 state.resumption.insert_or_update(ResumableSession {
785 fab_idx: record.fab_idx,
786 peer_nodeid: record.peer_nodeid,
787 peer_cat_ids: record.peer_cat_ids,
788 resumption_id: new_rid,
789 shared_secret: record.shared_secret.clone(),
790 });
791 Ok::<_, Error>(())
792 })?;
793 exchange.matter().transport().notify_resumption_dirty();
794
795 info!(
796 "CASE session resumed: local_sessid={}, peer_sessid={}, fabric={}, peer_nodeid=0x{:x}",
797 local_sessid,
798 init_sessid,
799 record.fab_idx.get(),
800 record.peer_nodeid,
801 );
802
803 Ok(true)
804 }
805}