@@ -43,13 +43,6 @@ enum CertType {
4343 Noc ,
4444}
4545
46- /// Internal shared certificate builder implementation.
47- ///
48- /// Contains all the common logic for building Noc, Icac, and Rcac certificates.
49- struct CertBuilderCore < ' a > {
50- buf : & ' a mut [ u8 ] ,
51- }
52-
5346pub struct IssuerDN {
5447 pub ( crate ) ca_id : Option < u64 > ,
5548 pub ( crate ) fabric_id : Option < u64 > ,
@@ -64,10 +57,40 @@ pub struct SubjectDN<'a> {
6457 pub ( crate ) ca_id : Option < u64 > ,
6558}
6659
60+ /// Validity period for certificates, represented as seconds since the Matter epoch (2000-01-01T00:00:00Z).
6761#[ derive( Clone , Copy ) ]
6862pub struct Validity {
69- pub ( crate ) not_before : u32 ,
70- pub ( crate ) not_after : u32 ,
63+ /// NotBefore time (seconds since Matter epoch)
64+ ///
65+ /// This must not be 0 (the Matter epoch start) to avoid collision with CHIP's epoch=0 sentinel in ASN.1 time encoding.
66+ pub not_before : u32 ,
67+ /// NotAfter time (seconds since Matter epoch, 0 = no expiry)
68+ pub not_after : u32 ,
69+ }
70+
71+ // NotBefore MUST NOT be 0 (Matter epoch start, 2000-01-01).
72+ // CHIP's ChipEpochToASN1Time treats epoch=0 as the "no
73+ // well-defined expiration date" sentinel and re-emits it as
74+ // GeneralizedTime "99991231235959Z" regardless of which field
75+ // it appears in (see CHIPCert.cpp:1076-1106 and the
76+ // explanatory comment about CHIP epoch 0 NotBefore producing
77+ // an invalid TBS signature on round-trip).
78+ //
79+ // We sign over UTCTime "000101000000Z" (Matter epoch); CHIP
80+ // would reconstruct GeneralizedTime "99991231235959Z" and the
81+ // hash would mismatch. Using 1 second past the Matter epoch
82+ // avoids the sentinel collision while keeping the cert
83+ // effectively unbounded on the lower end.
84+ pub const VALID_FOREVER : Validity = Validity {
85+ not_before : 1 , // 2000-01-01 00:00:01 — past CHIP's epoch=0 sentinel
86+ not_after : 0 , // no expiry (NotAfter sentinel is legitimate)
87+ } ;
88+
89+ /// Internal shared certificate builder implementation.
90+ ///
91+ /// Contains all the common logic for building Noc, Icac, and Rcac certificates.
92+ struct CertBuilderCore < ' a > {
93+ buf : & ' a mut [ u8 ] ,
7194}
7295
7396impl < ' a > CertBuilderCore < ' a > {
@@ -263,20 +286,21 @@ impl<'a> CertBuilderCore<'a> {
263286 subject_key_id : & KeyId ,
264287 authority_key_id : & KeyId ,
265288 ) -> Result < ( ) , Error > {
266- // 1. Basic Constraints
289+ // 1. Basic Constraints — per Matter Spec:
290+ // RCAC: cA = TRUE, pathLenConstraint shall NOT be present
291+ // ICAC: cA = TRUE, pathLenConstraint = 0
292+ // NOC: cA = FALSE, pathLenConstraint shall NOT be present
267293 tw. start_struct ( & TLVTag :: Context ( 1 ) ) ?;
268294 match cert_type {
269295 CertType :: Rcac => {
270- tw. bool ( & TLVTag :: Context ( 1 ) , true ) ?; // is_ca = true
271- tw. u8 ( & TLVTag :: Context ( 2 ) , 1 ) ?; // path_len = 1
296+ tw. bool ( & TLVTag :: Context ( 1 ) , true ) ?;
272297 }
273298 CertType :: Icac => {
274- tw. bool ( & TLVTag :: Context ( 1 ) , true ) ?; // is_ca = true
299+ tw. bool ( & TLVTag :: Context ( 1 ) , true ) ?;
275300 tw. u8 ( & TLVTag :: Context ( 2 ) , 0 ) ?; // path_len = 0
276301 }
277302 CertType :: Noc => {
278- tw. bool ( & TLVTag :: Context ( 1 ) , false ) ?; // is_ca = false
279- // No path_len for end entity
303+ tw. bool ( & TLVTag :: Context ( 1 ) , false ) ?;
280304 }
281305 }
282306 tw. end_container ( ) ?;
@@ -581,12 +605,13 @@ impl<'a> RcacBuilder<'a> {
581605
582606#[ cfg( test) ]
583607mod tests {
584- use super :: * ;
585608 use crate :: {
586609 cert:: { MAX_CERT_TLV_AND_ASN1_LEN , MAX_CERT_TLV_LEN } ,
587610 crypto:: { test_only_crypto, CanonPkcPublicKey , PublicKey , SigningSecretKey } ,
588611 } ;
589612
613+ use super :: * ;
614+
590615 #[ test]
591616 fn test_validate_cat_id_valid ( ) {
592617 // Version = 1, identifier = 0x1234
@@ -659,6 +684,41 @@ mod tests {
659684 assert ! ( len < MAX_CERT_TLV_LEN ) ;
660685 }
661686
687+ #[ test]
688+ fn test_rcac_self_verify ( ) {
689+ let crypto = test_only_crypto ( ) ;
690+ let rcac_secret_key = unwrap ! ( crypto. generate_secret_key( ) ) ;
691+ let rcac_pubkey = rcac_secret_key. pub_key ( ) . unwrap ( ) ;
692+
693+ let subject = SubjectDN {
694+ node_id : None ,
695+ fabric_id : Some ( 0x0000_0000_0000_0001 ) ,
696+ cat_ids : & [ ] ,
697+ ca_id : Some ( 0x1122_3344_5566_7788 ) ,
698+ } ;
699+ let mut cert_buf = [ 0u8 ; MAX_CERT_TLV_AND_ASN1_LEN ] ;
700+ let mut builder = RcacBuilder :: new ( & mut cert_buf) ;
701+ let len = unwrap ! ( builder. build(
702+ & crypto,
703+ subject,
704+ VALID_FOREVER ,
705+ & rcac_pubkey,
706+ & rcac_secret_key,
707+ & [ 0x01 ] ,
708+ ) ) ;
709+
710+ // Re-parse the just-built RCAC and self-verify.
711+ let cert = CertRef :: new ( crate :: tlv:: TLVElement :: new ( & cert_buf[ ..len] ) ) ;
712+ let mut scratch = [ 0u8 ; MAX_CERT_TLV_AND_ASN1_LEN ] ;
713+ let res = cert
714+ . verify_chain_start ( & crypto, VALID_FOREVER . not_before as _ )
715+ . finalise ( & mut scratch) ;
716+ assert ! (
717+ res. is_ok( ) ,
718+ "RCAC built by RcacBuilder failed self-verification: {res:?}"
719+ ) ;
720+ }
721+
662722 /// Test building an ICAC signed by RCAC
663723 #[ test]
664724 fn test_build_icac ( ) {
@@ -676,8 +736,6 @@ mod tests {
676736 let icac_id = 0x1234u64 ;
677737 let rcac_id = 0x5678u64 ;
678738 let fabric_id = 0x0000000000000001u64 ;
679- let not_before = 0u32 ;
680- let not_after = 0u32 ;
681739
682740 let subject = SubjectDN {
683741 node_id : None ,
@@ -686,11 +744,6 @@ mod tests {
686744 ca_id : Some ( icac_id) ,
687745 } ;
688746
689- let validity = Validity {
690- not_before,
691- not_after,
692- } ;
693-
694747 let issuer = IssuerDN {
695748 ca_id : Some ( rcac_id) ,
696749 fabric_id : Some ( fabric_id) ,
@@ -703,7 +756,7 @@ mod tests {
703756 let len = unwrap ! ( builder. build(
704757 & crypto,
705758 subject,
706- validity ,
759+ VALID_FOREVER ,
707760 & icac_pubkey,
708761 & rcac_pubkey,
709762 & rcac_secret_key,
0 commit comments