Skip to main content

rs_matter/cert/
gen.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//! Matter TLV-encoded certificate generator (RCAC / ICAC / NOC).
19//!
20//! [`CertGenerator`] is the one-shot, caller-buffer cert generator
21//! underlying [`crate::onboard::cac::RcacGenerator`],
22//! [`crate::onboard::cac::IcacGenerator`] and
23//! [`crate::onboard::noc::NocGenerator`]. It's parametric over
24//! [`CertType`], with subject/issuer/validity/keys plumbed in by the
25//! caller. (Matter Specification 6.5 "Operational Certificate Encoding")
26
27use crate::attest::trust_store::{compute_key_id, KeyId};
28use crate::cert::CertRef;
29use crate::crypto::{CanonPkcPublicKeyRef, CanonPkcSignature, Crypto, PKC_CANON_PUBLIC_KEY_LEN};
30use crate::error::{Error, ErrorCode};
31use crate::tlv::{TLVElement, TLVTag, TLVWrite};
32use crate::utils::storage::WriteBuf;
33
34use super::{x509::key_usage_tlv, CertTag, DNTag};
35
36/// Certificate kind passed to [`CertGenerator::generate`].
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum CertType {
39    /// Root CA Certificate (self-signed, is_ca=true, no path_len).
40    Rcac,
41    /// Intermediate CA Certificate (signed by RCAC, is_ca=true, path_len=0).
42    Icac,
43    /// Node Operational Certificate (end entity, is_ca=false).
44    Noc,
45}
46
47pub struct IssuerDN {
48    pub(crate) ca_id: Option<u64>,
49    pub(crate) fabric_id: Option<u64>,
50    pub(crate) is_rcac: bool,
51}
52
53#[derive(Clone, Copy)]
54pub struct SubjectDN<'a> {
55    pub(crate) node_id: Option<u64>,
56    pub(crate) fabric_id: Option<u64>,
57    pub(crate) cat_ids: &'a [u32],
58    pub(crate) ca_id: Option<u64>,
59}
60
61/// Validity period for certificates, represented as seconds since the Matter epoch (2000-01-01T00:00:00Z).
62#[derive(Clone, Copy)]
63pub struct Validity {
64    /// NotBefore time (seconds since Matter epoch)
65    ///
66    /// This must not be 0 (the Matter epoch start) to avoid collision with CHIP's epoch=0 sentinel in ASN.1 time encoding.
67    pub not_before: u32,
68    /// NotAfter time (seconds since Matter epoch, 0 = no expiry)
69    pub not_after: u32,
70}
71
72// NotBefore MUST NOT be 0 (Matter epoch start, 2000-01-01).
73// CHIP's ChipEpochToASN1Time treats epoch=0 as the "no
74// well-defined expiration date" sentinel and re-emits it as
75// GeneralizedTime "99991231235959Z" regardless of which field
76// it appears in (see CHIPCert.cpp:1076-1106 and the
77// explanatory comment about CHIP epoch 0 NotBefore producing
78// an invalid TBS signature on round-trip).
79//
80// We sign over UTCTime "000101000000Z" (Matter epoch); CHIP
81// would reconstruct GeneralizedTime "99991231235959Z" and the
82// hash would mismatch.  Using 1 second past the Matter epoch
83// avoids the sentinel collision while keeping the cert
84// effectively unbounded on the lower end.
85pub const VALID_FOREVER: Validity = Validity {
86    not_before: 1, // 2000-01-01 00:00:01 — past CHIP's epoch=0 sentinel
87    not_after: 0,  // no expiry (NotAfter sentinel is legitimate)
88};
89
90/// One-shot Matter-TLV certificate generator writing into a
91/// caller-supplied buffer.
92///
93/// Typical callers are the three issuers in [`crate::onboard::cac`]
94/// (RCAC, ICAC) and [`crate::onboard::noc`] (NOC); each one
95/// constructs a `CertGenerator` over its scratch buffer, calls
96/// [`Self::generate`] once and discards the generator. Subject /
97/// issuer / pubkey / signing-key consistency is the caller's
98/// responsibility — `generate` only checks invariants generic across
99/// cert types (serial number well-formedness).
100pub struct CertGenerator<'a> {
101    buf: &'a mut [u8],
102}
103
104impl<'a> CertGenerator<'a> {
105    /// Create a new generator over the given output buffer.
106    pub const fn new(buf: &'a mut [u8]) -> Self {
107        Self { buf }
108    }
109
110    /// Generate a Matter-TLV certificate of the given kind, sign it,
111    /// and return the length written to the buffer.
112    ///
113    /// `issuer_pubkey` must be `None` for [`CertType::Rcac`]
114    /// (self-signed: AKID = SKID) and `Some(_)` for `Icac` / `Noc`.
115    /// `signing_key` is the issuer's private key — the RCAC's own key
116    /// for RCAC and ICAC; the ICAC's (or RCAC's) for NOC.
117    #[allow(clippy::too_many_arguments)]
118    pub fn generate<C: Crypto>(
119        &mut self,
120        crypto: C,
121        cert_type: CertType,
122        serial_number: &[u8],
123        validity: Validity,
124        subject: SubjectDN,
125        issuer: IssuerDN,
126        subject_pubkey: CanonPkcPublicKeyRef<'_>,
127        issuer_pubkey: Option<CanonPkcPublicKeyRef<'_>>,
128        signing_key: &C::SecretKey<'_>,
129    ) -> Result<usize, Error> {
130        Self::validate_serial_number(serial_number)?;
131
132        let subject_key_id = compute_key_id(&crypto, subject_pubkey)?;
133
134        let authority_key_id = if let Some(issuer_pk) = issuer_pubkey {
135            compute_key_id(&crypto, issuer_pk)?
136        } else {
137            // Self-signed: AKID = SKID
138            subject_key_id
139        };
140
141        // Build the TBS (To-Be-Signed) certificate
142        let tbs_len = self.write_tbs_certificate(
143            serial_number,
144            validity,
145            subject_pubkey.access(),
146            &subject_key_id,
147            &authority_key_id,
148            cert_type,
149            subject,
150            issuer,
151        )?;
152
153        // Convert TBS to ASN1 format for signing
154        // According to the Matter Spec. "Matter certificate", the signature is over the
155        // "corresponding X.509 certificate, not a signature of the preceding Matter TLV data."
156        let (tlv_buf, asn1_buf) = self.buf.split_at_mut(tbs_len);
157        let tbs_cert_ref = CertRef::new(TLVElement::new(tlv_buf));
158        let asn1_len = tbs_cert_ref.as_asn1(asn1_buf)?;
159
160        // Sign the ASN1-encoded TBS certificate
161        let signature = Self::sign_tbs::<C>(&asn1_buf[..asn1_len], signing_key)?;
162
163        // Append signature to complete the certificate
164        self.append_signature(&signature, tbs_len)
165    }
166
167    /// Write the TBS (To-Be-Signed) certificate structure.
168    ///
169    /// This creates the certificate without the signature.
170    #[allow(clippy::too_many_arguments)]
171    fn write_tbs_certificate(
172        &mut self,
173        serial_number: &[u8],
174        validity: Validity,
175        pubkey: &[u8; PKC_CANON_PUBLIC_KEY_LEN],
176        subject_key_id: &KeyId,
177        authority_key_id: &KeyId,
178        cert_type: CertType,
179        subject: SubjectDN,
180        issuer: IssuerDN,
181    ) -> Result<usize, Error> {
182        let mut tw = WriteBuf::new(self.buf);
183
184        tw.start_struct(&TLVTag::Anonymous)?;
185
186        // 1. Serial Number
187        tw.str(&TLVTag::Context(CertTag::SerialNum as _), serial_number)?;
188
189        // 2. Signature Algorithm (1 = ECDSA-SHA256)
190        tw.u8(&TLVTag::Context(CertTag::SignAlgo as _), 1)?;
191
192        // 3. Issuer
193        tw.start_list(&TLVTag::Context(CertTag::Issuer as _))?;
194        match cert_type {
195            CertType::Rcac => {
196                // Self-signed: issuer = subject
197                if let Some(id) = subject.ca_id {
198                    tw.u64(&TLVTag::Context(DNTag::RootCaId as u8), id)?;
199                }
200                if let Some(fid) = subject.fabric_id {
201                    tw.u64(&TLVTag::Context(DNTag::FabricId as u8), fid)?; // Fabric ID
202                }
203            }
204            CertType::Icac | CertType::Noc => {
205                // Use provided issuer information
206                if let Some(id) = issuer.ca_id {
207                    let tag = if issuer.is_rcac {
208                        DNTag::RootCaId as u8
209                    } else {
210                        DNTag::IcaId as u8
211                    };
212                    tw.u64(&TLVTag::Context(tag), id)?;
213                }
214                if let Some(fid) = issuer.fabric_id {
215                    tw.u64(&TLVTag::Context(DNTag::FabricId as u8), fid)?;
216                }
217            }
218        }
219        tw.end_container()?;
220
221        // 4. Not Before
222        tw.u32(
223            &TLVTag::Context(CertTag::NotBefore as u8),
224            validity.not_before,
225        )?;
226
227        // 5. Not After (0 = no expiry)
228        tw.u32(
229            &TLVTag::Context(CertTag::NotAfter as u8),
230            validity.not_after,
231        )?;
232
233        // 6. Subject
234        tw.start_list(&TLVTag::Context(CertTag::Subject as u8))?;
235        match cert_type {
236            CertType::Noc => {
237                // NOC Subject: NodeId, FabricId, optional CAT IDs
238                if let Some(nid) = subject.node_id {
239                    tw.u64(&TLVTag::Context(DNTag::NodeId as u8), nid)?;
240                }
241                if let Some(fid) = subject.fabric_id {
242                    tw.u64(&TLVTag::Context(DNTag::FabricId as u8), fid)?
243                }
244                for cat_id in subject.cat_ids {
245                    tw.u64(&TLVTag::Context(DNTag::NocCat as u8), *cat_id as u64)?;
246                }
247            }
248            CertType::Icac => {
249                // ICAC Subject: ICAC ID, FabricId
250                if let Some(id) = subject.ca_id {
251                    tw.u64(&TLVTag::Context(DNTag::IcaId as u8), id)?;
252                }
253                if let Some(fid) = subject.fabric_id {
254                    tw.u64(&TLVTag::Context(DNTag::FabricId as u8), fid)?;
255                }
256            }
257            CertType::Rcac => {
258                // RCAC Subject: RCAC ID, FabricId
259                if let Some(id) = subject.ca_id {
260                    tw.u64(&TLVTag::Context(DNTag::RootCaId as u8), id)?;
261                }
262                if let Some(fid) = subject.fabric_id {
263                    tw.u64(&TLVTag::Context(DNTag::FabricId as u8), fid)?;
264                }
265            }
266        }
267        tw.end_container()?;
268
269        // 7. Public Key Algorithm (1 = EC Public Key)
270        tw.u8(&TLVTag::Context(CertTag::PubKeyAlgo as u8), 1)?;
271
272        // 8. EC Curve ID (1 = prime256v1)
273        tw.u8(&TLVTag::Context(CertTag::EcCurveId as u8), 1)?;
274
275        // 9. EC Public Key
276        tw.str(&TLVTag::Context(CertTag::EcPubKey as u8), pubkey)?;
277
278        // 10. Extensions
279        tw.start_list(&TLVTag::Context(CertTag::Extensions as u8))?;
280        Self::write_extensions(&mut tw, cert_type, subject_key_id, authority_key_id)?;
281        tw.end_container()?;
282
283        tw.end_container()?;
284
285        Ok(tw.get_tail())
286    }
287
288    /// Write certificate extensions.
289    fn write_extensions(
290        tw: &mut impl TLVWrite,
291        cert_type: CertType,
292        subject_key_id: &KeyId,
293        authority_key_id: &KeyId,
294    ) -> Result<(), Error> {
295        // 1. Basic Constraints — per Matter Spec:
296        //   RCAC: cA = TRUE,  pathLenConstraint shall NOT be present
297        //   ICAC: cA = TRUE,  pathLenConstraint = 0
298        //   NOC:  cA = FALSE, pathLenConstraint shall NOT be present
299        tw.start_struct(&TLVTag::Context(1))?;
300        match cert_type {
301            CertType::Rcac => {
302                tw.bool(&TLVTag::Context(1), true)?;
303            }
304            CertType::Icac => {
305                tw.bool(&TLVTag::Context(1), true)?;
306                tw.u8(&TLVTag::Context(2), 0)?; // path_len = 0
307            }
308            CertType::Noc => {
309                tw.bool(&TLVTag::Context(1), false)?;
310            }
311        }
312        tw.end_container()?;
313
314        // 2. Key Usage
315        let key_usage = match cert_type {
316            CertType::Rcac | CertType::Icac => {
317                key_usage_tlv::KEY_CERT_SIGN | key_usage_tlv::CRL_SIGN
318            }
319            CertType::Noc => key_usage_tlv::DIGITAL_SIGNATURE,
320        };
321        tw.u16(&TLVTag::Context(2), key_usage)?;
322
323        // 3. Extended Key Usage - for NOC only
324        if cert_type == CertType::Noc {
325            tw.start_array(&TLVTag::Context(3))?;
326            tw.u8(&TLVTag::Anonymous, 1)?; // ServerAuth
327            tw.u8(&TLVTag::Anonymous, 2)?; // ClientAuth
328            tw.end_container()?;
329        }
330
331        // 4. Subject Key Identifier
332        tw.str(&TLVTag::Context(4), subject_key_id)?;
333
334        // 5. Authority Key Identifier (not present for self-signed RCAC in some cases,
335        //    but Matter spec recommends including it)
336        tw.str(&TLVTag::Context(5), authority_key_id)?;
337
338        Ok(())
339    }
340
341    /// Sign the TBS certificate data.
342    fn sign_tbs<C: Crypto>(
343        tbs_data: &[u8],
344        signing_key: &C::SecretKey<'_>,
345    ) -> Result<CanonPkcSignature, Error> {
346        use crate::crypto::SigningSecretKey;
347
348        let mut signature = CanonPkcSignature::new();
349        signing_key.sign(tbs_data, &mut signature)?;
350        Ok(signature)
351    }
352
353    /// Append the signature to complete the certificate.
354    ///
355    /// This reads the TBS data, parses out the structure, and re-writes it
356    /// with the signature field added.
357    fn append_signature(
358        &mut self,
359        signature: &CanonPkcSignature,
360        tbs_len: usize,
361    ) -> Result<usize, Error> {
362        if tbs_len == 0 || self.buf[tbs_len - 1] != 0x18 {
363            return Err(ErrorCode::InvalidData.into());
364        }
365
366        let insert_pos = tbs_len - 1;
367
368        // Use proper TLV encoding via WriteBuf
369        let mut tw = WriteBuf::new(&mut self.buf[insert_pos..]);
370        tw.str(
371            &TLVTag::Context(CertTag::Signature as u8),
372            signature.access(),
373        )?;
374        tw.end_container()?;
375
376        Ok(insert_pos + tw.get_tail())
377    }
378
379    /// Validate serial number format.
380    fn validate_serial_number(serial: &[u8]) -> Result<(), Error> {
381        if serial.is_empty() {
382            return Err(ErrorCode::InvalidData.into());
383        }
384        // Check for unnecessary leading zeros (except one needed for positive sign)
385        if serial.len() > 1 && serial[0] == 0 && (serial[1] & 0x80) == 0 {
386            return Err(ErrorCode::InvalidData.into());
387        }
388        Ok(())
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use crate::{
395        cert::{MAX_CERT_TLV_AND_ASN1_LEN, MAX_CERT_TLV_LEN},
396        crypto::{test_only_crypto, CanonPkcPublicKey, PublicKey, SigningSecretKey},
397        dm::clusters::time_sync::UtcTime,
398    };
399
400    use super::*;
401
402    /// IssuerDN slot for self-signed certs (RCAC). `generate` ignores
403    /// `issuer` entirely when `cert_type == Rcac`, but the call site
404    /// still has to pass *some* value.
405    const RCAC_ISSUER_DN_UNUSED: IssuerDN = IssuerDN {
406        ca_id: None,
407        fabric_id: None,
408        is_rcac: false,
409    };
410
411    #[test]
412    fn test_validate_serial_number_valid() {
413        assert!(CertGenerator::validate_serial_number(&[0x01]).is_ok());
414        assert!(CertGenerator::validate_serial_number(&[0x00, 0x80]).is_ok()); // Leading zero needed for positive
415        assert!(CertGenerator::validate_serial_number(&[0x7F]).is_ok());
416    }
417
418    #[test]
419    fn test_validate_serial_number_invalid() {
420        assert!(CertGenerator::validate_serial_number(&[]).is_err()); // Empty
421        assert!(CertGenerator::validate_serial_number(&[0x00, 0x01]).is_err());
422        // Unnecessary leading zero
423    }
424
425    /// Test building a self-signed RCAC
426    #[test]
427    fn test_build_rcac() {
428        let crypto = test_only_crypto();
429
430        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
431        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
432
433        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
434        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
435
436        let serial_number = &[0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF];
437        let rcac_id = 0x1234567890u64;
438        let fabric_id = 0x0000000000000001u64;
439        let not_before = 0u32; // Matter epoch start
440        let not_after = 0u32; // No expiry
441
442        let subject = SubjectDN {
443            node_id: None,
444            fabric_id: Some(fabric_id),
445            cat_ids: &[],
446            ca_id: Some(rcac_id),
447        };
448
449        let validity = Validity {
450            not_before,
451            not_after,
452        };
453
454        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
455        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
456            &crypto,
457            CertType::Rcac,
458            serial_number,
459            validity,
460            subject,
461            RCAC_ISSUER_DN_UNUSED,
462            rcac_pubkey_canon.reference(),
463            None,
464            &rcac_secret_key,
465        ));
466
467        assert!(len > 100);
468        assert!(len < MAX_CERT_TLV_LEN);
469    }
470
471    #[test]
472    fn test_rcac_self_verify() {
473        let crypto = test_only_crypto();
474
475        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
476        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
477
478        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
479        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
480
481        let subject = SubjectDN {
482            node_id: None,
483            fabric_id: Some(0x0000_0000_0000_0001),
484            cat_ids: &[],
485            ca_id: Some(0x1122_3344_5566_7788),
486        };
487
488        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
489        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
490            &crypto,
491            CertType::Rcac,
492            &[0x01],
493            VALID_FOREVER,
494            subject,
495            RCAC_ISSUER_DN_UNUSED,
496            rcac_pubkey_canon.reference(),
497            None,
498            &rcac_secret_key,
499        ));
500
501        // Re-parse the just-built RCAC and self-verify.
502        let cert = CertRef::new(crate::tlv::TLVElement::new(&cert_buf[..len]));
503        let mut scratch = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
504        let res = cert
505            .verify_chain_start(
506                &crypto,
507                UtcTime::Reliable(VALID_FOREVER.not_before as u64 * 1_000_000),
508            )
509            .finalise(&mut scratch);
510        assert!(
511            res.is_ok(),
512            "RCAC built by CertGenerator failed self-verification: {res:?}"
513        );
514    }
515
516    /// Test building an ICAC signed by RCAC
517    #[test]
518    fn test_build_icac() {
519        let crypto = test_only_crypto();
520
521        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
522
523        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
524        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
525        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
526
527        let icac_secret_key = unwrap!(crypto.generate_secret_key());
528
529        let icac_pubkey = icac_secret_key.pub_key().unwrap();
530        let mut icac_pubkey_canon = CanonPkcPublicKey::new();
531        icac_pubkey.write_canon(&mut icac_pubkey_canon).unwrap();
532
533        let serial_number = &[0x01, 0x02, 0x03, 0x04];
534        let icac_id = 0x1234u64;
535        let rcac_id = 0x5678u64;
536        let fabric_id = 0x0000000000000001u64;
537
538        let subject = SubjectDN {
539            node_id: None,
540            fabric_id: Some(fabric_id),
541            cat_ids: &[],
542            ca_id: Some(icac_id),
543        };
544
545        let issuer = IssuerDN {
546            ca_id: Some(rcac_id),
547            fabric_id: Some(fabric_id),
548            is_rcac: true,
549        };
550
551        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
552        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
553            &crypto,
554            CertType::Icac,
555            serial_number,
556            VALID_FOREVER,
557            subject,
558            issuer,
559            icac_pubkey_canon.reference(),
560            Some(rcac_pubkey_canon.reference()),
561            &rcac_secret_key,
562        ));
563
564        assert!(len > 100);
565        assert!(len < MAX_CERT_TLV_LEN);
566    }
567
568    /// Test building a NOC signed by RCAC
569    #[test]
570    fn test_build_noc_signed_by_rcac() {
571        let crypto = test_only_crypto();
572
573        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
574
575        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
576        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
577        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
578
579        let noc_secret_key = unwrap!(crypto.generate_secret_key());
580
581        let noc_pubkey = noc_secret_key.pub_key().unwrap();
582        let mut noc_pubkey_canon = CanonPkcPublicKey::new();
583        noc_pubkey.write_canon(&mut noc_pubkey_canon).unwrap();
584
585        let serial_number = &[0xAA, 0xBB, 0xCC];
586        let node_id = 0x1122334455667788u64;
587        let fabric_id = 0x0000000000000001u64;
588        let rcac_id = 0x9999u64;
589        let not_before = 0u32;
590        let not_after = 0u32;
591
592        let subject = SubjectDN {
593            node_id: Some(node_id),
594            fabric_id: Some(fabric_id),
595            cat_ids: &[], // No CAT IDs
596            ca_id: None,
597        };
598
599        let validity = Validity {
600            not_before,
601            not_after,
602        };
603
604        let issuer = IssuerDN {
605            ca_id: Some(rcac_id),
606            fabric_id: Some(fabric_id),
607            is_rcac: true, // Issuer is RCAC
608        };
609
610        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
611        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
612            &crypto,
613            CertType::Noc,
614            serial_number,
615            validity,
616            subject,
617            issuer,
618            noc_pubkey_canon.reference(),
619            Some(rcac_pubkey_canon.reference()),
620            &rcac_secret_key,
621        ));
622
623        assert!(len > 100);
624        assert!(len < MAX_CERT_TLV_LEN);
625    }
626
627    /// Test building a NOC with CAT IDs
628    #[test]
629    fn test_build_noc_with_cat_ids() {
630        let crypto = test_only_crypto();
631
632        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
633
634        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
635        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
636        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
637
638        let noc_secret_key = unwrap!(crypto.generate_secret_key());
639
640        let noc_pubkey = noc_secret_key.pub_key().unwrap();
641        let mut noc_pubkey_canon = CanonPkcPublicKey::new();
642        noc_pubkey.write_canon(&mut noc_pubkey_canon).unwrap();
643
644        let serial_number = &[0x01];
645        let node_id = 0x0000000000000001u64;
646        let fabric_id = 0x0000000000000001u64;
647        let rcac_id = 0x1000u64;
648        let cat_ids = &[0x00011111u32, 0x00022222u32, 0x00033333u32]; // Valid CAT IDs (version != 0)
649        let not_before = 0u32;
650        let not_after = 0u32;
651
652        let subject = SubjectDN {
653            node_id: Some(node_id),
654            fabric_id: Some(fabric_id),
655            cat_ids,
656            ca_id: None,
657        };
658
659        let validity = Validity {
660            not_before,
661            not_after,
662        };
663
664        let issuer = IssuerDN {
665            ca_id: Some(rcac_id),
666            fabric_id: Some(fabric_id),
667            is_rcac: true,
668        };
669
670        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
671        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
672            &crypto,
673            CertType::Noc,
674            serial_number,
675            validity,
676            subject,
677            issuer,
678            noc_pubkey_canon.reference(),
679            Some(rcac_pubkey_canon.reference()),
680            &rcac_secret_key,
681        ));
682
683        assert!(len > 100);
684        assert!(len < MAX_CERT_TLV_LEN);
685    }
686
687    /// Test building a NOC signed by ICAC (3-cert chain)
688    #[test]
689    fn test_build_noc_signed_by_icac() {
690        let crypto = test_only_crypto();
691
692        let icac_secret_key = unwrap!(crypto.generate_secret_key());
693
694        let icac_pubkey = icac_secret_key.pub_key().unwrap();
695        let mut icac_pubkey_canon = CanonPkcPublicKey::new();
696        icac_pubkey.write_canon(&mut icac_pubkey_canon).unwrap();
697
698        let noc_secret_key = unwrap!(crypto.generate_secret_key());
699
700        let noc_pubkey = noc_secret_key.pub_key().unwrap();
701        let mut noc_pubkey_canon = CanonPkcPublicKey::new();
702        noc_pubkey.write_canon(&mut noc_pubkey_canon).unwrap();
703
704        let serial_number = &[0xFF];
705        let node_id = 0xDEADBEEFu64;
706        let fabric_id = 0x0000000000000001u64;
707        let icac_id = 0x2468u64;
708        let not_before = 0u32;
709        let not_after = 0u32;
710
711        let subject = SubjectDN {
712            node_id: Some(node_id),
713            fabric_id: Some(fabric_id),
714            cat_ids: &[], // No CAT IDs
715            ca_id: None,
716        };
717
718        let validity = Validity {
719            not_before,
720            not_after,
721        };
722
723        let issuer = IssuerDN {
724            ca_id: Some(icac_id),
725            fabric_id: Some(fabric_id),
726            is_rcac: false, // Issuer is ICAC, not RCAC
727        };
728
729        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
730        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
731            &crypto,
732            CertType::Noc,
733            serial_number,
734            validity,
735            subject,
736            issuer,
737            noc_pubkey_canon.reference(),
738            Some(icac_pubkey_canon.reference()),
739            &icac_secret_key,
740        ));
741
742        assert!(len > 100);
743        assert!(len < MAX_CERT_TLV_LEN);
744    }
745
746    /// Test complete certificate chain (RCAC -> ICAC -> NOC)
747    #[test]
748    fn test_build_complete_cert_chain() {
749        let crypto = test_only_crypto();
750
751        let fabric_id = 0x0000000000000001u64;
752        let rcac_id = 0x1111111111u64;
753        let icac_id = 0x2222u64;
754        let node_id = 0x3333333333333333u64;
755        let not_before = 0u32;
756        let not_after = 0u32;
757
758        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
759
760        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
761        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
762        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
763
764        let rcac_subject = SubjectDN {
765            node_id: None,
766            fabric_id: Some(fabric_id),
767            cat_ids: &[],
768            ca_id: Some(rcac_id),
769        };
770
771        let validity = Validity {
772            not_before,
773            not_after,
774        };
775
776        let mut rcac_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
777        let rcac_len = unwrap!(CertGenerator::new(&mut rcac_buf).generate(
778            &crypto,
779            CertType::Rcac,
780            &[0x01],
781            validity,
782            rcac_subject,
783            RCAC_ISSUER_DN_UNUSED,
784            rcac_pubkey_canon.reference(),
785            None,
786            &rcac_secret_key,
787        ));
788        assert!(rcac_len > 0);
789
790        let icac_secret_key = unwrap!(crypto.generate_secret_key());
791
792        let icac_pubkey = icac_secret_key.pub_key().unwrap();
793        let mut icac_pubkey_canon = CanonPkcPublicKey::new();
794        icac_pubkey.write_canon(&mut icac_pubkey_canon).unwrap();
795
796        let icac_subject = SubjectDN {
797            node_id: None,
798            fabric_id: Some(fabric_id),
799            cat_ids: &[],
800            ca_id: Some(icac_id),
801        };
802
803        let icac_issuer = IssuerDN {
804            ca_id: Some(rcac_id),
805            fabric_id: Some(fabric_id),
806            is_rcac: true,
807        };
808
809        let mut icac_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
810        let icac_len = unwrap!(CertGenerator::new(&mut icac_buf).generate(
811            &crypto,
812            CertType::Icac,
813            &[0x02],
814            validity,
815            icac_subject,
816            icac_issuer,
817            icac_pubkey_canon.reference(),
818            Some(rcac_pubkey_canon.reference()),
819            &rcac_secret_key,
820        ));
821        assert!(icac_len > 0);
822
823        let noc_secret_key = unwrap!(crypto.generate_secret_key());
824
825        let noc_pubkey = noc_secret_key.pub_key().unwrap();
826        let mut noc_pubkey_canon = CanonPkcPublicKey::new();
827        noc_pubkey.write_canon(&mut noc_pubkey_canon).unwrap();
828
829        let noc_subject = SubjectDN {
830            node_id: Some(node_id),
831            fabric_id: Some(fabric_id),
832            cat_ids: &[],
833            ca_id: None,
834        };
835
836        let noc_issuer = IssuerDN {
837            ca_id: Some(icac_id),
838            fabric_id: Some(fabric_id),
839            is_rcac: false, // Issuer is ICAC
840        };
841
842        let mut noc_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
843        let noc_len = unwrap!(CertGenerator::new(&mut noc_buf).generate(
844            &crypto,
845            CertType::Noc,
846            &[0x03],
847            validity,
848            noc_subject,
849            noc_issuer,
850            noc_pubkey_canon.reference(),
851            Some(icac_pubkey_canon.reference()),
852            &icac_secret_key,
853        ));
854        assert!(noc_len > 0);
855
856        // All certificates should be valid sizes
857        assert!(rcac_len > 100 && rcac_len < MAX_CERT_TLV_LEN);
858        assert!(icac_len > 100 && icac_len < MAX_CERT_TLV_LEN);
859        assert!(noc_len > 100 && noc_len < MAX_CERT_TLV_LEN);
860    }
861
862    /// Test certificate with validity period
863    #[test]
864    fn test_build_cert_with_validity() {
865        let crypto = test_only_crypto();
866
867        let rcac_secret_key = unwrap!(crypto.generate_secret_key());
868
869        let rcac_pubkey = rcac_secret_key.pub_key().unwrap();
870        let mut rcac_pubkey_canon = CanonPkcPublicKey::new();
871        rcac_pubkey.write_canon(&mut rcac_pubkey_canon).unwrap();
872
873        // Matter epoch: seconds since 2000-01-01 00:00:00 UTC
874        // Year 2021: approximately 662688000 seconds
875        let not_before = 662688000u32;
876        // 10 years later
877        let not_after = not_before + (10 * 365 * 24 * 60 * 60);
878
879        let subject = SubjectDN {
880            node_id: None,
881            fabric_id: Some(0x0000000000000001u64),
882            cat_ids: &[],
883            ca_id: Some(0x1234567890u64),
884        };
885
886        let validity = Validity {
887            not_before,
888            not_after,
889        };
890
891        let mut cert_buf = [0u8; MAX_CERT_TLV_AND_ASN1_LEN];
892        let len = unwrap!(CertGenerator::new(&mut cert_buf).generate(
893            &crypto,
894            CertType::Rcac,
895            &[0x01],
896            validity,
897            subject,
898            RCAC_ISSUER_DN_UNUSED,
899            rcac_pubkey_canon.reference(),
900            None,
901            &rcac_secret_key,
902        ));
903
904        assert!(len > 100);
905        assert!(len < MAX_CERT_TLV_LEN);
906    }
907
908    /// Test key identifier computation
909    #[test]
910    fn test_compute_key_id() {
911        let crypto = test_only_crypto();
912
913        let secret_key = unwrap!(crypto.generate_secret_key());
914
915        let mut pubkey = CanonPkcPublicKey::new();
916        unwrap!(secret_key.pub_key().unwrap().write_canon(&mut pubkey));
917
918        let key_id = unwrap!(compute_key_id(&crypto, pubkey.reference()));
919
920        // Key ID should be deterministic for the same public key
921        let key_id2 = unwrap!(compute_key_id(&crypto, pubkey.reference()));
922        assert_eq!(key_id, key_id2);
923    }
924
925    /// Test that different public keys produce different key IDs
926    #[test]
927    fn test_different_keys_different_ids() {
928        let crypto = test_only_crypto();
929
930        let secret_key1 = unwrap!(crypto.generate_secret_key());
931        let mut pubkey1 = CanonPkcPublicKey::new();
932        unwrap!(secret_key1.pub_key().unwrap().write_canon(&mut pubkey1));
933
934        let secret_key2 = unwrap!(crypto.generate_secret_key());
935        let mut pubkey2 = CanonPkcPublicKey::new();
936        unwrap!(secret_key2.pub_key().unwrap().write_canon(&mut pubkey2));
937
938        let key_id1 = unwrap!(compute_key_id(&crypto, pubkey1.reference()));
939        let key_id2 = unwrap!(compute_key_id(&crypto, pubkey2.reference()));
940
941        // Different keys should produce different IDs
942        assert_ne!(key_id1, key_id2);
943    }
944}