1use std::sync::Arc;
7
8use matter_cert::MatterTime;
9use matter_commissioning::{issue_icac, issue_noc, FabricRecord, NocRng, VerifiedCsr};
10use matter_crypto::{RingSigner, Signer};
11
12use crate::error::Error;
13use crate::state::{CommissionerIdentity, FabricEntry, IcacIdentity};
14
15#[derive(Debug, Clone)]
21#[non_exhaustive]
22pub struct FabricConfig {
23 pub fabric_id: u64,
25 pub rcac_id: u64,
27 pub commissioner_node_id: u64,
29 pub validity: (MatterTime, MatterTime),
31 pub issue_icac: bool,
37}
38
39impl FabricConfig {
40 #[must_use]
46 pub fn new(
47 fabric_id: u64,
48 rcac_id: u64,
49 commissioner_node_id: u64,
50 validity: (MatterTime, MatterTime),
51 ) -> Self {
52 Self {
53 fabric_id,
54 rcac_id,
55 commissioner_node_id,
56 validity,
57 issue_icac: false,
58 }
59 }
60}
61
62pub(crate) fn create_fabric(cfg: &FabricConfig, rng: &dyn NocRng) -> Result<FabricEntry, Error> {
73 let (root_signer, rcac_pkcs8) =
75 RingSigner::generate().map_err(|e| Error::Signer(e.to_string()))?;
76 let root_arc: Arc<dyn Signer> = Arc::new(root_signer);
77 let mut fabric_record = FabricRecord::new_root_only(
78 cfg.fabric_id,
79 root_arc,
80 cfg.validity.0,
81 cfg.validity.1,
82 cfg.rcac_id,
83 rng,
84 )?;
85
86 let icac_identity = if cfg.issue_icac {
91 let (icac_signer_raw, icac_pkcs8) =
92 RingSigner::generate().map_err(|e| Error::Signer(e.to_string()))?;
93 let icac_public_key = icac_signer_raw.public_key().clone();
94 let icac_cert = issue_icac(
99 &fabric_record,
100 cfg.rcac_id,
101 &icac_public_key,
102 cfg.validity,
103 rng,
104 )
105 .map_err(Error::Noc)?;
106 fabric_record.icac_signer = Some(Arc::new(icac_signer_raw));
107 fabric_record.icac_cert = Some(icac_cert.clone());
108 Some(IcacIdentity {
109 cert: icac_cert,
110 pkcs8: icac_pkcs8,
111 })
112 } else {
113 None
114 };
115
116 let (comm_signer, comm_pkcs8) =
118 RingSigner::generate().map_err(|e| Error::Signer(e.to_string()))?;
119 let comm_public_key = comm_signer.public_key().clone();
120
121 let verified = VerifiedCsr {
128 public_key: comm_public_key,
129 };
130 let noc = issue_noc(
131 &fabric_record,
132 &verified,
133 cfg.commissioner_node_id,
134 &[], cfg.validity,
136 rng,
137 )?;
138
139 Ok(FabricEntry {
140 fabric_id: cfg.fabric_id,
141 ipk: fabric_record.identity_protection_key,
142 rcac_cert: fabric_record.root_cert.clone(),
143 rcac_pkcs8,
144 commissioner: CommissionerIdentity {
145 node_id: cfg.commissioner_node_id,
146 operational_pkcs8: comm_pkcs8,
147 noc,
148 },
149 devices: Vec::new(),
150 group_keys: Vec::new(),
151 outbound_group_counter: 0,
152 icd_clients: Vec::new(),
153 icac: icac_identity,
154 })
155}
156
157#[cfg(test)]
158#[allow(clippy::unwrap_used, clippy::expect_used)] mod tests {
160 use super::*;
161 use matter_commissioning::SystemNocRng;
162
163 fn sample_cfg() -> FabricConfig {
164 FabricConfig::new(
165 0xDEAD_BEEF_0000_0001,
166 1,
167 0x0000_0000_0000_0001,
168 (
169 MatterTime::from_unix_secs(1_700_000_000),
170 MatterTime::NO_EXPIRY,
171 ),
172 )
173 }
174
175 #[test]
176 fn new_constructor_sets_all_fields() {
177 let cfg = FabricConfig::new(
180 7,
181 9,
182 3,
183 (MatterTime::from_unix_secs(1), MatterTime::NO_EXPIRY),
184 );
185 assert_eq!(cfg.fabric_id, 7);
186 assert_eq!(cfg.rcac_id, 9);
187 assert_eq!(cfg.commissioner_node_id, 3);
188 assert_eq!(cfg.validity.0, MatterTime::from_unix_secs(1));
189 }
190
191 #[test]
192 fn creates_fabric_with_no_devices() {
193 let fabric = create_fabric(&sample_cfg(), &SystemNocRng).expect("create");
194 assert_eq!(fabric.fabric_id, 0xDEAD_BEEF_0000_0001);
195 assert_eq!(fabric.commissioner.node_id, 1);
196 assert!(fabric.devices.is_empty());
197 assert!(!fabric.rcac_pkcs8.is_empty());
198 assert!(!fabric.commissioner.operational_pkcs8.is_empty());
199 }
200
201 #[test]
202 fn commissioner_noc_is_signed_by_the_rcac() {
203 let fabric = create_fabric(&sample_cfg(), &SystemNocRng).expect("create");
204 let rcac_key = fabric.rcac_cert.public_key();
205 fabric
206 .commissioner
207 .noc
208 .verify_signed_by(rcac_key)
209 .expect("commissioner NOC must verify under the RCAC");
210 }
211
212 #[test]
213 fn default_path_has_no_icac_and_noc_issuer_is_rcac() {
214 let fabric = create_fabric(&sample_cfg(), &SystemNocRng).expect("create");
218 assert!(fabric.icac.is_none());
219 assert_eq!(fabric.commissioner.noc.issuer(), fabric.rcac_cert.subject());
220 }
221
222 #[test]
223 fn issue_icac_true_mints_chain_and_signs_commissioner_noc_under_icac() {
224 let mut cfg = sample_cfg();
225 cfg.issue_icac = true;
226 let entry = create_fabric(&cfg, &SystemNocRng).expect("create");
227
228 let icac = entry.icac.clone().expect("icac must be Some");
230
231 let rec = entry.to_fabric_record().expect("to_fabric_record");
234 assert!(rec.icac_signer.is_some());
235 assert!(rec.icac_cert.is_some());
236
237 assert_eq!(entry.commissioner.noc.issuer(), icac.cert.subject());
240 assert_ne!(entry.commissioner.noc.issuer(), entry.rcac_cert.subject());
241
242 assert_eq!(icac.cert.issuer(), entry.rcac_cert.subject());
245 icac.cert
246 .verify_signed_by(entry.rcac_cert.public_key())
247 .expect("icac must verify under the rcac's public key");
248 assert_eq!(entry.commissioner.noc.issuer(), icac.cert.subject());
250 entry
251 .commissioner
252 .noc
253 .verify_signed_by(icac.cert.public_key())
254 .expect("commissioner noc must verify under the icac's public key");
255
256 let state = crate::state::ControllerState::new(vec![entry.clone()]);
259 let bytes = crate::snapshot::serialize(&state).expect("serialize");
260 let restored = crate::snapshot::deserialize(&bytes).expect("deserialize");
261 let restored_icac = restored.fabrics[0]
262 .icac
263 .clone()
264 .expect("icac must round-trip as Some");
265 assert_eq!(
266 restored_icac.cert.to_tlv().expect("tlv"),
267 icac.cert.to_tlv().expect("tlv"),
268 "restored icac cert must byte-match the original"
269 );
270 }
271
272 #[test]
273 fn commissioner_signer_matches_persisted_noc_key() {
274 let fabric = create_fabric(&sample_cfg(), &SystemNocRng).expect("create");
277 let signer = fabric.commissioner_signer().expect("reload signer");
278 assert_eq!(
279 signer.public_key().as_bytes(),
280 fabric.commissioner.noc.public_key().as_bytes(),
281 "persisted op key must match the NOC subject public key"
282 );
283 }
284}