Skip to main content

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