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}