diff --git a/rs-matter/src/cert.rs b/rs-matter/src/cert.rs index c0e4a2dc..f0c9b75d 100644 --- a/rs-matter/src/cert.rs +++ b/rs-matter/src/cert.rs @@ -21,6 +21,7 @@ use num::FromPrimitive; use num_derive::FromPrimitive; use crate::crypto::{Crypto, PublicKey}; +use crate::dm::clusters::time_sync::UtcTime; use crate::error::{Error, ErrorCode}; use crate::tlv::{FromTLV, Octets, TLVArray, TLVElement, TLVList, ToTLV}; use crate::utils::epoch::MATTER_CERT_DOESNT_EXPIRE; @@ -714,9 +715,9 @@ impl<'a> CertRef<'a> { pub fn verify_chain_start( &'a self, crypto: C, - lkg_utc_secs: u64, + utc_time: UtcTime, ) -> CertVerifier<'a, C> { - CertVerifier::new(self, crypto, lkg_utc_secs) + CertVerifier::new(self, crypto, utc_time) } fn encode(&self, w: &mut dyn CertConsumer) -> Result<(), Error> { @@ -792,20 +793,15 @@ impl fmt::Display for CertRef<'_> { pub struct CertVerifier<'a, C> { cert: &'a CertRef<'a>, crypto: C, - /// Last-Known-Good UTC Time as Matter-epoch seconds, snapshot at - /// the start of chain verification. Per Matter Core spec §3.5.6 - /// this is the time value used against cert `NotBefore`/`NotAfter` - /// when no live trusted real-time-clock is available — and the - /// stored LKG (§3.5.6.1) is exactly that. - lkg_utc_secs: u64, + utc_time: UtcTime, } impl<'a, C: Crypto> CertVerifier<'a, C> { - pub fn new(cert: &'a CertRef<'a>, crypto: C, lkg_utc_secs: u64) -> Self { + pub fn new(cert: &'a CertRef<'a>, crypto: C, utc_time: UtcTime) -> Self { Self { cert, crypto, - lkg_utc_secs, + utc_time, } } @@ -841,13 +837,23 @@ impl<'a, C: Crypto> CertVerifier<'a, C> { // §6.5.5. let not_before = self.cert.not_before()? as u64; let not_after = self.cert.not_after()?; - if self.lkg_utc_secs < not_before - || (not_after != 0 && self.lkg_utc_secs > not_after as u64) - { + + if not_after > 0 && self.utc_time.any_secs() > not_after as u64 { Err(ErrorCode::InvalidTime)?; } - Ok(CertVerifier::new(parent, self.crypto, self.lkg_utc_secs)) + if let Some(secs) = self.utc_time.reliable_secs() { + // Only check the NotBefore if we have a reliable UTC time; + // if we have only the Last-Known-Good time, then we may be + // in a gap between the LKG time and the real current time, + // and we don't want to reject certs that were generated _after_ + // our clock lost sync + if secs < not_before { + Err(ErrorCode::InvalidTime)?; + } + } + + Ok(CertVerifier::new(parent, self.crypto, self.utc_time)) } pub fn finalise(self, buf: &mut [u8]) -> Result<(), Error> { @@ -888,6 +894,7 @@ pub trait CertConsumer { #[cfg(test)] mod tests { use crate::crypto::test_only_crypto; + use crate::dm::clusters::time_sync::UtcTime; use crate::error::ErrorCode; use crate::tlv::{FromTLV, TLVElement, TagType, ToTLV}; use crate::utils::storage::WriteBuf; @@ -924,8 +931,8 @@ mod tests { let noc = CertRef::new(TLVElement::new(NOC1_SUCCESS)); let icac = CertRef::new(TLVElement::new(ICAC1_SUCCESS)); let rca = CertRef::new(TLVElement::new(RCA1_SUCCESS)); - let lkg = unwrap!(noc.not_before()) as u64; - let a = noc.verify_chain_start(test_only_crypto(), lkg); + let time = UtcTime::Reliable(unwrap!(noc.not_before()) as u64 * 1_000_000); + let a = noc.verify_chain_start(test_only_crypto(), time); unwrap!( unwrap!(unwrap!(a.add_cert(&icac, &mut buf)).add_cert(&rca, &mut buf)) .finalise(&mut buf) @@ -939,8 +946,8 @@ mod tests { let mut buf = [0; 1000]; let noc = CertRef::new(TLVElement::new(NOC1_SUCCESS)); let icac = CertRef::new(TLVElement::new(ICAC1_SUCCESS)); - let lkg = unwrap!(noc.not_before()) as u64; - let a = noc.verify_chain_start(test_only_crypto(), lkg); + let time = UtcTime::Reliable(unwrap!(noc.not_before()) as u64 * 1_000_000); + let a = noc.verify_chain_start(test_only_crypto(), time); assert_eq!( Err(ErrorCode::InvalidAuthKey), unwrap!(a.add_cert(&icac, &mut buf)) @@ -954,7 +961,7 @@ mod tests { let mut buf = [0; 1000]; let noc = CertRef::new(TLVElement::new(NOC1_AUTH_KEY_FAIL)); let icac = CertRef::new(TLVElement::new(ICAC1_SUCCESS)); - let a = noc.verify_chain_start(test_only_crypto(), 0); + let a = noc.verify_chain_start(test_only_crypto(), UtcTime::Reliable(0)); assert_eq!( Err(ErrorCode::InvalidAuthKey), a.add_cert(&icac, &mut buf) @@ -969,8 +976,8 @@ mod tests { let noc = CertRef::new(TLVElement::new(NOC_NOT_AFTER_ZERO)); let rca = CertRef::new(TLVElement::new(RCA_FOR_NOC_NOT_AFTER_ZERO)); - let lkg = unwrap!(noc.not_before()) as u64; - let v = noc.verify_chain_start(test_only_crypto(), lkg); + let time = UtcTime::Reliable(unwrap!(noc.not_before()) as u64 * 1_000_000); + let v = noc.verify_chain_start(test_only_crypto(), time); let v = unwrap!(v.add_cert(&rca, &mut buf)); unwrap!(v.finalise(&mut buf)); } @@ -980,7 +987,7 @@ mod tests { let mut buf = [0; 1000]; let noc = CertRef::new(TLVElement::new(NOC1_CORRUPT_CERT)); let icac = CertRef::new(TLVElement::new(ICAC1_SUCCESS)); - let a = noc.verify_chain_start(test_only_crypto(), 0); + let a = noc.verify_chain_start(test_only_crypto(), UtcTime::Reliable(0)); assert_eq!( Err(ErrorCode::InvalidSignature), a.add_cert(&icac, &mut buf) diff --git a/rs-matter/src/cert/builder.rs b/rs-matter/src/cert/builder.rs index c7c63e88..2b7a38ad 100644 --- a/rs-matter/src/cert/builder.rs +++ b/rs-matter/src/cert/builder.rs @@ -608,6 +608,7 @@ mod tests { use crate::{ cert::{MAX_CERT_TLV_AND_ASN1_LEN, MAX_CERT_TLV_LEN}, crypto::{test_only_crypto, CanonPkcPublicKey, PublicKey, SigningSecretKey}, + dm::clusters::time_sync::UtcTime, }; use super::*; @@ -711,7 +712,10 @@ mod tests { let cert = CertRef::new(crate::tlv::TLVElement::new(&cert_buf[..len])); let mut scratch = [0u8; MAX_CERT_TLV_AND_ASN1_LEN]; let res = cert - .verify_chain_start(&crypto, VALID_FOREVER.not_before as _) + .verify_chain_start( + &crypto, + UtcTime::Reliable(VALID_FOREVER.not_before as u64 * 1_000_000), + ) .finalise(&mut scratch); assert!( res.is_ok(), diff --git a/rs-matter/src/dm.rs b/rs-matter/src/dm.rs index d57f2645..cb4e62c0 100644 --- a/rs-matter/src/dm.rs +++ b/rs-matter/src/dm.rs @@ -36,7 +36,9 @@ use crate::im::{ use crate::persist::KvBlobStoreAccess; use crate::respond::ExchangeHandler; use crate::tlv::{get_root_node_struct, FromTLV, Nullable, TLVElement, TLVTag, TLVWrite, ToTLV}; -use crate::transport::exchange::{Exchange, MAX_EXCHANGE_RX_BUF_SIZE, MAX_EXCHANGE_TX_BUF_SIZE}; +use crate::transport::exchange::{ + Exchange, ExchangeId, MAX_EXCHANGE_RX_BUF_SIZE, MAX_EXCHANGE_TX_BUF_SIZE, +}; use crate::utils::select::Coalesce; use crate::utils::storage::pooled::BufferAccess; use crate::utils::storage::WriteBuf; @@ -206,22 +208,30 @@ where loop { Timer::after_secs(CHECK_INTERVAL_SECS).await; - self.check_timeouts()?; + self.check_timeouts(None)?; } } - fn check_timeouts(&self) -> Result<(), Error> { + fn check_timeouts(&self, exch_id: Option) -> Result<(), Error> { let mut notify_mdns = || self.matter.notify_mdns_changed(); let mut notify_change = |endpt_id, clust_id| self.notify_cluster_changed(endpt_id, clust_id); self.matter.with_state(|state| { + let expire_sess_id = exch_id.and_then(|exch_id| { + state + .sessions + .get(exch_id.session_id()) + .map(|sess| sess.id()) + }); + // Disarm the failsafe on timeout state.failsafe.check_failsafe_timeout( &mut state.fabrics, &mut state.sessions, &self.networks, &self.kv, + expire_sess_id, &mut notify_mdns, &mut notify_change, )?; @@ -265,7 +275,7 @@ where None }; - self.check_timeouts()?; + self.check_timeouts(Some(exchange.id()))?; // TODO: Handle the cases where we receive a timeout request // before read and subscribe. This is probably not allowed. diff --git a/rs-matter/src/dm/clusters/gen_diag.rs b/rs-matter/src/dm/clusters/gen_diag.rs index 0920ad9d..4bb4eab9 100644 --- a/rs-matter/src/dm/clusters/gen_diag.rs +++ b/rs-matter/src/dm/clusters/gen_diag.rs @@ -23,6 +23,7 @@ use core::net::{Ipv4Addr, Ipv6Addr}; use crate::dm::{ArrayAttributeRead, Cluster, Dataver, InvokeContext, ReadContext}; use crate::error::{Error, ErrorCode}; use crate::tlv::{Nullable, Octets, TLVBuilder, TLVBuilderParent}; +use crate::utils::epoch::MATTER_EPOCH_SECS; use crate::utils::sync::DynBase; use crate::with; @@ -297,13 +298,13 @@ impl ClusterHandler for GenDiagHandler<'_> { // (we do) or when its `UTCTime` attribute is null. We read the // Matter-wide current UTC time directly from `Matter::utc_time` // and convert (Matter-epoch microseconds) → POSIX-epoch ms. - let posix_time_ms = match ctx.matter().with_state(|state| state.rtc.utc_time()) { - Some(utc_us) => Nullable::some( - (utc_us / 1000) - .saturating_add(crate::utils::epoch::MATTER_EPOCH_SECS.saturating_mul(1000)), - ), - None => Nullable::none(), - }; + let posix_time_ms = Nullable::new( + ctx.matter() + .with_state(|state| state.rtc.utc_time()) + .reliable() + .map(|us| us / 1000) + .map(|ms| ms.saturating_add(MATTER_EPOCH_SECS.saturating_mul(1000))), + ); response .system_time_ms(system_time_ms)? diff --git a/rs-matter/src/dm/clusters/noc.rs b/rs-matter/src/dm/clusters/noc.rs index 1ecbd59e..8edf655f 100644 --- a/rs-matter/src/dm/clusters/noc.rs +++ b/rs-matter/src/dm/clusters/noc.rs @@ -321,7 +321,7 @@ impl ClusterHandler for NocHandler { // `SetUTCTime` yet, this is the firmware build timestamp. // Read directly from the already-held `state` — re-entering // `Matter::with_state` here would deadlock the inner mutex. - let epoch = (state.rtc.last_known_utc_time() / 1_000_000) as u32; + let epoch = state.rtc.utc_time().any_secs() as u32; let mut signature = MaybeUninit::uninit(); let signature = signature.init_with(CanonPkcSignature::init()); // TODO MEDIUM BUFFER @@ -501,12 +501,11 @@ impl ClusterHandler for NocHandler { let status = NodeOperationalCertStatusEnum::map(GenCommHandler::with_armed_failsafe( &ctx, |state, mut notify_mdns| { - let lkg_utc_secs = state.rtc.utc_time_best_effort() / 1_000_000; let sess = ctx.exchange().id().session(&mut state.sessions); let fabric = state.failsafe.add_noc( ctx.crypto(), - lkg_utc_secs, + state.rtc.utc_time(), &mut state.fabrics, sess.get_session_mode(), request.admin_vendor_id()?, @@ -592,12 +591,11 @@ impl ClusterHandler for NocHandler { let status = NodeOperationalCertStatusEnum::map(GenCommHandler::with_armed_failsafe( &ctx, |state, notify_mdns| { - let lkg_utc_secs = state.rtc.utc_time_best_effort() / 1_000_000; let sess = ctx.exchange().id().session(&mut state.sessions); state.failsafe.update_noc( ctx.crypto(), - lkg_utc_secs, + state.rtc.utc_time(), &mut state.fabrics, sess.get_session_mode(), icac, @@ -775,12 +773,11 @@ impl ClusterHandler for NocHandler { let mut buf = [0u8; crate::cert::MAX_CERT_ASN1_LEN]; GenCommHandler::with_armed_failsafe(&ctx, |state, _| { - let lkg_utc_secs = state.rtc.utc_time_best_effort() / 1_000_000; let sess = ctx.exchange().id().session(&mut state.sessions); state.failsafe.add_trusted_root_cert( ctx.crypto(), - lkg_utc_secs, + state.rtc.utc_time(), sess.get_session_mode(), request.root_ca_certificate()?.0, &mut buf, diff --git a/rs-matter/src/dm/clusters/time_sync.rs b/rs-matter/src/dm/clusters/time_sync.rs index 5db21fd2..135baa4c 100644 --- a/rs-matter/src/dm/clusters/time_sync.rs +++ b/rs-matter/src/dm/clusters/time_sync.rs @@ -74,6 +74,54 @@ pub use crate::dm::clusters::decl::time_synchronization::*; pub mod client; +/// An enum describing the current UTC timestamp the real-time clock is aware of. +/// +/// The timestamp is expressed as Matter-epoch microseconds. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UtcTime { + /// The RTC is actively tracking the current time, anchored at the given Matter-epoch microseconds value. + Reliable(u64), + /// The RTC is not currently tracking the current time, but the given Matter-epoch microseconds value is + /// the last known good UTC time persisted on the device. + /// + /// Do note that a "last known" time is always available, as the firmware build timestamp is used as the + /// initial value on a freshly-flashed device. + LastKnown(u64), +} + +impl UtcTime { + /// Return the current UTC time if available, or `None` if no reliable time is currently tracked. + pub const fn reliable(&self) -> Option { + match self { + UtcTime::Reliable(utc) => Some(*utc), + UtcTime::LastKnown(_) => None, + } + } + + /// Return the current UTC time if available, or the persisted LKG UTC otherwise. + pub const fn any(&self) -> u64 { + match self { + UtcTime::Reliable(utc) | UtcTime::LastKnown(utc) => *utc, + } + } + + /// Return the current UTC time in seconds if available, or `None` if no reliable time is currently tracked. + pub const fn reliable_secs(&self) -> Option { + match self { + UtcTime::Reliable(utc) => Some(*utc / 1_000_000), + UtcTime::LastKnown(_) => None, + } + } + + /// Return the current UTC time in seconds if available, or the persisted LKG UTC otherwise. + pub const fn any_secs(&self) -> u64 { + match self { + UtcTime::Reliable(utc) | UtcTime::LastKnown(utc) => *utc / 1_000_000, + } + } +} + /// Last-Known-Good UTC Time tracking for the device (Matter Core spec /// §3.5.6.1). /// @@ -256,42 +304,18 @@ impl Rtc { Ok(()) } - /// Return the persisted Last-Known-Good UTC Time as Matter-epoch - /// microseconds (Matter Core spec §3.5.6.1). - /// - /// This value is always defined — seeded from - /// [`crate::utils::epoch::FIRMWARE_BUILD_MATTER_US`] on first - /// boot, persisted across reboots, and updated on every - /// [`Self::set_utc_time`] call. It is the time source consulted by - /// rs-matter's cert path validation, the NOC attestation - /// timestamp, and the TimeSync cluster's mandatory members. - pub fn last_known_utc_time(&self) -> u64 { - self.utc_us - } - - /// Return the current UTC time as Matter-epoch microseconds — - /// `Some(utc)` if [`Self::set_utc_time`] has been called since - /// boot (advancing monotonically via [`embassy_time::Instant`] - /// from the captured anchor), or `None` if no current-time - /// tracking is currently active. - /// - /// Note that even when this returns `None`, - /// [`Self::last_known_utc_time`] still carries the persisted - /// LKG value usable as a cert-validity lower bound per spec - /// §3.5.6. - pub fn utc_time(&self) -> Option { - let anchor = self.anchor?; - let elapsed_us = embassy_time::Instant::now() - .checked_duration_since(anchor) - .map(|d| d.as_micros()) - .unwrap_or(0); - - Some(self.utc_us.saturating_add(elapsed_us)) - } + /// Return the current UTC time if available, or the persisted Last-Known-Good UTC Time otherwise. + pub fn utc_time(&self) -> UtcTime { + if let Some(anchor) = self.anchor { + let elapsed_us = embassy_time::Instant::now() + .checked_duration_since(anchor) + .map(|d| d.as_micros()) + .unwrap_or(0); - /// Return the current UTC time if available, or the persisted LKG UTC otherwise. - pub fn utc_time_best_effort(&self) -> u64 { - self.utc_time().unwrap_or(self.utc_us) + UtcTime::Reliable(self.utc_us.saturating_add(elapsed_us)) + } else { + UtcTime::LastKnown(self.utc_us) + } } /// Return the Granularity reported on the wire for the TimeSync @@ -871,12 +895,11 @@ impl ClusterHandler for TimeSyncHandler<'_> { // from the user-supplied `TimeSync` provider). fn utc_time(&self, ctx: impl ReadContext) -> Result, Error> { - Ok( - match ctx.matter().with_state(|state| state.rtc.utc_time()) { - Some(us) => Nullable::some(us), - None => Nullable::none(), - }, - ) + Ok(Nullable::new( + ctx.matter() + .with_state(|state| state.rtc.utc_time()) + .reliable(), + )) } fn granularity(&self, ctx: impl ReadContext) -> Result { diff --git a/rs-matter/src/failsafe.rs b/rs-matter/src/failsafe.rs index 717e468f..fc1d9690 100644 --- a/rs-matter/src/failsafe.rs +++ b/rs-matter/src/failsafe.rs @@ -25,6 +25,7 @@ use crate::crypto::{ SigningSecretKey, PKC_SECRET_KEY_ZEROED, }; use crate::dm::clusters::net_comm::NetworksAccess; +use crate::dm::clusters::time_sync::UtcTime; use crate::dm::endpoints::ROOT_ENDPOINT_ID; use crate::dm::{ClusterId, EndptId}; use crate::error::{Error, ErrorCode}; @@ -119,12 +120,14 @@ impl FailSafe { /// /// This should be called periodically to ensure that the fail-safe state is updated in a timely manner. /// Ideally, it should also be called at the beginning of any API that requires the fail-safe to be armed to ensure that the state is up to date. + #[allow(clippy::too_many_arguments)] pub fn check_failsafe_timeout( &mut self, fabrics: &mut Fabrics, sessions: &mut crate::transport::session::Sessions, networks: N, kv: S, + expire_sess_id: Option, mdns_notif: impl FnMut(), notify_change: impl FnMut(EndptId, ClusterId), ) -> Result @@ -145,7 +148,7 @@ impl FailSafe { self.expire( fabrics, sessions, - None, + expire_sess_id, networks, kv, mdns_notif, @@ -402,7 +405,7 @@ impl FailSafe { pub fn add_trusted_root_cert( &mut self, crypto: C, - lkg_utc_secs: u64, + time: UtcTime, session_mode: &SessionMode, root_ca: &[u8], buf: &mut [u8], @@ -423,7 +426,7 @@ impl FailSafe { { let root_ref = CertRef::new(TLVElement::new(root_ca)); root_ref - .verify_chain_start(&crypto, lkg_utc_secs) + .verify_chain_start(&crypto, time) .finalise(buf) .map_err(|_| ErrorCode::InvalidCommand)?; } @@ -486,7 +489,7 @@ impl FailSafe { pub fn update_noc<'a, C: Crypto>( &mut self, crypto: C, - lkg_utc_secs: u64, + time: UtcTime, fabrics: &'a mut Fabrics, session_mode: &SessionMode, icac: Option<&[u8]>, @@ -527,15 +530,8 @@ impl FailSafe { // signature verification (or that doesn't chain back to the // staged root) is reported as `kInvalidNOC` cluster status per // Matter Core spec section 11.18.6.7 (`UpdateNOC`). - Self::validate_certs( - &crypto, - lkg_utc_secs, - &noc_ref, - icac_ref.as_ref(), - &root_ref, - buf, - ) - .map_err(|_| ErrorCode::NocInvalidNoc)?; + Self::validate_certs(&crypto, time, &noc_ref, icac_ref.as_ref(), &root_ref, buf) + .map_err(|_| ErrorCode::NocInvalidNoc)?; // The NOC's public key must match the public key derived from // the most recent `CSRRequest(isForUpdateNOC=true)` (Matter @@ -590,7 +586,7 @@ impl FailSafe { pub fn add_noc<'a, C: Crypto>( &mut self, crypto: C, - lkg_utc_secs: u64, + time: UtcTime, fabrics: &'a mut Fabrics, session_mode: &SessionMode, vendor_id: u16, @@ -625,15 +621,8 @@ impl FailSafe { // signature verification (or that doesn't chain back to the // staged root) is reported as `kInvalidNOC` cluster status per // Matter Core spec section 11.18.6.6 (`AddNOC`). - Self::validate_certs( - &crypto, - lkg_utc_secs, - &noc_ref, - icac_ref.as_ref(), - &root_ref, - buf, - ) - .map_err(|_| ErrorCode::NocInvalidNoc)?; + Self::validate_certs(&crypto, time, &noc_ref, icac_ref.as_ref(), &root_ref, buf) + .map_err(|_| ErrorCode::NocInvalidNoc)?; // The NOC's public key must match the public key derived from // the most recent `CSRRequest` (Matter Core spec section @@ -717,13 +706,13 @@ impl FailSafe { #[allow(clippy::too_many_arguments)] fn validate_certs( crypto: C, - lkg_utc_secs: u64, + time: UtcTime, noc: &CertRef, icac: Option<&CertRef>, root: &CertRef, buf: &mut [u8], ) -> Result<(), Error> { - let mut verifier = noc.verify_chain_start(crypto, lkg_utc_secs); + let mut verifier = noc.verify_chain_start(crypto, time); if let Some(icac) = icac { // If ICAC is present handle it. Reject the case where the diff --git a/rs-matter/src/sc.rs b/rs-matter/src/sc.rs index 2cb26962..aa51ebfc 100644 --- a/rs-matter/src/sc.rs +++ b/rs-matter/src/sc.rs @@ -97,8 +97,11 @@ pub enum SCStatusCodes { impl SCStatusCodes { pub fn reliable(&self) -> bool { - // CloseSession and Busy are sent without the R flag raised - !matches!(self, SCStatusCodes::CloseSession | SCStatusCodes::Busy) + // CloseSession, Busy and SessionNotFound are sent without the R flag raised + !matches!( + self, + SCStatusCodes::CloseSession | SCStatusCodes::Busy | SCStatusCodes::SessionNotFound + ) } pub fn as_report<'a>(&self, payload: &'a [u8]) -> StatusReport<'a> { diff --git a/rs-matter/src/sc/case/casep.rs b/rs-matter/src/sc/case/casep.rs index ee1d0ed5..5b10836f 100644 --- a/rs-matter/src/sc/case/casep.rs +++ b/rs-matter/src/sc/case/casep.rs @@ -25,6 +25,7 @@ use crate::crypto::{ AEAD_CANON_KEY_LEN, AEAD_KEY_ZEROED, AEAD_TAG_LEN, AEAD_TAG_ZEROED, HASH_LEN, HASH_ZEROED, PKC_CANON_PUBLIC_KEY_LEN, PKC_PUBLIC_KEY_ZEROED, PKC_SHARED_SECRET_ZEROED, }; +use crate::dm::clusters::time_sync::UtcTime; use crate::error::{Error, ErrorCode}; use crate::fabric::Fabric; use crate::tlv::{Optional, TLVElement, TLVTag, TLVWrite}; @@ -424,6 +425,8 @@ impl<'a, C: Crypto + 'a> CaseP<'a, C> { /// Validate the certificate chain /// /// # Arguments + /// - `crypto` - The crypto provider + /// - `time` - The current UTC time for validating certificate validity periods /// - `fabric` - The local fabric /// - `noc` - The Node Operational Certificate /// - `icac` - The Intermediate Certificate Authority Certificate (optional) @@ -435,17 +438,13 @@ impl<'a, C: Crypto + 'a> CaseP<'a, C> { pub fn validate_certs( &self, crypto: &C, - lkg_utc_secs: u64, + time: UtcTime, fabric: &Fabric, noc: &CertRef, icac: Option<&CertRef>, tmp_buf: &mut [u8], ) -> Result<(), Error> { - // Validate the NOC chain (signatures + `NotBefore` / `NotAfter` - // against the device's Last-Known-Good UTC Time, per Matter Core - // spec §3.5.6). Callers snapshot `lkg_utc_secs` from - // `Matter::last_known_utc_time` and divide by 1_000_000. - let mut verifier = noc.verify_chain_start(crypto, lkg_utc_secs); + let mut verifier = noc.verify_chain_start(crypto, time); if fabric.fabric_id() != noc.get_fabric_id()? { Err(ErrorCode::Invalid)?; diff --git a/rs-matter/src/sc/case/initiator.rs b/rs-matter/src/sc/case/initiator.rs index 2c289c04..34e0d106 100644 --- a/rs-matter/src/sc/case/initiator.rs +++ b/rs-matter/src/sc/case/initiator.rs @@ -253,12 +253,11 @@ impl<'a, C: Crypto + 'a> CaseInitiator<'a, C> { .map(|icac| CertRef::new(TLVElement::new(icac.0))); let mut tmp_buf = alloc!([0u8; CASE_LARGE_BUF_SIZE]); // TODO LARGE BUFFER - let lkg_utc_secs = state.rtc.utc_time_best_effort() / 1_000_000; initiator .casep .validate_certs( crypto, - lkg_utc_secs, + state.rtc.utc_time(), fabric, &responder_noc, icac_cert.as_ref(), diff --git a/rs-matter/src/sc/case/responder.rs b/rs-matter/src/sc/case/responder.rs index 833ed249..7aec70f5 100644 --- a/rs-matter/src/sc/case/responder.rs +++ b/rs-matter/src/sc/case/responder.rs @@ -247,7 +247,6 @@ impl<'a, C: Crypto> CaseResponder<'a, C> { check_opcode(exchange, OpCode::CASESigma3)?; let status = exchange.with_state(|state| { - let lkg_utc_secs = state.rtc.utc_time_best_effort() / 1_000_000; let sess = exchange.id().session(&mut state.sessions); let fabric = NonZeroU8::new(self.casep.local_fabric_idx()) @@ -317,7 +316,7 @@ impl<'a, C: Crypto> CaseResponder<'a, C> { let buf = &mut buf[..]; if let Err(e) = self.casep.validate_certs( self.crypto, - lkg_utc_secs, + state.rtc.utc_time(), fabric, &initiator_noc, initiator_icac.as_ref(), diff --git a/rs-matter/src/transport/session.rs b/rs-matter/src/transport/session.rs index 7b55ecdd..61c5a10f 100644 --- a/rs-matter/src/transport/session.rs +++ b/rs-matter/src/transport/session.rs @@ -337,6 +337,14 @@ impl Session { Err(ErrorCode::NoExchange)?; } + if self.expired { + // Per Matter Core spec section 4.7.1.3, an expired session must not + // accept new inbound messages. Skipping expired sessions here lets the + // caller surface a `SessionNotFound` to the peer rather than running + // the request through ACL checks against a removed fabric. + Err(ErrorCode::NoSession)?; + } + if let Some(exch_index) = self.add_exch(rx_header.proto.exch_id, Role::Responder(Default::default())) { @@ -1128,14 +1136,10 @@ impl Sessions { rx_peer: &Address, rx_plain: &PlainHdr, ) -> Option<&mut Session> { - // Per Matter Core spec section 4.7.1.3, an expired session must not - // accept new inbound messages. Skipping expired sessions here lets the - // caller surface a `SessionNotFound` to the peer rather than running - // the request through ACL checks against a removed fabric. let mut session = self .sessions .iter_mut() - .find(|sess| !sess.expired && sess.is_for_rx(rx_peer, rx_plain)); + .find(|sess| sess.is_for_rx(rx_peer, rx_plain)); if let Some(session) = session.as_mut() { session.update_last_used(); diff --git a/rs-matter/src/utils/epoch.rs b/rs-matter/src/utils/epoch.rs index 6f9ca531..a3141fd0 100644 --- a/rs-matter/src/utils/epoch.rs +++ b/rs-matter/src/utils/epoch.rs @@ -35,14 +35,14 @@ /// Matter epoch (2000-01-01T00:00:00Z UTC). Add this constant to a /// Matter-epoch value to get a UNIX-epoch value, or subtract it from /// a UNIX-epoch value to get a Matter-epoch value. -pub const MATTER_EPOCH_SECS: u64 = 946684800; +pub const MATTER_EPOCH_SECS: u64 = 946_684_800; /// Matter-epoch value used in cert `NotAfter` fields to mean "no /// expiration" — corresponds to the X.509 `99991231235959Z` /// generalized-time sentinel translated to Matter-epoch seconds. /// /// `MATTER_CERT_DOESNT_EXPIRE = epoch(99991231235959Z) - MATTER_EPOCH_SECS`. -pub const MATTER_CERT_DOESNT_EXPIRE: u64 = 252455615999; +pub const MATTER_CERT_DOESNT_EXPIRE: u64 = 252_455_615_999; // Pulls in `pub const FIRMWARE_BUILD_MATTER_US: u64 = …;` written by // `build.rs`. Used by `MatterState::LkgUtc` as the seed value for the