Skip to main content

rs_matter/onboard/
cac.rs

1/*
2 *
3 *    Copyright (c) 2026 Project CHIP Authors
4 *
5 *    Licensed under the Apache License, Version 2.0 (the "License");
6 *    you may not use this file except in compliance with the License.
7 *    You may obtain a copy of the License at
8 *
9 *        http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *    Unless required by applicable law or agreed to in writing, software
12 *    distributed under the License is distributed on an "AS IS" BASIS,
13 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *    See the License for the specific language governing permissions and
15 *    limitations under the License.
16 */
17
18//! CA chain generation utilities — Root CA (RCAC) and Intermediate
19//! CA (ICAC) cert minting.
20//!
21//! These primitives are deliberately **separate** from
22//! [`NocGenerator`](super::noc::NocGenerator). In real
23//! Matter PKI:
24//!
25//! - The **RCAC** is generated once per organisation, typically on an
26//!   HSM, and its private key never resides on a running controller.
27//!   It's used (offline) to sign one or more ICACs, then put away.
28//! - The **ICAC** is generated occasionally (per controller / region /
29//!   product family), typically at factory-provisioning time. Its
30//!   private key gets baked into a controller's firmware or secure
31//!   storage.
32//! - The **NOC** is signed at runtime by the controller every time it
33//!   commissions a new device.
34//!
35//! These helpers cover the first two cases (the third lives in
36//! `NocGenerator`). They're plain functions returning
37//! `(privkey, cert_bytes)` so the caller chooses exactly what to
38//! retain — fine for the test/self-contained-controller path where
39//! everything happens in one process, equally fine for the
40//! factory / HSM path where each function might run on a different
41//! machine.
42//!
43//! All certs follow Matter Core spec cert layout: subject DN
44//! carries the fabric ID and the CA's own subject ID
45//! (`RootCaId` / `IcaId`); issuer DN carries the parent's subject ID
46//! (with `is_rcac` set when the parent is the RCAC itself).
47
48use crate::cert::gen::{CertGenerator, CertType, IssuerDN, SubjectDN, Validity};
49use crate::cert::CertRef;
50use crate::crypto::{
51    CanonPkcPublicKey, CanonPkcSecretKey, CanonPkcSecretKeyRef, Crypto, PublicKey, RngCore,
52    SecretKey, SigningSecretKey,
53};
54use crate::error::Error;
55use crate::tlv::TLVElement;
56
57pub struct RcacGenerator<'a> {
58    buf: &'a mut [u8],
59}
60
61impl<'a> RcacGenerator<'a> {
62    /// Create a new generator with the provided buffer for cert encoding.
63    pub const fn new(buf: &'a mut [u8]) -> Self {
64        Self { buf }
65    }
66
67    /// Build a fresh self-signed RCAC for a fabric.
68    ///
69    /// Returns `(rcac_privkey, rcac_bytes)`:
70    ///   - `rcac_privkey` — the RCAC's P-256 private key (canonical
71    ///     bytes). Treat as the fabric's trust anchor; in production
72    ///     this is what lives in an HSM. Retain only as long as needed
73    ///     to sign one or more ICACs; drop it afterwards.
74    ///   - `rcac_bytes` — Matter-TLV-encoded RCAC. Install in
75    ///     [`crate::fabric::Fabric::root_ca`] (via
76    ///     [`crate::fabric::Fabrics::add`]) and ship to every fabric
77    ///     member.
78    ///
79    /// The RCAC's subject ID is randomly generated; the caller can
80    /// extract it from the cert bytes via [`CertRef::get_ca_id`].
81    pub fn generate<C: Crypto>(
82        &mut self,
83        crypto: C,
84        fabric_id: u64,
85        validity: Validity,
86    ) -> Result<(CanonPkcSecretKey, &[u8]), Error> {
87        // Random 64-bit subject ID for this RCAC.
88        let mut rcac_id_bytes = [0u8; 8];
89        crypto.rand()?.fill_bytes(&mut rcac_id_bytes);
90        let rcac_id = u64::from_be_bytes(rcac_id_bytes);
91
92        // P-256 keypair. Persist `rcac_privkey` as canonical bytes; the
93        // borrowed `rcac_key` here is only for the signing operation
94        // below.
95        let rcac_key = crypto.generate_secret_key()?;
96
97        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
98        rcac_key.pub_key()?.write_canon(&mut rcac_pubkey_canon)?;
99
100        let mut serial_bytes = [0u8; 8];
101        crypto.rand()?.fill_bytes(&mut serial_bytes);
102
103        let cert_len = CertGenerator::new(self.buf).generate(
104            &crypto,
105            CertType::Rcac,
106            &serial_bytes,
107            validity,
108            SubjectDN {
109                node_id: None,
110                fabric_id: Some(fabric_id),
111                cat_ids: &[],
112                ca_id: Some(rcac_id),
113            },
114            // RCAC is self-signed; issuer DN is ignored by `generate`
115            // when `cert_type == Rcac` but a value is still required.
116            IssuerDN {
117                ca_id: None,
118                fabric_id: None,
119                is_rcac: false,
120            },
121            rcac_pubkey_canon.reference(),
122            None, // self-signed: no separate issuer pubkey
123            &rcac_key,
124        )?;
125
126        let mut rcac_privkey = CanonPkcSecretKey::new();
127        rcac_key.write_canon(&mut rcac_privkey)?;
128
129        Ok((rcac_privkey, &self.buf[..cert_len]))
130    }
131}
132
133pub struct IcacGenerator<'a> {
134    buf: &'a mut [u8],
135}
136
137impl<'a> IcacGenerator<'a> {
138    /// Create a new generator with the provided buffer for cert encoding.
139    pub const fn new(buf: &'a mut [u8]) -> Self {
140        Self { buf }
141    }
142
143    /// Build a fresh ICAC signed by an existing RCAC.
144    ///
145    /// Inputs:
146    ///   - `rcac_privkey` — borrowed reference to the RCAC private key.
147    ///     Used here exactly once (to sign the ICAC TBS); the caller
148    ///     decides what to do with the key afterwards (production path:
149    ///     drop / return to HSM).
150    ///   - `rcac_bytes` — the RCAC's TLV-encoded cert. The function
151    ///     reads the RCAC's subject ID and fabric ID from it to populate
152    ///     the ICAC's issuer DN; if `fabric_id` is supplied and disagrees
153    ///     with what the RCAC carries, the function errors out.
154    ///
155    /// Returns `(icac_privkey, icac_bytes)`. The ICAC's own subject ID is
156    /// random; recover via [`CertRef::get_ca_id`] from the returned bytes.
157    pub fn generate<C: Crypto>(
158        &mut self,
159        crypto: C,
160        rcac_privkey: CanonPkcSecretKeyRef<'_>,
161        rcac_bytes: &[u8],
162        validity: Validity,
163    ) -> Result<(CanonPkcSecretKey, &[u8]), Error> {
164        let rcac = CertRef::new(TLVElement::new(rcac_bytes));
165        let rcac_pubkey = rcac.pubkey()?.try_into()?;
166        let rcac_id = rcac.get_ca_id()?;
167        let fabric_id = rcac.get_fabric_id()?;
168
169        // Random ICAC subject ID.
170        let mut icac_id_bytes = [0u8; 8];
171        crypto.rand()?.fill_bytes(&mut icac_id_bytes);
172        let icac_id = u64::from_be_bytes(icac_id_bytes);
173
174        // ICAC keypair (retained by caller).
175        let icac_key = crypto.generate_secret_key()?;
176
177        let mut icac_pubkey_canon = CanonPkcPublicKey::new();
178        icac_key.pub_key()?.write_canon(&mut icac_pubkey_canon)?;
179
180        // RCAC signing key — borrowed only for this build.
181        let rcac_signing_key = crypto.secret_key(rcac_privkey)?;
182
183        let mut serial_bytes = [0u8; 8];
184        crypto.rand()?.fill_bytes(&mut serial_bytes);
185
186        let cert_len = CertGenerator::new(self.buf).generate(
187            &crypto,
188            CertType::Icac,
189            &serial_bytes,
190            validity,
191            SubjectDN {
192                node_id: None,
193                fabric_id: Some(fabric_id),
194                cat_ids: &[],
195                ca_id: Some(icac_id),
196            },
197            IssuerDN {
198                ca_id: Some(rcac_id),
199                fabric_id: Some(fabric_id),
200                is_rcac: true,
201            },
202            icac_pubkey_canon.reference(),
203            Some(rcac_pubkey),
204            &rcac_signing_key,
205        )?;
206
207        let mut icac_privkey = CanonPkcSecretKey::new();
208        icac_key.write_canon(&mut icac_privkey)?;
209
210        Ok((icac_privkey, &self.buf[..cert_len]))
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use crate::cert::gen::VALID_FOREVER;
217    use crate::cert::{CertRef, MAX_CERT_TLV_AND_ASN1_LEN};
218    use crate::crypto::test_only_crypto;
219    use crate::tlv::TLVElement;
220
221    use super::*;
222
223    #[test]
224    fn rcac_carries_supplied_fabric_id() {
225        let crypto = test_only_crypto();
226
227        let mut cert_buf = [0; MAX_CERT_TLV_AND_ASN1_LEN];
228        let mut rcac_gen = RcacGenerator::new(&mut cert_buf);
229
230        let (_priv, rcac) = rcac_gen
231            .generate(&crypto, 0xABCD1234, VALID_FOREVER)
232            .unwrap();
233
234        let cert = CertRef::new(TLVElement::new(rcac));
235        assert_eq!(cert.get_fabric_id().unwrap(), 0xABCD1234);
236        // ca_id is random but must be set.
237        let _ = cert.get_ca_id().unwrap();
238    }
239
240    #[test]
241    fn icac_inherits_rcac_fabric_id() {
242        let crypto = test_only_crypto();
243        let fabric_id = 0x0102030405060708u64;
244
245        let mut cert_buf1 = [0; MAX_CERT_TLV_AND_ASN1_LEN];
246        let mut rcac_gen = RcacGenerator::new(&mut cert_buf1);
247        let (rcac_priv, rcac) = rcac_gen
248            .generate(&crypto, fabric_id, VALID_FOREVER)
249            .unwrap();
250
251        let mut cert_buf2 = [0; MAX_CERT_TLV_AND_ASN1_LEN];
252        let mut icac_gen = IcacGenerator::new(&mut cert_buf2);
253        let (_icac_priv, icac) = icac_gen
254            .generate(&crypto, rcac_priv.reference(), rcac, VALID_FOREVER)
255            .unwrap();
256
257        let icac_cert = CertRef::new(TLVElement::new(icac));
258        assert_eq!(icac_cert.get_fabric_id().unwrap(), fabric_id);
259        let _ = icac_cert.get_ca_id().unwrap();
260    }
261}