matc/
commission.rs

1use anyhow::{Context, Result};
2use rand::RngCore;
3
4use crate::{
5    cert_matter, cert_x509, certmanager, controller::auth_sigma, fabric::Fabric, messages,
6    retransmit, session, tlv, transport::ConnectionTrait,
7};
8
9const CLUSTER_OPERATIONAL_CREDENTIALS: u32 = 0x3e;
10const CMD_OPERATIONAL_CREDENTIALS_ADDTRUSTEDROOTCERTIFICATE: u32 = 0xb;
11const CMD_OPERATIONAL_CREDENTIALS_ADDNOC: u32 = 0x6;
12const CMD_OPERATIONAL_CSRREQUEST: u32 = 0x4;
13//const CMD_OPERATIONAL_ATTESTATION_REQUEST: u32 = 0x0;
14//const CMD_OPERATIONAL_CERTCHAIN_REQUEST: u32 = 0x2;
15
16const CLUSTER_GENERAL_COMMISSIONING: u32 = 0x30;
17#[cfg(feature = "ble")]
18const CMD_GENERAL_COMMISSIONING_ARMFAILSAFE: u32 = 0;
19//const CMD_GENERAL_COMMISSIONING_SETREGULATORYCONFIG: u32 = 2;
20const CMD_GENERAL_COMMISSIONING_COMMISSIONINGCOMPLETE: u32 = 4;
21
22#[cfg(feature = "ble")]
23const CLUSTER_NETWORK_COMMISSIONING: u32 = 0x31;
24#[cfg(feature = "ble")]
25const CMD_NETWORK_ADD_OR_UPDATE_WIFI: u32 = 2;
26#[cfg(feature = "ble")]
27const CMD_NETWORK_ADD_OR_UPDATE_THREAD: u32 = 3;
28#[cfg(feature = "ble")]
29const CMD_NETWORK_CONNECT: u32 = 6;
30
31/// Credentials for the network the device should join after commissioning.
32#[derive(Clone)]
33pub enum NetworkCreds {
34    /// Commission a Wi-Fi device: provide SSID and passphrase bytes.
35    WiFi { ssid: Vec<u8>, creds: Vec<u8> },
36    /// Commission a Thread device: provide the operational dataset bytes.
37    Thread { dataset: Vec<u8> },
38    /// Device is already on the IP network (Ethernet or pre-provisioned); skip
39    /// NetworkCommissioning cluster writes.
40    AlreadyOnNetwork,
41}
42
43
44/*async fn run_attestation(
45    retrctx: &mut retransmit::RetrContext<'_>,
46    exchange_base: u16,
47) -> Result<()> {
48    // CertificateChainRequest(PAI)
49    {
50        let mut tlv_buf = tlv::TlvBuffer::new();
51        tlv_buf.write_uint8(0, 2)?; // certificateType = 2 = PAI
52        let req = messages::im_invoke_request(
53            0,
54            CLUSTER_OPERATIONAL_CREDENTIALS,
55            CMD_OPERATIONAL_CERTCHAIN_REQUEST,
56            exchange_base,
57            &tlv_buf.data,
58            false,
59        )?;
60        retrctx.send(&req).await?;
61        let _resp = retrctx.get_next_message().await.context("CertChainRequest PAI")?;
62    }
63
64    // CertificateChainRequest(DAC)
65    {
66        let mut tlv_buf = tlv::TlvBuffer::new();
67        tlv_buf.write_uint8(0, 1)?; // certificateType = 1 = DAC
68        let req = messages::im_invoke_request(
69            0,
70            CLUSTER_OPERATIONAL_CREDENTIALS,
71            CMD_OPERATIONAL_CERTCHAIN_REQUEST,
72            exchange_base.wrapping_add(1),
73            &tlv_buf.data,
74            false,
75        )?;
76        retrctx.send(&req).await?;
77        let _resp = retrctx.get_next_message().await.context("CertChainRequest DAC")?;
78    }
79
80    // AttestationRequest
81    {
82        let mut nonce = [0u8; 32];
83        rand::thread_rng().fill_bytes(&mut nonce);
84        let mut tlv_buf = tlv::TlvBuffer::new();
85        tlv_buf.write_octetstring(0, &nonce)?;
86        let req = messages::im_invoke_request(
87            0,
88            CLUSTER_OPERATIONAL_CREDENTIALS,
89            CMD_OPERATIONAL_ATTESTATION_REQUEST,
90            exchange_base.wrapping_add(2),
91            &tlv_buf.data,
92            false,
93        )?;
94        retrctx.send(&req).await?;
95        let _resp = retrctx.get_next_message().await.context("AttestationRequest")?;
96    }
97
98    Ok(())
99}*/
100
101async fn push_ca_cert(
102    retrcrx: &mut retransmit::RetrContext<'_>,
103    cm: &dyn certmanager::CertManager,
104    exchange_id: u16,
105) -> Result<()> {
106    let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
107    let ca_cert = cm.get_ca_cert()?;
108    let mcert = cert_matter::convert_x509_bytes_to_matter(&ca_cert, &ca_pubkey)?;
109    log::debug!("AddTrustedRootCertificate: matter cert TLV ({} bytes): {}", mcert.len(),
110        mcert.iter().map(|b| format!("{:02x}", b)).collect::<Vec<_>>().join(""));
111    let mut tlv = tlv::TlvBuffer::new();
112    tlv.write_octetstring(0, &mcert)?;
113    let t1 = messages::im_invoke_request(
114        0,
115        CLUSTER_OPERATIONAL_CREDENTIALS,
116        CMD_OPERATIONAL_CREDENTIALS_ADDTRUSTEDROOTCERTIFICATE,
117        exchange_id,
118        &tlv.data,
119        false,
120    )?;
121    retrcrx.send(&t1).await?;
122
123    // push ca cert response
124    let resp = retrcrx.get_next_message().await?;
125    let noc_status = {
126        resp.tlv
127            .get_int(&[1, 0, 1, 1, 0])
128            .context("can't get status for AddTrustedRootCertificate")?
129    };
130    if noc_status != 0 {
131        return Err(anyhow::anyhow!(
132            "AddTrustedRootCertificate failed with status {}/{}",
133            noc_status,
134            noc_status_to_str(noc_status)
135        ));
136    }
137    Ok(())
138}
139
140fn noc_status_to_str(status: u64) -> &'static str {
141    match status {
142        0 => "Success",
143        1 => "InvalidPublicKey",
144        2 => "InvalidNodeOpId",
145        3 => "InvalidNOC",
146        4 => "MissingCsr",
147        5 => "TableFull",
148        6 => "InvalidAdminSubject",
149        7 => "?",
150        8 => "?",
151        9 => "FabricConflict",
152        10 => "LabelConflict",
153        11 => "InvalidFabricIndex",
154        _ => "UnknownStatus",
155    }
156}
157
158async fn push_device_cert(
159    retrcrx: &mut retransmit::RetrContext<'_>,
160    cm: &dyn certmanager::CertManager,
161    csrd: x509_cert::request::CertReq,
162    node_id: u64,
163    controller_id: u64,
164    fabric: &Fabric,
165    exchange_id: u16,
166) -> Result<()> {
167    let ca_id = fabric.ca_id;
168    let ca_pubkey = cm.get_ca_key()?.public_key().to_sec1_bytes();
169    let node_public_key = csrd
170        .info
171        .public_key
172        .subject_public_key
173        .as_bytes()
174        .context("can't extract pubkey from csr")?;
175    let ca_private = cm.get_ca_key()?;
176    let noc_x509 = cert_x509::encode_x509(
177        node_public_key,
178        node_id,
179        cm.get_fabric_id(),
180        ca_id,
181        &ca_private,
182        false,
183    )?;
184    let noc = cert_matter::convert_x509_bytes_to_matter(&noc_x509, &ca_pubkey)?;
185    let mut tlv = tlv::TlvBuffer::new();
186    tlv.write_octetstring(0, &noc)?;
187    tlv.write_octetstring(2, &fabric.ipk_epoch_key)?;
188    tlv.write_uint64(3, controller_id)?;
189    tlv.write_uint64(4, 101)?;
190    let t1 = messages::im_invoke_request(
191        0,
192        CLUSTER_OPERATIONAL_CREDENTIALS,
193        CMD_OPERATIONAL_CREDENTIALS_ADDNOC,
194        exchange_id,
195        &tlv.data,
196        false,
197    )?;
198    retrcrx.send(&t1).await?;
199
200    let resp = retrcrx.get_next_message().await?;
201    let noc_status = {
202        resp.tlv
203            .get_int(&[1, 0, 0, 1, 0])
204            .context("can't get status for AddNOC")?
205    };
206    if noc_status != 0 {
207        return Err(anyhow::anyhow!("AddNOC failed with status {}/{}", noc_status, noc_status_to_str(noc_status)));
208    }
209    Ok(())
210}
211
212async fn send_csr(
213    retrcrx: &mut retransmit::RetrContext<'_>,
214    exchange_id: u16,
215) -> Result<x509_cert::request::CertReq> {
216    let mut tlv = tlv::TlvBuffer::new();
217    let mut random_csr_nonce = vec![0; 32];
218    rand::thread_rng().fill_bytes(&mut random_csr_nonce);
219    tlv.write_octetstring(0, &random_csr_nonce)?;
220    let csr_request = messages::im_invoke_request(
221        0,
222        CLUSTER_OPERATIONAL_CREDENTIALS,
223        CMD_OPERATIONAL_CSRREQUEST,
224        exchange_id,
225        &tlv.data,
226        false,
227    )?;
228    retrcrx.send(&csr_request).await?;
229
230    let csr_msg = retrcrx.get_next_message().await?;
231
232    let csr_tlve = csr_msg
233        .tlv
234        .get_octet_string(&[1, 0, 0, 1, 0])
235        .context("csr tlv missing")?;
236    let csr_t = tlv::decode_tlv(csr_tlve).context("csr tlv can't decode")?;
237    let csr = csr_t
238        .get_octet_string(&[1])
239        .context("csr tlv in tlv missing")?;
240    let csrd = x509_cert::request::CertReq::try_from(csr)?;
241    Ok(csrd)
242}
243
244async fn commissioning_complete(
245    connection: &dyn ConnectionTrait,
246    cm: &dyn certmanager::CertManager,
247    node_id: u64,
248    controller_id: u64,
249    fabric: &Fabric,
250) -> Result<session::Session> {
251    let ses = auth_sigma(connection, fabric, cm, node_id, controller_id).await?;
252    let t1 = messages::im_invoke_request(
253        0,
254        CLUSTER_GENERAL_COMMISSIONING,
255        CMD_GENERAL_COMMISSIONING_COMMISSIONINGCOMPLETE,
256        30,
257        &[],
258        false,
259    )?;
260    let mut retrctx = retransmit::RetrContext::new(connection, &ses);
261
262    retrctx.send(&t1).await?;
263    let resp = retrctx.get_next_message().await?;
264    let comresp_status = {
265        resp.tlv
266            .get_int(&[1, 0, 0, 1, 0])
267            .context("can't get status from CommissioningCompleteResponse")?
268    };
269    if comresp_status != 0 {
270        return Err(anyhow::anyhow!(
271            "CommissioningComplete failed with status {}",
272            comresp_status
273        ));
274    }
275    Ok(ses)
276}
277
278pub(crate) async fn commission(
279    connection: &dyn ConnectionTrait,
280    session: &mut session::Session,
281    fabric: &Fabric,
282    cm: &dyn certmanager::CertManager,
283    node_id: u64,
284    controller_id: u64,
285) -> Result<session::Session> {
286    // node operational credentials procedure
287    let mut retrctx = retransmit::RetrContext::new(connection, session);
288    let base: u16 = rand::random();
289
290    let csrd = send_csr(&mut retrctx, base).await?;
291
292    push_ca_cert(&mut retrctx, cm, base.wrapping_add(1)).await?;
293
294    push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, base.wrapping_add(2)).await?;
295
296    let ses = commissioning_complete(connection, cm, node_id, controller_id, fabric).await?;
297
298    Ok(ses)
299}
300
301#[cfg(feature = "ble")]
302async fn arm_failsafe(
303    retrctx: &mut retransmit::RetrContext<'_>,
304    timeout_secs: u16,
305    exchange_id: u16,
306) -> Result<()> {
307    let mut tlv_buf = tlv::TlvBuffer::new();
308    tlv_buf.write_uint16(0, timeout_secs)?;
309    tlv_buf.write_uint64(1, 0)?; // breadcrumb
310    let req = messages::im_invoke_request(
311        0,
312        CLUSTER_GENERAL_COMMISSIONING,
313        CMD_GENERAL_COMMISSIONING_ARMFAILSAFE,
314        exchange_id,
315        &tlv_buf.data,
316        false,
317    )?;
318    retrctx.send(&req).await?;
319    let resp = retrctx.get_next_message().await?;
320    let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ArmFailSafe: status missing")?;
321    if status != 0 {
322        return Err(anyhow::anyhow!("ArmFailSafe failed with status {}", status));
323    }
324    Ok(())
325}
326
327/*async fn set_regulatory_config(
328    retrctx: &mut retransmit::RetrContext<'_>,
329    exchange_id: u16,
330) -> Result<()> {
331    let mut tlv_buf = tlv::TlvBuffer::new();
332    tlv_buf.write_uint8(0, 0)?;  // location type: Indoor
333    tlv_buf.write_string(1, "XX")?; // regulatory location (placeholder)
334    tlv_buf.write_uint64(2, 0)?;  // breadcrumb
335    let req = messages::im_invoke_request(
336        0,
337        CLUSTER_GENERAL_COMMISSIONING,
338        CMD_GENERAL_COMMISSIONING_SETREGULATORYCONFIG,
339        exchange_id,
340        &tlv_buf.data,
341        false,
342    )?;
343    retrctx.send(&req).await?;
344    let resp = retrctx.get_next_message().await?;
345    let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("SetRegulatoryConfig: status missing")?;
346    if status != 0 {
347        return Err(anyhow::anyhow!("SetRegulatoryConfig failed with status {}", status));
348    }
349    Ok(())
350}*/
351
352#[cfg(feature = "ble")]
353async fn add_or_update_wifi(
354    retrctx: &mut retransmit::RetrContext<'_>,
355    ssid: &[u8],
356    creds: &[u8],
357    exchange_id: u16,
358) -> Result<Vec<u8>> {
359    let mut tlv_buf = tlv::TlvBuffer::new();
360    tlv_buf.write_octetstring(0, ssid)?;
361    tlv_buf.write_octetstring(1, creds)?;
362    let req = messages::im_invoke_request(
363        0,
364        CLUSTER_NETWORK_COMMISSIONING,
365        CMD_NETWORK_ADD_OR_UPDATE_WIFI,
366        exchange_id,
367        &tlv_buf.data,
368        false,
369    )?;
370    retrctx.send(&req).await?;
371    let resp = retrctx.get_next_message().await?;
372    let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateWifiNetwork: status missing")?;
373    if status != 0 {
374        return Err(anyhow::anyhow!("AddOrUpdateWifiNetwork failed with status {}", status));
375    }
376    Ok(ssid.to_vec())
377}
378
379#[cfg(feature = "ble")]
380async fn add_or_update_thread(
381    retrctx: &mut retransmit::RetrContext<'_>,
382    dataset: &[u8],
383    exchange_id: u16,
384) -> Result<Vec<u8>> {
385    let mut tlv_buf = tlv::TlvBuffer::new();
386    tlv_buf.write_octetstring(0, dataset)?;
387    let req = messages::im_invoke_request(
388        0,
389        CLUSTER_NETWORK_COMMISSIONING,
390        CMD_NETWORK_ADD_OR_UPDATE_THREAD,
391        exchange_id,
392        &tlv_buf.data,
393        false,
394    )?;
395    retrctx.send(&req).await?;
396    let resp = retrctx.get_next_message().await?;
397    let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("AddOrUpdateThreadNetwork: status missing")?;
398    if status != 0 {
399        return Err(anyhow::anyhow!("AddOrUpdateThreadNetwork failed with status {}", status));
400    }
401    // Return first 8 bytes as network ID (typically the Extended PAN ID)
402    Ok(dataset[..dataset.len().min(8)].to_vec())
403}
404
405#[cfg(feature = "ble")]
406async fn connect_network(
407    retrctx: &mut retransmit::RetrContext<'_>,
408    network_id: &[u8],
409    exchange_id: u16,
410) -> Result<()> {
411    let mut tlv_buf = tlv::TlvBuffer::new();
412    tlv_buf.write_octetstring(0, network_id)?;
413    tlv_buf.write_uint64(1, 0)?; // breadcrumb
414    let req = messages::im_invoke_request(
415        0,
416        CLUSTER_NETWORK_COMMISSIONING,
417        CMD_NETWORK_CONNECT,
418        exchange_id,
419        &tlv_buf.data,
420        false,
421    )?;
422    retrctx.send(&req).await?;
423    let resp = retrctx.get_next_message().await?;
424    let status = resp.tlv.get_int(&[1, 0, 0, 1, 0]).context("ConnectNetwork: status missing")?;
425    if status != 0 {
426        return Err(anyhow::anyhow!("ConnectNetwork failed with status {}", status));
427    }
428    Ok(())
429}
430
431#[cfg(feature = "ble")]
432pub(crate) async fn commission_ble_phase(
433    ble_connection: &dyn ConnectionTrait,
434    pase_session: &mut session::Session,
435    fabric: &Fabric,
436    cm: &dyn certmanager::CertManager,
437    node_id: u64,
438    controller_id: u64,
439    network_creds: &NetworkCreds,
440) -> Result<()> {
441    let mut retrctx = retransmit::RetrContext::new(ble_connection, pase_session);
442    let base: u16 = rand::random();
443    let e_arm      = base;
444    //let e_reg      = base.wrapping_add(1);
445    // run_attestation uses e_attest+0, e_attest+1, e_attest+2 (3 exchanges)
446    //let e_attest   = base.wrapping_add(2);
447    let e_csr      = base.wrapping_add(5);
448    let e_ca       = base.wrapping_add(6);
449    let e_noc      = base.wrapping_add(7);
450    let e_net1     = base.wrapping_add(8);
451    let e_net2     = base.wrapping_add(9);
452
453    arm_failsafe(&mut retrctx, 60, e_arm).await.context("ArmFailSafe")?;
454    log::debug!("Failsafe armed for 60 seconds");
455
456    //set_regulatory_config(&mut retrctx, e_reg).await.context("SetRegulatoryConfig")?;
457    //log::debug!("Regulatory configuration set");
458
459    //run_attestation(&mut retrctx, e_attest).await.context("Attestation")?;
460    //log::debug!("Attestation completed");
461
462    let csrd = send_csr(&mut retrctx, e_csr).await?;
463    log::debug!("CSR received");
464    push_ca_cert(&mut retrctx, cm, e_ca).await?;
465    log::debug!("CA certificate pushed");
466    push_device_cert(&mut retrctx, cm, csrd, node_id, controller_id, fabric, e_noc).await?;
467    log::debug!("Device certificate pushed");
468
469    match network_creds {
470        NetworkCreds::WiFi { ssid, creds } => {
471            let net_id = add_or_update_wifi(&mut retrctx, ssid, creds, e_net1).await?;
472            connect_network(&mut retrctx, &net_id, e_net2).await?;
473            log::debug!("WiFi network connected");
474            // Give the device time to join the network before the caller probes mDNS.
475            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
476        }
477        NetworkCreds::Thread { dataset } => {
478            let net_id = add_or_update_thread(&mut retrctx, dataset, e_net1).await?;
479            connect_network(&mut retrctx, &net_id, e_net2).await?;
480            log::debug!("Thread network connected");
481            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
482        }
483        NetworkCreds::AlreadyOnNetwork => {}
484    }
485
486    Ok(())
487}
488
489#[cfg(feature = "ble")]
490pub(crate) async fn commissioning_complete_udp(
491    udp_connection: &dyn ConnectionTrait,
492    cm: &dyn certmanager::CertManager,
493    node_id: u64,
494    controller_id: u64,
495    fabric: &Fabric,
496) -> Result<session::Session> {
497    commissioning_complete(udp_connection, cm, node_id, controller_id, fabric).await
498}