1use core::{mem::MaybeUninit, num::NonZeroU8};
19
20use super::casep::{CaseP, CaseRandom, CaseResumptionId, CaseSessionKeys};
21use super::CASE_LARGE_BUF_SIZE;
22use crate::alloc;
23use crate::cert::CertRef;
24use crate::crypto::{CanonPkcSignature, CanonPkcSignatureRef, Crypto, Hash, AEAD_CANON_KEY_LEN};
25use crate::error::Error;
26use crate::sc::{
27 check_opcode, complete_with_status, sc_write, OpCode, SCStatusCodes, SessionParameters,
28};
29use crate::tlv::{get_root_node_struct, FromTLV, OctetStr, TLVElement, TLVTag, TLVWrite, ToTLV};
30use crate::transport::exchange::Exchange;
31use crate::transport::session::{NocCatIds, ReservedSession, SessionMode};
32use crate::utils::init::{init, Init, InitMaybeUninit};
33
34#[derive(FromTLV, Debug)]
36#[cfg_attr(feature = "defmt", derive(defmt::Format))]
37#[tlvargs(start = 1, lifetime = "'a")]
38struct Sigma1Req<'a> {
39 initiator_random: OctetStr<'a>,
41 initiator_sessid: u16,
43 dest_id: OctetStr<'a>,
45 peer_pub_key: OctetStr<'a>,
47 session_parameters: Option<SessionParameters>,
49 resumption_id: Option<OctetStr<'a>>,
51 initiator_resume_mic: Option<OctetStr<'a>>,
53}
54
55#[derive(FromTLV, Debug)]
57#[cfg_attr(feature = "defmt", derive(defmt::Format))]
58#[tlvargs(start = 1, lifetime = "'a")]
59struct Sigma3Decrypt<'a> {
60 initiator_noc: OctetStr<'a>,
62 initiator_icac: Option<OctetStr<'a>>,
64 signature: OctetStr<'a>,
66}
67
68pub struct CaseResponder<'a, C: Crypto> {
70 crypto: &'a C,
71 casep: CaseP<'a, C>,
73}
74
75impl<'a, C: Crypto> CaseResponder<'a, C> {
76 #[inline(always)]
78 pub const fn new(crypto: &'a C) -> Self {
79 Self {
80 crypto,
81 casep: CaseP::new(),
82 }
83 }
84
85 pub fn init(crypto: &'a C) -> impl Init<Self> {
87 init!(Self {
88 crypto,
89 casep <- CaseP::init(),
90 })
91 }
92
93 pub async fn handle(&mut self, exchange: &mut Exchange<'_>) -> Result<(), Error> {
98 let mut session = ReservedSession::reserve(exchange.matter(), self.crypto).await?;
99
100 self.handle_casesigma1(exchange, &mut session).await?;
101
102 exchange.recv_fetch().await?;
103
104 self.handle_casesigma3(exchange, session).await?;
105
106 exchange.acknowledge().await?;
107
108 Ok(())
109 }
110
111 async fn handle_casesigma1(
119 &mut self,
120 exchange: &mut Exchange<'_>,
121 session: &mut ReservedSession<'_>,
122 ) -> Result<(), Error> {
123 check_opcode(exchange, OpCode::CASESigma1)?;
124
125 let req = Sigma1Req::from_tlv(&get_root_node_struct(exchange.rx()?.payload())?)?;
126
127 if req.resumption_id.is_some() != req.initiator_resume_mic.is_some() {
133 error!("Sigma1 has mismatched resumptionID/initiatorResumeMIC presence; rejecting");
134 complete_with_status(exchange, SCStatusCodes::InvalidParameter, &[]).await?;
135
136 return Ok(());
137 }
138
139 let local_fabric_idx = exchange.with_state(|state| {
140 Ok(state
141 .fabrics
142 .get_by_dest_id(self.crypto, req.initiator_random.0, req.dest_id.0)
143 .map(|fabric| fabric.fab_idx()))
144 })?;
145
146 if local_fabric_idx.is_none() {
147 error!("Fabric Index mismatch");
148 complete_with_status(exchange, SCStatusCodes::NoSharedTrustRoots, &[]).await?;
149
150 return Ok(());
151 }
152
153 let local_sessid = exchange.with_state(|state| Ok(state.sessions.get_next_sess_id()))?;
154
155 let mut our_random = MaybeUninit::<CaseRandom>::uninit(); let our_random = our_random.init_with(CaseRandom::init());
157
158 let mut resumption_id = MaybeUninit::<CaseResumptionId>::uninit(); let resumption_id = resumption_id.init_with(CaseResumptionId::init());
160
161 let mut tt_hash = MaybeUninit::<Hash>::uninit(); let tt_hash = tt_hash.init_with(Hash::init());
163
164 self.casep.start(
165 self.crypto,
166 req.initiator_sessid,
167 local_sessid,
168 unwrap!(local_fabric_idx).get(),
169 req.peer_pub_key.0.try_into()?,
170 exchange.rx()?.payload(),
171 our_random,
172 resumption_id,
173 tt_hash,
174 )?;
175
176 if let Some(params) = req.session_parameters.as_ref() {
184 exchange.with_state(|state| {
185 exchange
186 .id()
187 .session(&mut state.sessions)
188 .set_peer_session_params(params);
189 Ok(())
190 })?;
191 session.set_peer_session_params(params)?;
192 }
193
194 trace!(
195 "Destination ID matched to fabric index {}",
196 self.casep.local_fabric_idx()
197 );
198
199 let mut tt_updated = false;
200 exchange
201 .send_with(|exchange, tw| {
202 exchange.with_state(|state| {
203 let fabric = NonZeroU8::new(self.casep.local_fabric_idx())
204 .and_then(|fabric_idx| state.fabrics.get(fabric_idx));
205
206 let Some(fabric) = fabric else {
207 return sc_write(tw, SCStatusCodes::NoSharedTrustRoots, &[]);
208 };
209
210 let mut signature = MaybeUninit::<CanonPkcSignature>::uninit(); let signature = signature.init_with(CanonPkcSignature::init());
212
213 let sign_buf = tw.empty_as_mut_slice();
215
216 self.casep.compute_sigma2_signature(
217 self.crypto,
218 fabric,
219 sign_buf,
220 signature,
221 )?;
222
223 tw.start_struct(&TLVTag::Anonymous)?;
224 tw.str(&TLVTag::Context(1), our_random.access())?;
225 tw.u16(&TLVTag::Context(2), local_sessid)?;
226 tw.str(&TLVTag::Context(3), self.casep.our_pub_key().access())?;
227
228 tw.str_cb(&TLVTag::Context(4), |buf| {
229 self.casep.sigma2_encrypt(
230 self.crypto,
231 fabric,
232 our_random.reference(),
233 tt_hash.reference(),
234 signature.reference(),
235 resumption_id.reference(),
236 buf,
237 )
238 })?;
239
240 let session_params = crate::sc::SessionParameters {
242 max_paths_per_invoke: Some(
243 exchange.matter().dev_det().max_paths_per_invoke,
244 ),
245 ..Default::default()
246 };
247 session_params.to_tlv(&TLVTag::Context(5), &mut *tw)?;
248
249 tw.end_container()?;
250
251 if !tt_updated {
252 self.casep.update_tt(tw.as_slice())?;
253 tt_updated = true;
254 }
255
256 Ok(Some(OpCode::CASESigma2.into()))
257 })
258 })
259 .await
260 }
261
262 async fn handle_casesigma3(
268 &mut self,
269 exchange: &mut Exchange<'_>,
270 mut session: ReservedSession<'_>,
271 ) -> Result<(), Error> {
272 check_opcode(exchange, OpCode::CASESigma3)?;
273
274 let status = exchange.with_state(|state| {
275 let sess = exchange.id().session(&mut state.sessions);
276
277 let fabric = NonZeroU8::new(self.casep.local_fabric_idx())
278 .and_then(|fabric_idx| state.fabrics.get(fabric_idx));
279 if let Some(fabric) = fabric {
280 let req = match get_root_node_struct(exchange.rx()?.payload()) {
287 Ok(req) => req,
288 Err(e) => {
289 error!("Sigma3 outer TLV parse failed: {}", e);
290 return Ok(SCStatusCodes::InvalidParameter);
291 }
292 };
293 let encrypted = match req.structure().and_then(|s| s.ctx(1)).and_then(|c| c.str()) {
294 Ok(s) => s,
295 Err(e) => {
296 error!("Sigma3 encrypted field parse failed: {}", e);
297 return Ok(SCStatusCodes::InvalidParameter);
298 }
299 };
300
301 let mut decrypted = alloc!([0; CASE_LARGE_BUF_SIZE]); if encrypted.len() > decrypted.len() {
303 error!(
304 "Encrypted Sigma3 data too large ({} bytes)",
305 encrypted.len()
306 );
307 return Ok(SCStatusCodes::InvalidParameter);
308 }
309
310 let decrypted = &mut decrypted[..encrypted.len()];
311 decrypted.copy_from_slice(encrypted);
312
313 let len =
314 match self
315 .casep
316 .sigma3_decrypt(self.crypto, fabric.ipk().op_key(), decrypted)
317 {
318 Ok(len) => len,
319 Err(e) => {
320 error!("Sigma3 AEAD decrypt failed: {}", e);
321 return Ok(SCStatusCodes::InvalidParameter);
322 }
323 };
324 let decrypted = &decrypted[..len];
325 let decrypted_req: Sigma3Decrypt<'_> = match get_root_node_struct(decrypted)
326 .and_then(|n| Sigma3Decrypt::from_tlv(&n))
327 {
328 Ok(req) => req,
329 Err(e) => {
330 error!("Sigma3 decrypted TLV parse failed: {}", e);
331 return Ok(SCStatusCodes::InvalidParameter);
332 }
333 };
334
335 let initiator_noc = CertRef::new(TLVElement::new(decrypted_req.initiator_noc.0));
336 let initiator_icac = decrypted_req
337 .initiator_icac
338 .map(|icac| CertRef::new(TLVElement::new(icac.0)));
339
340 let mut buf = alloc!([0; CASE_LARGE_BUF_SIZE]); let buf = &mut buf[..];
342 if let Err(e) = self.casep.validate_certs(
343 self.crypto,
344 state.rtc.utc_time(),
345 fabric,
346 &initiator_noc,
347 initiator_icac.as_ref(),
348 buf,
349 ) {
350 error!("Certificate Chain doesn't match: {}", e);
351 Ok(SCStatusCodes::InvalidParameter)
352 } else if let Err(e) = self.casep.validate_peer_tbs_signature(
353 self.crypto,
354 decrypted_req.initiator_noc.0,
355 decrypted_req.initiator_icac.map(|a| a.0),
356 &initiator_noc,
357 CanonPkcSignatureRef::try_new(decrypted_req.signature.0)?,
358 buf,
359 ) {
360 error!("Sigma3 Signature doesn't match: {}", e);
361 Ok(SCStatusCodes::InvalidParameter)
362 } else {
363 let mut peer_catids: NocCatIds = Default::default();
365 initiator_noc.get_cat_ids(&mut peer_catids)?;
366 self.casep.update_tt(exchange.rx()?.payload())?;
367
368 let mut session_keys = MaybeUninit::<CaseSessionKeys>::uninit(); let session_keys = session_keys.init_with(CaseSessionKeys::init());
370 self.casep.compute_session_keys(
371 self.crypto,
372 fabric.ipk().op_key(),
373 session_keys,
374 )?;
375
376 let peer_addr = sess.get_peer_addr();
377
378 let (dec_key, remaining) = session_keys
379 .reference()
380 .split::<AEAD_CANON_KEY_LEN, { AEAD_CANON_KEY_LEN * 2 }>();
381 let (enc_key, att_challenge) =
382 remaining.split::<AEAD_CANON_KEY_LEN, AEAD_CANON_KEY_LEN>();
383
384 session.update_with_state(
385 state,
386 fabric.node_id(),
387 initiator_noc.get_node_id()?,
388 self.casep.peer_sessid(),
389 self.casep.local_sessid(),
390 peer_addr,
391 SessionMode::Case {
392 fab_idx: unwrap!(NonZeroU8::new(self.casep.local_fabric_idx())),
394 cat_ids: peer_catids,
395 },
396 Some(dec_key),
397 Some(enc_key),
398 Some(att_challenge),
399 )?;
400
401 Ok(SCStatusCodes::SessionEstablishmentSuccess)
402 }
403 } else {
404 Ok(SCStatusCodes::NoSharedTrustRoots)
405 }
406 })?;
407
408 if matches!(status, SCStatusCodes::SessionEstablishmentSuccess) {
409 session.complete();
417 }
418
419 complete_with_status(exchange, status, &[]).await
420 }
421}