Skip to content

Commit b23a6ab

Browse files
authored
Do not use NotBefore=0 in certs validity periods (#458)
This is #454 except with all Gemini code review comments addressed (and future comments to be addressed too).
1 parent 6c53af6 commit b23a6ab

4 files changed

Lines changed: 148 additions & 90 deletions

File tree

rs-matter/src/cert/builder.rs

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
5346
pub 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)]
6862
pub 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

7396
impl<'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)]
583607
mod 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

Comments
 (0)