1use core::cell::Cell;
21use core::mem::MaybeUninit;
22use core::num::NonZeroU8;
23
24use crate::acl::AclEntry;
25use crate::cert::CertRef;
26use crate::crypto::{CanonPkcSignature, Crypto, SigningSecretKey, PKC_CANON_PUBLIC_KEY_LEN};
27use crate::dm::clusters::acl::{emit_acl_entry_changed, ChangeTypeEnum};
28use crate::dm::clusters::adm_comm;
29use crate::dm::clusters::dev_att::DeviceAttestation;
30use crate::dm::clusters::gen_comm::GenCommHandler;
31use crate::dm::endpoints::ROOT_ENDPOINT_ID;
32use crate::dm::{ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext};
33use crate::error::{Error, ErrorCode};
34use crate::fabric::{Fabric, FabricPersist, MAX_FABRICS};
35use crate::tlv::{
36 Nullable, Octets, OctetsArrayBuilder, OctetsBuilder, TLVBuilder, TLVBuilderParent, TLVElement,
37 TLVTag, TLVWrite,
38};
39use crate::transport::session::{AttChallengeRef, SessionMode, ATT_CHALLENGE_LEN};
40use crate::utils::init::InitMaybeUninit;
41use crate::utils::storage::WriteBuf;
42
43pub use crate::dm::clusters::decl::operational_credentials::*;
44
45impl NodeOperationalCertStatusEnum {
46 fn map(result: Result<(), Error>) -> Result<Self, Error> {
47 match result {
48 Ok(()) => Ok(Self::OK),
49 Err(err) => match err.code() {
50 ErrorCode::NocFabricTableFull => Ok(Self::TableFull),
51 ErrorCode::NocInvalidFabricIndex => Ok(Self::InvalidFabricIndex),
52 ErrorCode::NocFabricConflict => Ok(Self::FabricConflict),
53 ErrorCode::NocLabelConflict => Ok(Self::LabelConflict),
54 ErrorCode::NocInvalidNoc => Ok(Self::InvalidNOC),
55 ErrorCode::NocInvalidPublicKey => Ok(Self::InvalidPublicKey),
56 ErrorCode::NocInvalidAdminSubject => Ok(Self::InvalidAdminSubject),
57 ErrorCode::NocMissingCsr => Ok(Self::MissingCsr),
58 _ => Err(err),
65 },
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72#[cfg_attr(feature = "defmt", derive(defmt::Format))]
73pub struct NocHandler {
74 dataver: Dataver,
75}
76
77impl NocHandler {
78 pub const fn new(dataver: Dataver) -> Self {
80 Self { dataver }
81 }
82
83 pub const fn adapt(self) -> HandlerAdaptor<Self> {
85 HandlerAdaptor(self)
86 }
87
88 fn compute_attestation_signature<C: Crypto>(
90 crypto: C,
91 dev_att: &dyn DeviceAttestation,
92 attest_element: &mut WriteBuf,
93 attest_challenge: AttChallengeRef<'_>,
94 signature: &mut CanonPkcSignature,
95 ) -> Result<(), Error> {
96 let dac_key = crypto.secret_key(dev_att.dac_priv_key())?;
97
98 attest_element.copy_from_slice(attest_challenge.access())?;
99 dac_key.sign(attest_element.as_slice(), signature)?;
100
101 Ok(())
102 }
103}
104
105impl ClusterHandler for NocHandler {
106 const CLUSTER: Cluster<'static> = FULL_CLUSTER;
107
108 fn dataver(&self) -> u32 {
109 self.dataver.get()
110 }
111
112 fn dataver_changed(&self) {
113 self.dataver.changed();
114 }
115
116 fn nocs<P: TLVBuilderParent>(
117 &self,
118 ctx: impl ReadContext,
119 builder: ArrayAttributeRead<NOCStructArrayBuilder<P>, NOCStructBuilder<P>>,
120 ) -> Result<P, Error> {
121 fn read_into<P: TLVBuilderParent>(
122 fabric: &Fabric,
123 builder: NOCStructBuilder<P>,
124 ) -> Result<P, Error> {
125 builder
126 .noc(Octets::new(fabric.noc()))?
127 .icac(Nullable::new(
128 (!fabric.icac().is_empty()).then(|| Octets::new(fabric.icac())),
129 ))?
130 .vvsc((!fabric.vvsc().is_empty()).then(|| Octets::new(fabric.vvsc())))?
131 .fabric_index(Some(fabric.fab_idx().get()))?
132 .end()
133 }
134
135 let attr = ctx.attr();
136
137 ctx.exchange().with_state(|state| {
138 let mut fabrics = state.fabrics.iter().filter(|fabric| {
139 (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get())
140 && !fabric.root_ca().is_empty()
141 });
142
143 match builder {
153 ArrayAttributeRead::ReadAll(mut builder) => {
154 for fabric in fabrics {
155 builder = read_into(fabric, builder.push()?)?;
156 }
157
158 builder.end()
159 }
160 ArrayAttributeRead::ReadOne(index, builder) => {
161 if let Some(fabric) = fabrics.nth(index as _) {
162 read_into(fabric, builder)
163 } else {
164 Err(ErrorCode::ConstraintError.into())
165 }
166 }
167 ArrayAttributeRead::ReadNone(builder) => builder.end(),
168 }
169 })
170 }
171
172 fn fabrics<P: TLVBuilderParent>(
173 &self,
174 ctx: impl ReadContext,
175 builder: ArrayAttributeRead<
176 FabricDescriptorStructArrayBuilder<P>,
177 FabricDescriptorStructBuilder<P>,
178 >,
179 ) -> Result<P, Error> {
180 fn read_into<P: TLVBuilderParent>(
181 fabric: &Fabric,
182 builder: FabricDescriptorStructBuilder<P>,
183 ) -> Result<P, Error> {
184 let root_ca_cert = CertRef::new(TLVElement::new(fabric.root_ca()));
186
187 builder
188 .root_public_key(Octets::new(root_ca_cert.pubkey()?))?
189 .vendor_id(fabric.vendor_id())?
190 .fabric_id(fabric.fabric_id())?
191 .node_id(fabric.node_id())?
192 .label(fabric.label())?
193 .vid_verification_statement(
194 (!fabric.vid_verification_statement().is_empty())
195 .then(|| Octets::new(fabric.vid_verification_statement())),
196 )?
197 .fabric_index(Some(fabric.fab_idx().get()))?
198 .end()
199 }
200
201 let attr = ctx.attr();
202
203 ctx.exchange().with_state(|state| {
204 let mut fabrics = state.fabrics.iter().filter(|fabric| {
205 (!attr.fab_filter || attr.fab_idx == fabric.fab_idx().get())
206 && !fabric.root_ca().is_empty()
207 });
208
209 match builder {
210 ArrayAttributeRead::ReadAll(mut builder) => {
211 for fabric in fabrics {
212 builder = read_into(fabric, builder.push()?)?;
213 }
214
215 builder.end()
216 }
217 ArrayAttributeRead::ReadOne(index, builder) => {
218 let fabric = fabrics.nth(index as _);
219
220 if let Some(fabric) = fabric {
221 read_into(fabric, builder)
222 } else {
223 Err(ErrorCode::ConstraintError.into())
224 }
225 }
226 ArrayAttributeRead::ReadNone(builder) => builder.end(),
227 }
228 })
229 }
230
231 fn supported_fabrics(&self, _ctx: impl ReadContext) -> Result<u8, Error> {
232 Ok(MAX_FABRICS as u8)
233 }
234
235 fn commissioned_fabrics(&self, ctx: impl ReadContext) -> Result<u8, Error> {
236 ctx.exchange()
237 .with_state(|state| Ok(state.fabrics.iter().count() as u8))
238 }
239
240 fn trusted_root_certificates<P: TLVBuilderParent>(
241 &self,
242 ctx: impl ReadContext,
243 builder: ArrayAttributeRead<OctetsArrayBuilder<P>, OctetsBuilder<P>>,
244 ) -> Result<P, Error> {
245 ctx.exchange().with_state(|state| {
246 let fabric_certs = state
250 .fabrics
251 .iter()
252 .filter(|fabric| !fabric.root_ca().is_empty())
253 .map(|fabric| fabric.root_ca());
254
255 let mut certs = fabric_certs.chain(state.failsafe.pending_root_ca());
262
263 match builder {
264 ArrayAttributeRead::ReadAll(mut builder) => {
265 for cert in certs {
266 builder = builder.push(Octets::new(cert))?;
267 }
268
269 builder.end()
270 }
271 ArrayAttributeRead::ReadOne(index, builder) => {
272 if let Some(cert) = certs.nth(index as _) {
273 builder.set(Octets::new(cert))
274 } else {
275 Err(ErrorCode::ConstraintError.into())
276 }
277 }
278 ArrayAttributeRead::ReadNone(builder) => builder.end(),
279 }
280 })
281 }
282
283 fn current_fabric_index(&self, ctx: impl ReadContext) -> Result<u8, Error> {
284 let attr = ctx.attr();
285 Ok(attr.fab_idx)
286 }
287
288 fn handle_attestation_request<P: TLVBuilderParent>(
289 &self,
290 ctx: impl InvokeContext,
291 request: AttestationRequestRequest<'_>,
292 response: AttestationResponseBuilder<P>,
293 ) -> Result<P, Error> {
294 info!("Got Attestation Request");
295
296 const ATTESTATION_NONCE_LEN: usize = 32;
300
301 if request.attestation_nonce()?.0.len() != ATTESTATION_NONCE_LEN {
302 return Err(ErrorCode::InvalidCommand.into());
303 }
304
305 ctx.exchange().with_state(|state| {
306 let sess = ctx.exchange().id().session(&mut state.sessions);
307
308 let mut parent = response.unchecked_into_parent();
312 let writer = parent.writer();
313
314 let epoch = state.rtc.utc_time().any_secs() as u32;
325
326 let mut signature = MaybeUninit::uninit();
327 let signature = signature.init_with(CanonPkcSignature::init()); writer.str_cb(&TLVTag::Context(0), |buf| {
330 let dev_att = ctx.exchange().matter().dev_att();
331
332 let mut wb = WriteBuf::new(buf);
333 wb.start_struct(&TLVTag::Anonymous)?;
334 wb.str(&TLVTag::Context(1), dev_att.cert_declaration())?;
335 wb.str(&TLVTag::Context(2), request.attestation_nonce()?.0)?;
336 wb.u32(&TLVTag::Context(3), epoch)?;
337 wb.end_container()?;
338
339 let len = wb.get_tail();
340
341 Self::compute_attestation_signature(
342 ctx.crypto(),
343 dev_att,
344 &mut wb,
345 sess.get_att_challenge().ok_or(ErrorCode::InvalidState)?,
346 signature,
347 )?;
348
349 Ok(len)
350 })?;
351
352 writer.str(&TLVTag::Context(1), signature.access())?;
353
354 writer.end_container()?;
355
356 Ok(parent)
357 })
358 }
359
360 fn handle_certificate_chain_request<P: TLVBuilderParent>(
361 &self,
362 ctx: impl InvokeContext,
363 request: CertificateChainRequestRequest<'_>,
364 response: CertificateChainResponseBuilder<P>,
365 ) -> Result<P, Error> {
366 info!("Got Cert Chain Request");
367
368 let mut parent = response.unchecked_into_parent();
372 let writer = parent.writer();
373
374 let dev_att = ctx.exchange().matter().dev_att();
378
379 writer.str(
380 &TLVTag::Context(0),
381 match request.certificate_type()? {
382 CertificateChainTypeEnum::DACCertificate => dev_att.dac(),
383 CertificateChainTypeEnum::PAICertificate => dev_att.pai(),
384 },
385 )?;
386
387 writer.end_container()?;
388
389 Ok(parent)
390 }
391
392 fn handle_csr_request<P: TLVBuilderParent>(
393 &self,
394 ctx: impl InvokeContext,
395 request: CSRRequestRequest<'_>,
396 response: CSRResponseBuilder<P>,
397 ) -> Result<P, Error> {
398 info!("Got CSR Request");
399
400 const CSR_NONCE_LEN: usize = 32;
404
405 if request.csr_nonce()?.0.len() != CSR_NONCE_LEN {
406 return Err(ErrorCode::InvalidCommand.into());
407 }
408
409 let is_for_update_noc = request.is_for_update_noc()?.unwrap_or(false);
410
411 GenCommHandler::with_armed_failsafe(&ctx, |state, _| {
412 let sess = ctx.exchange().id().session(&mut state.sessions);
413
414 if is_for_update_noc && !matches!(sess.get_session_mode(), SessionMode::Case { .. }) {
420 return Err(ErrorCode::InvalidCommand.into());
421 }
422
423 let secret_key = if is_for_update_noc {
424 state
425 .failsafe
426 .update_csr_req(ctx.crypto(), sess.get_session_mode())?
427 } else {
428 state
429 .failsafe
430 .add_csr_req(ctx.crypto(), sess.get_session_mode())?
431 };
432
433 let mut parent = response.unchecked_into_parent();
437 let writer = parent.writer();
438
439 let mut signature = MaybeUninit::uninit();
443 let signature = signature.init_with(CanonPkcSignature::init()); writer.str_cb(&TLVTag::Context(0), |buf| {
446 let mut wb = WriteBuf::new(buf);
447
448 wb.start_struct(&TLVTag::Anonymous)?;
449 wb.str_cb(&TLVTag::Context(1), |buf| {
450 ctx.crypto()
451 .secret_key(secret_key)?
452 .csr(buf)
453 .map(|slice| slice.len())
454 })?;
455 wb.str(&TLVTag::Context(2), request.csr_nonce()?.0)?;
456 wb.end_container()?;
457
458 let len = wb.get_tail();
459
460 Self::compute_attestation_signature(
461 ctx.crypto(),
462 ctx.exchange().matter().dev_att(),
463 &mut wb,
464 sess.get_att_challenge().ok_or(ErrorCode::InvalidState)?,
465 signature,
466 )?;
467
468 Ok(len)
469 })?;
470
471 writer.str(&TLVTag::Context(1), signature.access())?;
472
473 writer.end_container()?;
474
475 Ok(parent)
476 })
477 }
478
479 fn handle_add_noc<P: TLVBuilderParent>(
480 &self,
481 ctx: impl InvokeContext,
482 request: AddNOCRequest<'_>,
483 mut response: NOCResponseBuilder<P>,
484 ) -> Result<P, Error> {
485 info!("Got Add NOC Request");
486
487 let icac = request
488 .icac_value()?
489 .as_ref()
490 .map(|icac| icac.0)
491 .filter(|icac| !icac.is_empty());
492
493 let mut added_fab_idx = None;
494 let mut admin_acl_entry: Option<AclEntry> = None;
498 let rolled_back_fab_idx = Cell::new(None);
501
502 let buf = response.writer().available_space();
503
504 let status = NodeOperationalCertStatusEnum::map(GenCommHandler::with_armed_failsafe(
505 &ctx,
506 |state, mut notify_mdns| {
507 let sess = ctx.exchange().id().session(&mut state.sessions);
508
509 let fabric = state.failsafe.add_noc(
510 ctx.crypto(),
511 state.rtc.utc_time(),
512 &mut state.fabrics,
513 sess.get_session_mode(),
514 request.admin_vendor_id()?,
515 icac,
516 request.noc_value()?.0,
517 request.ipk_value()?.0,
518 request.case_admin_subject()?,
519 buf,
520 &mut notify_mdns,
521 )?;
522
523 let fab_idx = fabric.fab_idx();
524 let captured_admin_entry = fabric.acl_iter().next().cloned();
528 let succeeded = Cell::new(false);
529
530 let _fab_guard = scopeguard::guard(fab_idx, |fab_idx| {
531 if !succeeded.get() {
532 warn!("Removing fabric {} due to failure", fab_idx.get());
534
535 unwrap!(state.fabrics.remove(fab_idx));
536
537 notify_mdns();
538
539 rolled_back_fab_idx.set(Some(fab_idx));
540 }
541 });
542
543 if matches!(sess.get_session_mode(), SessionMode::Pase { .. }) {
544 sess.upgrade_fabric_idx(fab_idx)?;
545 }
546
547 succeeded.set(true);
548 added_fab_idx = Some(fab_idx.get());
549 admin_acl_entry = captured_admin_entry;
550
551 Ok(())
552 },
553 ));
554
555 if let Some(fab_idx) = rolled_back_fab_idx.get() {
560 ctx.notify_fabric_removed(fab_idx);
561 }
562
563 let status = status?;
564
565 ctx.notify_own_cluster_changed();
567
568 if let (Some(fab_idx), Some(entry)) = (added_fab_idx, &admin_acl_entry) {
573 emit_acl_entry_changed(
574 &ctx,
575 crate::tlv::Nullable::none(),
576 crate::tlv::Nullable::some(0u16),
577 ChangeTypeEnum::Added,
578 entry,
579 fab_idx,
580 )?;
581 }
582
583 response
584 .status_code(status)?
585 .fabric_index(added_fab_idx)?
586 .debug_text(None)?
587 .end()
588 }
589
590 fn handle_update_noc<P: TLVBuilderParent>(
591 &self,
592 ctx: impl InvokeContext,
593 request: UpdateNOCRequest<'_>,
594 mut response: NOCResponseBuilder<P>,
595 ) -> Result<P, Error> {
596 info!("Got Update NOC Request");
597
598 let icac = request
599 .icac_value()?
600 .as_ref()
601 .map(|icac| icac.0)
602 .filter(|icac| !icac.is_empty());
603
604 let buf = response.writer().available_space();
605
606 let status = NodeOperationalCertStatusEnum::map(GenCommHandler::with_armed_failsafe(
607 &ctx,
608 |state, notify_mdns| {
609 let sess = ctx.exchange().id().session(&mut state.sessions);
610
611 state.failsafe.update_noc(
612 ctx.crypto(),
613 state.rtc.utc_time(),
614 &mut state.fabrics,
615 sess.get_session_mode(),
616 icac,
617 request.noc_value()?.0,
618 buf,
619 notify_mdns,
620 )?;
621
622 Ok(())
623 },
624 ))?;
625
626 ctx.notify_own_cluster_changed();
628
629 response
630 .status_code(status)?
631 .fabric_index(Some(ctx.cmd().fab_idx))?
632 .debug_text(None)?
633 .end()
634 }
635
636 fn handle_update_fabric_label<P: TLVBuilderParent>(
637 &self,
638 ctx: impl InvokeContext,
639 request: UpdateFabricLabelRequest<'_>,
640 response: NOCResponseBuilder<P>,
641 ) -> Result<P, Error> {
642 info!("Got Update Fabric Label Request: {:?}", request.label());
643
644 let mut updated_fab_idx = None;
645
646 let status = NodeOperationalCertStatusEnum::map(ctx.exchange().with_state(|state| {
647 let sess = ctx.exchange().id().session(&mut state.sessions);
648
649 let fab_idx = NonZeroU8::new(sess.get_local_fabric_idx())
659 .ok_or(ErrorCode::GennCommInvalidAuthentication)?;
660
661 let fabric = state
662 .fabrics
663 .update_label(fab_idx, request.label()?)
664 .map_err(|e| {
665 if e.code() == ErrorCode::Invalid {
666 ErrorCode::NocLabelConflict.into()
667 } else {
668 e
669 }
670 })?;
671
672 updated_fab_idx = Some(fabric.fab_idx().get());
673
674 Ok(())
675 }))?;
676
677 ctx.notify_own_cluster_changed();
679
680 response
681 .status_code(status)?
682 .fabric_index(updated_fab_idx)?
683 .debug_text(None)?
684 .end()
685 }
686
687 fn handle_remove_fabric<P: TLVBuilderParent>(
688 &self,
689 ctx: impl InvokeContext,
690 request: RemoveFabricRequest<'_>,
691 response: NOCResponseBuilder<P>,
692 ) -> Result<P, Error> {
693 info!("Got Remove Fabric Request");
694
695 let fab_idx = NonZeroU8::new(request.fabric_index()?).ok_or(ErrorCode::ConstraintError)?;
696
697 let notify_mdns = || ctx.exchange().matter().transport().notify_mdns_changed();
698
699 let mut persist = FabricPersist::new(ctx.kv());
700
701 let (status, opener_fabric_removed) = ctx.exchange().with_state(|state| {
702 let sess = ctx.exchange().id().session(&mut state.sessions);
703
704 if state.fabrics.remove(fab_idx).is_ok() {
705 let expire_sess_id =
709 (sess.get_local_fabric_idx() == fab_idx.get()).then_some(sess.id());
710
711 state.sessions.remove_for_fabric(fab_idx, expire_sess_id);
714
715 #[cfg(feature = "case-resumption")]
719 state.resumption.remove_for_fabric(fab_idx);
720
721 ctx.exchange().matter().transport().notify_session_removed();
723
724 #[cfg(feature = "case-resumption")]
728 ctx.exchange()
729 .matter()
730 .transport()
731 .notify_resumption_dirty();
732
733 notify_mdns();
735
736 persist.remove(fab_idx)?;
740
741 if state.rtc.trusted_time_source().map(|tts| tts.fab_idx) == Some(fab_idx) {
745 state.rtc.set_trusted_time_source_persist(
746 None,
747 persist.persist_mut(),
748 &ctx,
749 &ctx,
750 )?;
751 }
752
753 info!("Removed operational fabric with local index {}", fab_idx);
754
755 let opener_fabric_removed = state
760 .pase
761 .comm_window()
762 .and_then(|w| w.opener())
763 .map(|opener| opener.fab_idx == fab_idx)
764 .unwrap_or(false);
765
766 Ok::<_, Error>((NodeOperationalCertStatusEnum::OK, opener_fabric_removed))
767 } else {
768 Ok((NodeOperationalCertStatusEnum::InvalidFabricIndex, false))
769 }
770 })?;
771
772 persist.run()?;
773
774 if matches!(status, NodeOperationalCertStatusEnum::OK) {
775 let emitted = crate::dm::clusters::decl::basic_information::Leave::emit_for(
784 &ctx,
785 ROOT_ENDPOINT_ID,
786 |event| event.fabric_index(fab_idx.get())?.end(),
787 );
788
789 if let Err(e) = emitted {
790 warn!("Failed to emit the Leave event: {:?}", e);
791 }
792
793 ctx.notify_fabric_removed(fab_idx);
798 }
799
800 ctx.notify_own_cluster_changed();
802
803 if opener_fabric_removed {
804 ctx.notify_cluster_changed(ROOT_ENDPOINT_ID, adm_comm::FULL_CLUSTER.id);
805 }
806
807 response
808 .status_code(status)?
809 .fabric_index(Some(fab_idx.get()))?
810 .debug_text(None)?
811 .end()
812 }
813
814 fn handle_add_trusted_root_certificate(
815 &self,
816 ctx: impl InvokeContext,
817 request: AddTrustedRootCertificateRequest<'_>,
818 ) -> Result<(), Error> {
819 info!("Got Add Trusted Root Cert Request");
820
821 let mut buf = [0u8; crate::cert::MAX_CERT_ASN1_LEN];
830
831 GenCommHandler::with_armed_failsafe(&ctx, |state, _| {
832 let sess = ctx.exchange().id().session(&mut state.sessions);
833
834 state.failsafe.add_trusted_root_cert(
835 ctx.crypto(),
836 state.rtc.utc_time(),
837 sess.get_session_mode(),
838 request.root_ca_certificate()?.0,
839 &mut buf,
840 )
841 })
842 }
843
844 fn handle_set_vid_verification_statement(
845 &self,
846 ctx: impl InvokeContext,
847 request: SetVIDVerificationStatementRequest<'_>,
848 ) -> Result<(), Error> {
849 info!("Got Set VID Verification Statement Request");
850
851 let vendor_id = request.vendor_id()?;
852 let vvs = request.vid_verification_statement()?;
853 let vvsc = request.vvsc()?;
854
855 if vendor_id.is_none() && vvs.is_none() && vvsc.is_none() {
859 return Err(ErrorCode::InvalidCommand.into());
860 }
861
862 if let Some(vid) = vendor_id {
866 if vid == 0 || vid > 0xFFF4 {
867 return Err(ErrorCode::ConstraintError.into());
868 }
869 }
870
871 if let Some(s) = &vvs {
875 if !s.0.is_empty() && s.0.len() != crate::fabric::VID_VERIFICATION_STATEMENT_LEN {
876 return Err(ErrorCode::ConstraintError.into());
877 }
878 }
879
880 if let Some(v) = &vvsc {
885 if v.0.len() > crate::cert::MAX_CERT_TLV_LEN {
886 return Err(ErrorCode::ConstraintError.into());
887 }
888 }
889
890 let fab_idx = NonZeroU8::new(ctx.cmd().fab_idx).ok_or(ErrorCode::UnsupportedAccess)?;
891
892 let mut persist = FabricPersist::new(ctx.kv());
893
894 ctx.exchange().with_state(|state| {
895 let fabric = state.fabrics.fabric_mut(fab_idx)?;
896
897 if let Some(v) = &vvsc {
903 if !v.0.is_empty() && !fabric.icac().is_empty() {
904 return Err(ErrorCode::InvalidCommand.into());
905 }
906 }
907
908 fabric.set_vid_verification(
909 vendor_id,
910 vvs.as_ref().map(|s| s.0),
911 vvsc.as_ref().map(|v| v.0),
912 )?;
913
914 let part_of_pending_fabric =
925 state.failsafe.is_armed() && state.failsafe.has_pending_noc_for(fab_idx);
926 if !part_of_pending_fabric {
927 persist.store(fabric)?;
928 }
929
930 Ok(())
931 })?;
932
933 persist.run()?;
934
935 ctx.notify_own_cluster_changed();
938
939 Ok(())
940 }
941
942 fn handle_sign_vid_verification_request<P: TLVBuilderParent>(
943 &self,
944 ctx: impl InvokeContext,
945 request: SignVIDVerificationRequestRequest<'_>,
946 mut response: SignVIDVerificationResponseBuilder<P>,
947 ) -> Result<P, Error> {
948 info!("Got Sign VID Verification Request");
949
950 let fab_idx_raw = request.fabric_index()?;
953 let fab_idx = NonZeroU8::new(fab_idx_raw)
954 .filter(|fi| fi.get() != u8::MAX)
955 .ok_or(ErrorCode::ConstraintError)?;
956
957 let client_challenge = request.client_challenge()?.0;
961 if client_challenge.len() != VID_VERIFY_CLIENT_CHALLENGE_LEN {
962 return Err(ErrorCode::ConstraintError.into());
963 }
964
965 ctx.exchange().with_state(|state| {
966 let sess = ctx.exchange().id().session(&mut state.sessions);
967 let attestation_challenge = sess.get_att_challenge().ok_or(ErrorCode::InvalidState)?;
968 let attestation_challenge_bytes: [u8; ATT_CHALLENGE_LEN] =
969 *attestation_challenge.access();
970
971 let fabric = state
972 .fabrics
973 .get(fab_idx)
974 .ok_or(ErrorCode::ConstraintError)?;
975
976 let root_ref = CertRef::new(TLVElement::new(fabric.root_ca()));
980 let root_pub_key = root_ref.pubkey()?;
981 if root_pub_key.len() != PKC_CANON_PUBLIC_KEY_LEN {
982 return Err(ErrorCode::InvalidData.into());
983 }
984
985 let fabric_id_be = fabric.fabric_id().to_be_bytes();
986 let vendor_id_be = fabric.vendor_id().to_be_bytes();
987
988 let tbs_buf = response.writer().available_space();
999 let mut len = 0usize;
1000
1001 tbs_buf[len] = FABRIC_BINDING_VERSION_1;
1002 len += 1;
1003 tbs_buf[len..len + client_challenge.len()].copy_from_slice(client_challenge);
1004 len += client_challenge.len();
1005 tbs_buf[len..len + attestation_challenge_bytes.len()]
1006 .copy_from_slice(&attestation_challenge_bytes);
1007 len += attestation_challenge_bytes.len();
1008 tbs_buf[len] = fab_idx.get();
1009 len += 1;
1010 tbs_buf[len] = FABRIC_BINDING_VERSION_1;
1012 len += 1;
1013 tbs_buf[len..len + PKC_CANON_PUBLIC_KEY_LEN].copy_from_slice(root_pub_key);
1014 len += PKC_CANON_PUBLIC_KEY_LEN;
1015 tbs_buf[len..len + 8].copy_from_slice(&fabric_id_be);
1016 len += 8;
1017 tbs_buf[len..len + 2].copy_from_slice(&vendor_id_be);
1018 len += 2;
1019 let vvs = fabric.vid_verification_statement();
1021 if !vvs.is_empty() {
1022 tbs_buf[len..len + vvs.len()].copy_from_slice(vvs);
1023 len += vvs.len();
1024 }
1025
1026 let mut signature = MaybeUninit::uninit();
1028 let signature = signature.init_with(CanonPkcSignature::init());
1029
1030 ctx.crypto()
1031 .secret_key(fabric.secret_key())?
1032 .sign(&tbs_buf[..len], signature)?;
1033
1034 response
1035 .fabric_index(fab_idx.get())?
1036 .fabric_binding_version(FABRIC_BINDING_VERSION_1)?
1037 .signature(Octets::new(signature.access()))?
1038 .end()
1039 })
1040 }
1041}
1042
1043const FABRIC_BINDING_VERSION_1: u8 = 1;
1046
1047const VID_VERIFY_CLIENT_CHALLENGE_LEN: usize = 32;