From 8cc291d723116d8fba0a6754c32170d0eb1320a3 Mon Sep 17 00:00:00 2001 From: Michael Sandstedt Date: Tue, 7 Jun 2022 10:02:58 -0500 Subject: [PATCH] Bring certificate validity logic into spec compliance (#19119) * remove use of hard-coded time * add configuration manager support for firmware build time * implement spec-mandated Last Known Good UTC Time support * enable certificate validity NotBefore checks * add application-injectable certificate validity policies * add ESP32 platform workaround for #19081 Fixes: #14202, #15584 Co-authored-by: Tennessee Carmel-Veilleux Co-authored-by: Evgeny Margolis Co-authored-by: Boris Zbarsky --- src/app/CASEClient.cpp | 6 +- src/app/CASEClient.h | 12 +- src/app/OperationalDeviceProxy.cpp | 10 +- src/app/OperationalDeviceProxy.h | 37 +- .../operational-credentials-server.cpp | 37 +- src/app/server/Server.cpp | 7 +- src/app/server/Server.h | 4 + src/controller/CHIPDeviceController.cpp | 5 +- .../CHIPDeviceControllerFactory.cpp | 3 +- src/controller/CHIPDeviceControllerFactory.h | 13 +- .../CHIPDeviceControllerSystemState.h | 21 +- src/credentials/BUILD.gn | 5 + src/credentials/CHIPCert.cpp | 119 +++- src/credentials/CHIPCert.h | 254 +-------- src/credentials/CHIPCertificateSet.h | 298 ++++++++++ src/credentials/CertificateValidityPolicy.h | 58 ++ src/credentials/FabricTable.cpp | 159 +++++- src/credentials/FabricTable.h | 72 ++- src/credentials/LastKnownGoodTime.cpp | 267 +++++++++ src/credentials/LastKnownGoodTime.h | 171 ++++++ ...GoodTimeCertificateValidityPolicyExample.h | 64 +++ .../StrictCertificateValidityPolicyExample.h | 62 +++ src/credentials/tests/CHIPCert_test_vectors.h | 1 + src/credentials/tests/TestChipCert.cpp | 522 ++++++++++++++++-- src/credentials/tests/TestFabricTable.cpp | 310 ++++++++++- src/include/platform/BuildTime.h | 71 +++ src/include/platform/CHIPDeviceConfig.h | 4 +- src/include/platform/ConfigurationManager.h | 20 +- .../GenericConfigurationManagerImpl.h | 2 + .../GenericConfigurationManagerImpl.ipp | 39 ++ src/lib/support/DefaultStorageKeyAllocator.h | 3 + src/lib/support/logging/CHIPLogging.cpp | 2 +- src/platform/Ameba/BLEManagerImpl.cpp | 0 src/platform/BUILD.gn | 1 + src/platform/ESP32/SystemTimeSupport.cpp | 6 + src/platform/fake/ConfigurationManagerImpl.h | 13 +- src/platform/tests/TestConfigurationMgr.cpp | 196 +++++++ src/protocols/secure_channel/CASEServer.cpp | 18 +- src/protocols/secure_channel/CASEServer.h | 7 +- src/protocols/secure_channel/CASESession.cpp | 162 +++--- src/protocols/secure_channel/CASESession.h | 25 +- .../secure_channel/tests/TestCASESession.cpp | 55 +- src/tools/chip-cert/Cmd_ValidateCert.cpp | 5 +- src/tools/chip-cert/chip-cert.h | 1 + 44 files changed, 2635 insertions(+), 512 deletions(-) create mode 100644 src/credentials/CHIPCertificateSet.h create mode 100644 src/credentials/CertificateValidityPolicy.h create mode 100644 src/credentials/LastKnownGoodTime.cpp create mode 100644 src/credentials/LastKnownGoodTime.h create mode 100644 src/credentials/examples/LastKnownGoodTimeCertificateValidityPolicyExample.h create mode 100644 src/credentials/examples/StrictCertificateValidityPolicyExample.h create mode 100644 src/include/platform/BuildTime.h mode change 100755 => 100644 src/platform/Ameba/BLEManagerImpl.cpp diff --git a/src/app/CASEClient.cpp b/src/app/CASEClient.cpp index de0f3da6380b4d..0abae541ef10d4 100644 --- a/src/app/CASEClient.cpp +++ b/src/app/CASEClient.cpp @@ -44,9 +44,9 @@ CHIP_ERROR CASEClient::EstablishSession(PeerId peer, const Transport::PeerAddres VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_INTERNAL); mCASESession.SetGroupDataProvider(mInitParams.groupDataProvider); - ReturnErrorOnFailure(mCASESession.EstablishSession(*mInitParams.sessionManager, mInitParams.fabricInfo, peer.GetNodeId(), - exchange, mInitParams.sessionResumptionStorage, delegate, - mInitParams.mrpLocalConfig)); + ReturnErrorOnFailure(mCASESession.EstablishSession( + *mInitParams.sessionManager, mInitParams.fabricTable, mInitParams.fabricIndex, peer.GetNodeId(), exchange, + mInitParams.sessionResumptionStorage, mInitParams.certificateValidityPolicy, delegate, mInitParams.mrpLocalConfig)); return CHIP_NO_ERROR; } diff --git a/src/app/CASEClient.h b/src/app/CASEClient.h index e47f664a635805..42fd3192343e5e 100644 --- a/src/app/CASEClient.h +++ b/src/app/CASEClient.h @@ -28,11 +28,13 @@ class CASEClient; struct CASEClientInitParams { - SessionManager * sessionManager = nullptr; - SessionResumptionStorage * sessionResumptionStorage = nullptr; - Messaging::ExchangeManager * exchangeMgr = nullptr; - FabricInfo * fabricInfo = nullptr; - Credentials::GroupDataProvider * groupDataProvider = nullptr; + SessionManager * sessionManager = nullptr; + SessionResumptionStorage * sessionResumptionStorage = nullptr; + Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; + Messaging::ExchangeManager * exchangeMgr = nullptr; + FabricTable * fabricTable = nullptr; + FabricIndex fabricIndex = kUndefinedFabricIndex; + Credentials::GroupDataProvider * groupDataProvider = nullptr; Optional mrpLocalConfig = Optional::Missing(); }; diff --git a/src/app/OperationalDeviceProxy.cpp b/src/app/OperationalDeviceProxy.cpp index 6c6a6080de02c2..7d248901680c14 100644 --- a/src/app/OperationalDeviceProxy.cpp +++ b/src/app/OperationalDeviceProxy.cpp @@ -67,7 +67,7 @@ bool OperationalDeviceProxy::AttachToExistingSecureSession() { VerifyOrReturnError(mState == State::NeedsAddress || mState == State::ResolvingAddress || mState == State::HasAddress, false); - ScopedNodeId peerNodeId(mPeerId.GetNodeId(), mFabricInfo->GetFabricIndex()); + ScopedNodeId peerNodeId(mPeerId.GetNodeId(), mFabricIndex); auto sessionHandle = mInitParams.sessionManager->FindSecureSessionForNode(peerNodeId, MakeOptional(Transport::SecureSession::Type::kCASE)); if (!sessionHandle.HasValue()) @@ -211,9 +211,9 @@ void OperationalDeviceProxy::UpdateDeviceData(const Transport::PeerAddress & add CHIP_ERROR OperationalDeviceProxy::EstablishConnection() { - mCASEClient = mInitParams.clientPool->Allocate( - CASEClientInitParams{ mInitParams.sessionManager, mInitParams.sessionResumptionStorage, mInitParams.exchangeMgr, - mFabricInfo, mInitParams.groupDataProvider, mInitParams.mrpLocalConfig }); + mCASEClient = mInitParams.clientPool->Allocate(CASEClientInitParams{ + mInitParams.sessionManager, mInitParams.sessionResumptionStorage, mInitParams.certificateValidityPolicy, + mInitParams.exchangeMgr, mFabricTable, mFabricIndex, mInitParams.groupDataProvider, mInitParams.mrpLocalConfig }); ReturnErrorCodeIf(mCASEClient == nullptr, CHIP_ERROR_NO_MEMORY); CHIP_ERROR err = mCASEClient->EstablishSession(mPeerId, mDeviceAddress, mRemoteMRPConfig, this); @@ -352,7 +352,7 @@ void OperationalDeviceProxy::OnSessionHang() CHIP_ERROR OperationalDeviceProxy::ShutdownSubscriptions() { - return app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricInfo->GetFabricIndex(), GetDeviceId()); + return app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(mFabricIndex, GetDeviceId()); } OperationalDeviceProxy::~OperationalDeviceProxy() diff --git a/src/app/OperationalDeviceProxy.h b/src/app/OperationalDeviceProxy.h index 74731440c09c5f..dd13479d3a3a23 100644 --- a/src/app/OperationalDeviceProxy.h +++ b/src/app/OperationalDeviceProxy.h @@ -47,12 +47,13 @@ namespace chip { struct DeviceProxyInitParams { - SessionManager * sessionManager = nullptr; - SessionResumptionStorage * sessionResumptionStorage = nullptr; - Messaging::ExchangeManager * exchangeMgr = nullptr; - FabricTable * fabricTable = nullptr; - CASEClientPoolDelegate * clientPool = nullptr; - Credentials::GroupDataProvider * groupDataProvider = nullptr; + SessionManager * sessionManager = nullptr; + SessionResumptionStorage * sessionResumptionStorage = nullptr; + Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; + Messaging::ExchangeManager * exchangeMgr = nullptr; + FabricTable * fabricTable = nullptr; + CASEClientPoolDelegate * clientPool = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; Optional mrpLocalConfig = Optional::Missing(); @@ -105,8 +106,16 @@ class DLL_EXPORT OperationalDeviceProxy : public DeviceProxy, mSystemLayer = params.exchangeMgr->GetSessionManager()->SystemLayer(); mPeerId = peerId; - mFabricInfo = params.fabricTable->FindFabricWithCompressedId(peerId.GetCompressedFabricId()); - mState = State::NeedsAddress; + mFabricTable = params.fabricTable; + if (mFabricTable != nullptr) + { + auto fabricInfo = params.fabricTable->FindFabricWithCompressedId(peerId.GetCompressedFabricId()); + if (fabricInfo != nullptr) + { + mFabricIndex = fabricInfo->GetFabricIndex(); + } + } + mState = State::NeedsAddress; mAddressLookupHandle.SetListener(this); } @@ -192,14 +201,7 @@ class DLL_EXPORT OperationalDeviceProxy : public DeviceProxy, /** * @brief Get the raw Fabric ID assigned to the device. */ - FabricIndex GetFabricIndex() const - { - if (mFabricInfo != nullptr) - { - return mFabricInfo->GetFabricIndex(); - } - return kUndefinedFabricIndex; - } + FabricIndex GetFabricIndex() const { return mFabricIndex; } /** * Triggers a DNSSD lookup to find a usable peer address for this operational device. @@ -222,7 +224,8 @@ class DLL_EXPORT OperationalDeviceProxy : public DeviceProxy, }; DeviceProxyInitParams mInitParams; - FabricInfo * mFabricInfo; + FabricTable * mFabricTable = nullptr; + FabricIndex mFabricIndex = kUndefinedFabricIndex; System::Layer * mSystemLayer; // mCASEClient is only non-null if we are in State::Connecting or just diff --git a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp index 50e5c4944b39bf..57daf899ddced5 100644 --- a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp +++ b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp @@ -316,7 +316,17 @@ void FailSafeCleanup(const chip::DeviceLayer::ChipDeviceEvent * event) // command. if (event->FailSafeTimerExpired.addNocCommandHasBeenInvoked) { - DeleteFabricFromTable(fabricIndex); + CHIP_ERROR err; + err = DeleteFabricFromTable(fabricIndex); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "OpCreds: failed to delete fabric at index %u: %" CHIP_ERROR_FORMAT, fabricIndex, err.Format()); + } + err = Server::GetInstance().GetFabricTable().RevertLastKnownGoodChipEpochTime(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "OpCreds: failed to revert Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } } // If an UpdateNOC command had been successfully invoked, revert the state of operational key pair, NOC and ICAC for that @@ -325,6 +335,22 @@ void FailSafeCleanup(const chip::DeviceLayer::ChipDeviceEvent * event) if (event->FailSafeTimerExpired.updateNocCommandHasBeenInvoked) { // TODO: Revert the state of operational key pair, NOC and ICAC + CHIP_ERROR err = Server::GetInstance().GetFabricTable().RevertLastKnownGoodChipEpochTime(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "OpCreds: failed to revert Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + } +} + +void CommissioningComplete(const chip::DeviceLayer::ChipDeviceEvent * event) +{ + ChipLogProgress(Zcl, "OpCreds: Commissioning Complete"); + + CHIP_ERROR err = Server::GetInstance().GetFabricTable().CommitLastKnownGoodChipEpochTime(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Zcl, "OpCreds: failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); } } @@ -334,6 +360,10 @@ void OnPlatformEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, in { FailSafeCleanup(event); } + if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) + { + CommissioningComplete(event); + } } } // anonymous namespace @@ -840,10 +870,7 @@ bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); } - err = fabric->SetFabricInfo(gFabricBeingCommissioned); - VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); - - err = Server::GetInstance().GetFabricTable().Store(fabricIndex); + err = Server::GetInstance().GetFabricTable().UpdateFabric(fabricIndex, gFabricBeingCommissioned); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); // Flag on the fail-safe context that the UpdateNOC command was invoked. diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index aa9c714bd87e62..abd7edb535baae 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -125,6 +125,8 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) mDeviceStorage = initParams.persistentStorageDelegate; mSessionResumptionStorage = initParams.sessionResumptionStorage; + mCertificateValidityPolicy = initParams.certificateValidityPolicy; + // Set up attribute persistence before we try to bring up the data model // handler. SuccessOrExit(mAttributePersister.Init(mDeviceStorage)); @@ -255,6 +257,7 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) .sessionInitParams = { .sessionManager = &mSessions, .sessionResumptionStorage = mSessionResumptionStorage, + .certificateValidityPolicy = mCertificateValidityPolicy, .exchangeMgr = &mExchangeMgr, .fabricTable = &mFabrics, .clientPool = &mCASEClientPool, @@ -266,8 +269,8 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) err = mCASESessionManager.Init(&DeviceLayer::SystemLayer(), caseSessionManagerConfig); SuccessOrExit(err); - err = - mCASEServer.ListenForSessionEstablishment(&mExchangeMgr, &mSessions, &mFabrics, mSessionResumptionStorage, mGroupsProvider); + err = mCASEServer.ListenForSessionEstablishment(&mExchangeMgr, &mSessions, &mFabrics, mSessionResumptionStorage, + mCertificateValidityPolicy, mGroupsProvider); SuccessOrExit(err); // This code is necessary to restart listening to existing groups after a reboot diff --git a/src/app/server/Server.h b/src/app/server/Server.h index 66e7b223a161e0..b8e21e9058e619 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -94,6 +94,9 @@ struct ServerInitParams // Session resumption storage: Optional. Support session resumption when provided. // Must be initialized before being provided. SessionResumptionStorage * sessionResumptionStorage = nullptr; + // Certificate validity policy: Optional. If none is injected, CHIPCert + // enforces a default policy. + Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; // Group data provider: MUST be injected. Used to maintain critical keys such as the Identity // Protection Key (IPK) for CASE. Must be initialized before being provided. Credentials::GroupDataProvider * groupDataProvider = nullptr; @@ -394,6 +397,7 @@ class Server PersistentStorageDelegate * mDeviceStorage; SessionResumptionStorage * mSessionResumptionStorage; + Credentials::CertificateValidityPolicy * mCertificateValidityPolicy; Credentials::GroupDataProvider * mGroupsProvider; app::DefaultAttributePersistenceProvider mAttributePersister; GroupDataProviderListener mListener; diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 125c459c1e8471..e215fc7fa92c5c 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -191,10 +191,7 @@ CHIP_ERROR DeviceController::InitControllerNOCChain(const ControllerInitParams & mFabricInfo = params.systemState->Fabrics()->FindFabric(rootPublicKey, fabricId); if (mFabricInfo != nullptr) { - ReturnErrorOnFailure(mFabricInfo->SetFabricInfo(newFabric)); - // Store the new fabric info, since we might now have new certificates - // and whatnot. - ReturnErrorOnFailure(params.systemState->Fabrics()->Store(mFabricInfo->GetFabricIndex())); + ReturnErrorOnFailure(params.systemState->Fabrics()->UpdateFabric(mFabricInfo->GetFabricIndex(), newFabric)); } else { diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index 9da43c5ddc0cde..ee497e79611b0a 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -151,6 +151,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.sessionMgr = chip::Platform::New(); SimpleSessionResumptionStorage * sessionResumptionStorage = chip::Platform::New(); stateParams.sessionResumptionStorage = sessionResumptionStorage; + stateParams.certificateValidityPolicy = params.certificateValidityPolicy; stateParams.exchangeMgr = chip::Platform::New(); stateParams.messageCounterManager = chip::Platform::New(); stateParams.groupDataProvider = params.groupDataProvider; @@ -190,7 +191,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) // Enable listening for session establishment messages. ReturnErrorOnFailure(stateParams.caseServer->ListenForSessionEstablishment( stateParams.exchangeMgr, stateParams.sessionMgr, stateParams.fabricTable, stateParams.sessionResumptionStorage, - stateParams.groupDataProvider)); + stateParams.certificateValidityPolicy, stateParams.groupDataProvider)); // // We need to advertise the port that we're listening to for unsolicited messages over UDP. However, we have both a IPv4 diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index 8c00195453f5aa..aee73089d0a660 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -81,12 +81,13 @@ struct SetupParams // We're blocked because of the need to support !CHIP_DEVICE_LAYER struct FactoryInitParams { - System::Layer * systemLayer = nullptr; - PersistentStorageDelegate * fabricIndependentStorage = nullptr; - Credentials::GroupDataProvider * groupDataProvider = nullptr; - Inet::EndPointManager * tcpEndPointManager = nullptr; - Inet::EndPointManager * udpEndPointManager = nullptr; - FabricTable * fabricTable = nullptr; + System::Layer * systemLayer = nullptr; + PersistentStorageDelegate * fabricIndependentStorage = nullptr; + Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; + Inet::EndPointManager * tcpEndPointManager = nullptr; + Inet::EndPointManager * udpEndPointManager = nullptr; + FabricTable * fabricTable = nullptr; #if CONFIG_NETWORK_LAYER_BLE Ble::BleLayer * bleLayer = nullptr; #endif diff --git a/src/controller/CHIPDeviceControllerSystemState.h b/src/controller/CHIPDeviceControllerSystemState.h index 5b1cb9b6ff2ecb..92a3879f1c4682 100644 --- a/src/controller/CHIPDeviceControllerSystemState.h +++ b/src/controller/CHIPDeviceControllerSystemState.h @@ -82,16 +82,17 @@ struct DeviceControllerSystemStateParams // Params that will be deallocated via Platform::Delete in // DeviceControllerSystemState::Shutdown. - DeviceTransportMgr * transportMgr = nullptr; - SessionResumptionStorage * sessionResumptionStorage = nullptr; - SessionManager * sessionMgr = nullptr; - Messaging::ExchangeManager * exchangeMgr = nullptr; - secure_channel::MessageCounterManager * messageCounterManager = nullptr; - CASEServer * caseServer = nullptr; - CASESessionManager * caseSessionManager = nullptr; - OperationalDevicePool * operationalDevicePool = nullptr; - CASEClientPool * caseClientPool = nullptr; - FabricTable::Delegate * fabricTableDelegate = nullptr; + DeviceTransportMgr * transportMgr = nullptr; + SessionResumptionStorage * sessionResumptionStorage = nullptr; + Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr; + SessionManager * sessionMgr = nullptr; + Messaging::ExchangeManager * exchangeMgr = nullptr; + secure_channel::MessageCounterManager * messageCounterManager = nullptr; + CASEServer * caseServer = nullptr; + CASESessionManager * caseSessionManager = nullptr; + OperationalDevicePool * operationalDevicePool = nullptr; + CASEClientPool * caseClientPool = nullptr; + FabricTable::Delegate * fabricTableDelegate = nullptr; }; // A representation of the internal state maintained by the DeviceControllerFactory diff --git a/src/credentials/BUILD.gn b/src/credentials/BUILD.gn index 30faf6720f2222..c907b0aaa83996 100644 --- a/src/credentials/BUILD.gn +++ b/src/credentials/BUILD.gn @@ -25,6 +25,7 @@ static_library("credentials") { "CHIPCert.h", "CHIPCertFromX509.cpp", "CHIPCertToX509.cpp", + "CHIPCertificateSet.h", "CertificationDeclaration.cpp", "CertificationDeclaration.h", "DeviceAttestationConstructor.cpp", @@ -37,6 +38,8 @@ static_library("credentials") { "GenerateChipX509Cert.cpp", "GroupDataProvider.h", "GroupDataProviderImpl.cpp", + "LastKnownGoodTime.cpp", + "LastKnownGoodTime.h", "attestation_verifier/DeviceAttestationDelegate.h", "attestation_verifier/DeviceAttestationVerifier.cpp", "attestation_verifier/DeviceAttestationVerifier.h", @@ -46,6 +49,8 @@ static_library("credentials") { "examples/ExampleDACs.h", "examples/ExamplePAI.cpp", "examples/ExamplePAI.h", + "examples/LastKnownGoodTimeCertificateValidityPolicyExample.h", + "examples/StrictCertificateValidityPolicyExample.h", ] # TODO: These tests files should be removed after the DeviceAttestationCredsExample implementation diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp index e5ac7fd3b911d8..7f2f757c50726b 100644 --- a/src/credentials/CHIPCert.cpp +++ b/src/credentials/CHIPCert.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -273,7 +274,7 @@ CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, Va context.mTrustAnchor = nullptr; - return ValidateCert(cert, context, context.mValidateFlags, 0); + return ValidateCert(cert, context, 0); } CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, @@ -281,7 +282,7 @@ CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const Cer { context.mTrustAnchor = nullptr; - return FindValidCert(subjectDN, subjectKeyId, context, context.mValidateFlags, 0, certData); + return FindValidCert(subjectDN, subjectKeyId, context, 0, certData); } CHIP_ERROR ChipCertificateSet::VerifySignature(const ChipCertificateData * cert, const ChipCertificateData * caCert) @@ -304,8 +305,7 @@ CHIP_ERROR ChipCertificateSet::VerifySignature(const ChipCertificateData * cert, return CHIP_NO_ERROR; } -CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, ValidationContext & context, - BitFlags validateFlags, uint8_t depth) +CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, ValidationContext & context, uint8_t depth) { CHIP_ERROR err = CHIP_NO_ERROR; const ChipCertificateData * caCert = nullptr; @@ -371,15 +371,85 @@ CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, Va } } - // Verify the validity time of the certificate, if requested. - if (cert->mNotBeforeTime != 0 && !validateFlags.Has(CertValidateFlags::kIgnoreNotBefore)) + // Verify NotBefore and NotAfter validity of the certificates. + // + // See also ASN1ToChipEpochTime(). + // + // X.509/RFC5280 defines the special time 99991231235959Z to mean 'no + // well-defined expiration date'. In CHIP TLV-encoded certificates, this + // special value is represented as a CHIP Epoch time value of 0 sec + // (2020-01-01 00:00:00 UTC). + // + // When evaluating NotBefore from a CHIP TLV-encoded certificate, this + // special value 0 does not require additional handling, as it is impossible + // for CHIP epoch time to represent any moment before the epoch, and so all + // representable times at or after this will be considered valid. But for + // NotAfter, we special case this value as always passing (not expired). + CertificateValidityResult validityResult; + if (context.mEffectiveTime.Is()) { - // TODO - enable check for certificate validity dates - // VerifyOrExit(context.mEffectiveTime >= cert->mNotBeforeTime, err = CHIP_ERROR_CERT_NOT_VALID_YET); + if (context.mEffectiveTime.Get().count() < cert->mNotBeforeTime) + { + validityResult = CertificateValidityResult::kNotYetValid; + } + else if (cert->mNotAfterTime != 0 && context.mEffectiveTime.Get().count() > cert->mNotAfterTime) + { + validityResult = CertificateValidityResult::kExpired; + } + else + { + validityResult = CertificateValidityResult::kValid; + } + } + else if (context.mEffectiveTime.Is()) + { + // Last Known Good Time may not be moved forward except at the time of + // commissioning or firmware update, so we can't use it to validate + // NotBefore. However, so long as firmware build times are properly + // recorded and certificates loaded during commissioning are in fact + // valid at the time of commissioning, observing a NotAfter that falls + // before Last Known Good Time is a reliable indicator that the + // certificate in question is expired. Check for this. + if (cert->mNotAfterTime != 0 && context.mEffectiveTime.Get().count() > cert->mNotAfterTime) + { + validityResult = CertificateValidityResult::kExpiredAtLastKnownGoodTime; + } + else + { + validityResult = CertificateValidityResult::kNotExpiredAtLastKnownGoodTime; + } + } + else + { + validityResult = CertificateValidityResult::kTimeUnknown; + } + + if (context.mValidityPolicy != nullptr) + { + SuccessOrExit(err = context.mValidityPolicy->ApplyCertificateValidityPolicy(cert, depth, validityResult)); } - if (cert->mNotAfterTime != 0 && !validateFlags.Has(CertValidateFlags::kIgnoreNotAfter)) + else { - VerifyOrExit(context.mEffectiveTime <= cert->mNotAfterTime, err = CHIP_ERROR_CERT_EXPIRED); + switch (validityResult) + { + case CertificateValidityResult::kValid: + case CertificateValidityResult::kNotExpiredAtLastKnownGoodTime: + // By default, we do not enforce certificate validity based upon a Last + // Known Good Time source. However, implementations may always inject a + // policy that does enforce based upon this. + case CertificateValidityResult::kExpiredAtLastKnownGoodTime: + case CertificateValidityResult::kTimeUnknown: + break; + case CertificateValidityResult::kNotYetValid: + ExitNow(err = CHIP_ERROR_CERT_NOT_VALID_YET); + break; + case CertificateValidityResult::kExpired: + ExitNow(err = CHIP_ERROR_CERT_EXPIRED); + break; + default: + ExitNow(err = CHIP_ERROR_INTERNAL); + break; + } } // If the certificate itself is trusted, then it is implicitly valid. Record this certificate as the trust @@ -411,7 +481,7 @@ CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, Va // Search for a valid CA certificate that matches the Issuer DN and Authority Key Id of the current certificate. // Fail if no acceptable certificate is found. - err = FindValidCert(cert->mIssuerDN, cert->mAuthKeyId, context, validateFlags, static_cast(depth + 1), &caCert); + err = FindValidCert(cert->mIssuerDN, cert->mAuthKeyId, context, static_cast(depth + 1), &caCert); if (err != CHIP_NO_ERROR) { ExitNow(err = CHIP_ERROR_CA_CERT_NOT_FOUND); @@ -427,8 +497,7 @@ CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, Va } CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, - ValidationContext & context, BitFlags validateFlags, uint8_t depth, - const ChipCertificateData ** certData) + ValidationContext & context, uint8_t depth, const ChipCertificateData ** certData) { CHIP_ERROR err; @@ -461,7 +530,7 @@ CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const Cer // Attempt to validate the cert. If the cert is valid, return it to the caller. Otherwise, // save the returned error and continue searching. If there are no other matching certs this // will be the error returned to the caller. - err = ValidateCert(candidateCert, context, validateFlags, depth); + err = ValidateCert(candidateCert, context, depth); if (err == CHIP_NO_ERROR) { *certData = candidateCert; @@ -513,11 +582,11 @@ bool ChipCertificateData::IsEqual(const ChipCertificateData & other) const void ValidationContext::Reset() { - mEffectiveTime = 0; - mTrustAnchor = nullptr; + mEffectiveTime = EffectiveTime{}; + mTrustAnchor = nullptr; + mValidityPolicy = nullptr; mRequiredKeyUsages.ClearAll(); mRequiredKeyPurposes.ClearAll(); - mValidateFlags.ClearAll(); mRequiredCertType = kCertType_NotSpecified; } @@ -1013,7 +1082,7 @@ DLL_EXPORT CHIP_ERROR ASN1ToChipEpochTime(const chip::ASN1::ASN1UniversalTime & CHIP_ERROR err = CHIP_NO_ERROR; // X.509/RFC5280 defines the special time 99991231235959Z to mean 'no well-defined expiration date'. - // In CHIP certificate it is represented as a CHIP Epoch UTC time value of 0 sec (2000-01-01 00:00:00 UTC). + // In CHIP certificate it is represented as a CHIP Epoch time value of 0 sec (2000-01-01 00:00:00 UTC). if ((asn1Time.Year == kX509NoWellDefinedExpirationDateYear) && (asn1Time.Month == kMonthsPerYear) && (asn1Time.Day == kMaxDaysPerMonth) && (asn1Time.Hour == kHoursPerDay - 1) && (asn1Time.Minute == kMinutesPerHour - 1) && (asn1Time.Second == kSecondsPerMinute - 1)) @@ -1276,6 +1345,20 @@ CHIP_ERROR ExtractPublicKeyFromChipCert(const ByteSpan & chipCert, P256PublicKey return CHIP_NO_ERROR; } +CHIP_ERROR ExtractNotBeforeFromChipCert(const ByteSpan & chipCert, chip::System::Clock::Seconds32 & notBeforeChipEpochTime) +{ + ChipCertificateSet certSet; + ChipCertificateData certData; + + ReturnErrorOnFailure(certSet.Init(&certData, 1)); + + ReturnErrorOnFailure(certSet.LoadCert(chipCert, BitFlags())); + + notBeforeChipEpochTime = chip::System::Clock::Seconds32(certData.mNotBeforeTime); + + return CHIP_NO_ERROR; +} + CHIP_ERROR ExtractSKIDFromChipCert(const ByteSpan & chipCert, CertificateKeyId & skid) { ChipCertificateSet certSet; diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h index cbc6d0d98f2b00..16279294498c75 100644 --- a/src/credentials/CHIPCert.h +++ b/src/credentials/CHIPCert.h @@ -173,18 +173,6 @@ enum class CertDecodeFlags : uint8_t kIsTrustAnchor = 0x02, /**< Indicates that the corresponding certificate is trust anchor. */ }; -/** CHIP Certificate Validate Flags - * - * Contains information specifying how a certificate should be validated. - */ -enum class CertValidateFlags : uint8_t -{ - kIgnoreNotBefore = 0x01, /**< Indicate that a Not Before field should be ignored when doing certificate validation. This - flag applies to all certificates in the validation chain. */ - kIgnoreNotAfter = 0x02, /**< Indicate that a Not After field should be ignored when doing certificate validation. This - flag applies to all certificates in the validation chain. */ -}; - enum { kNullCertTime = 0 @@ -454,225 +442,6 @@ struct ChipCertificateData uint8_t mTBSHash[Crypto::kSHA256_Hash_Length]; /**< Certificate TBS hash. */ }; -/** - * @struct ValidationContext - * - * @brief - * Context information used during certification validation. - */ -struct ValidationContext -{ - uint32_t mEffectiveTime; /**< Current CHIP Epoch UTC time. */ - const ChipCertificateData * mTrustAnchor; /**< Pointer to the Trust Anchor Certificate data structure. - This value is set during certificate validation process - to indicate to the caller the trust anchor of the - validated certificate. */ - BitFlags mRequiredKeyUsages; /**< Key usage extensions that should be present in the - validated certificate. */ - BitFlags mRequiredKeyPurposes; /**< Extended Key usage extensions that should be present - in the validated certificate. */ - BitFlags mValidateFlags; /**< Certificate validation flags, specifying how a certificate - should be validated. */ - uint8_t mRequiredCertType; /**< Required certificate type. */ - - void Reset(); -}; - -/** - * @class ChipCertificateSet - * - * @brief - * Collection of CHIP certificate data providing methods for - * certificate validation and signature verification. - */ -class DLL_EXPORT ChipCertificateSet -{ -public: - ChipCertificateSet(); - ~ChipCertificateSet(); - - ChipCertificateSet & operator=(ChipCertificateSet && aOther) - { - mCerts = aOther.mCerts; - aOther.mCerts = nullptr; - mCertCount = aOther.mCertCount; - mMaxCerts = aOther.mMaxCerts; - mMemoryAllocInternal = aOther.mMemoryAllocInternal; - - return *this; - } - - /** - * @brief Initialize ChipCertificateSet. - * This initialization method is used when all memory structures needed for operation are - * allocated internally using chip::Platform::MemoryAlloc() and freed with chip::Platform::MemoryFree(). - * - * @param maxCertsArraySize Maximum number of CHIP certificates to be loaded to the set. - * - * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR Init(uint8_t maxCertsArraySize); - - /** - * @brief Initialize ChipCertificateSet. - * This initialization method is used when all memory structures needed for operation are - * allocated externally and methods in this class don't need to deal with memory allocations. - * - * @param certsArray A pointer to the array of the ChipCertificateData structures. - * @param certsArraySize Number of ChipCertificateData entries in the array. - * - * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR Init(ChipCertificateData * certsArray, uint8_t certsArraySize); - - /** - * @brief Release resources allocated by this class. - **/ - void Release(); - - /** - * @brief Clear certificate data loaded into this set. - **/ - void Clear(); - - /** - * @brief Load CHIP certificate into set. - * It is required that the CHIP certificate in the chipCert buffer stays valid while - * the certificate data in the set is used. - * In case of an error the certificate set is left in the same state as prior to this call. - * - * @param chipCert Buffer containing certificate encoded in CHIP format. - * @param decodeFlags Certificate decoding option flags. - * - * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR LoadCert(const ByteSpan chipCert, BitFlags decodeFlags); - - /** - * @brief Load CHIP certificate into set. - * It is required that the CHIP certificate in the reader's underlying buffer stays valid while - * the certificate data in the set is used. - * In case of an error the certificate set is left in the same state as prior to this call. - * - * @param reader A TLVReader positioned at the CHIP certificate TLV structure. - * @param decodeFlags Certificate decoding option flags. - * @param chipCert Buffer containing certificate encoded on CHIP format. It is required that this CHIP certificate - * in chipCert ByteSpan stays valid while the certificate data in the set is used. - * - * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR LoadCert(chip::TLV::TLVReader & reader, BitFlags decodeFlags, ByteSpan chipCert = ByteSpan()); - - CHIP_ERROR ReleaseLastCert(); - - /** - * @brief Find certificate in the set. - * - * @param subjectKeyId Subject key identifier of the certificate to be found in the set. - * - * @return A pointer to the certificate data On success. Otherwise, NULL if no certificate found. - **/ - const ChipCertificateData * FindCert(const CertificateKeyId & subjectKeyId) const; - - /** - * @return A pointer to the set of certificate data entries. - **/ - const ChipCertificateData * GetCertSet() const { return mCerts; } - - /** - * @return A pointer to the last certificate data in the set. Returns NULL if certificate set is empty. - **/ - const ChipCertificateData * GetLastCert() const { return (mCertCount > 0) ? &mCerts[mCertCount - 1] : nullptr; } - - /** - * @return Number of certificates loaded into the set. - **/ - uint8_t GetCertCount() const { return mCertCount; } - - /** - * @brief Check whether certificate is in the set. - * - * @param cert Pointer to the ChipCertificateData structures. - * - * @return True if certificate is in the set, false otherwise. - **/ - bool IsCertInTheSet(const ChipCertificateData * cert) const; - - /** - * @brief Validate CHIP certificate. - * - * @param cert Pointer to the CHIP certificate to be validated. The certificate is - * required to be in this set, otherwise this function returns error. - * @param context Certificate validation context. - * - * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR ValidateCert(const ChipCertificateData * cert, ValidationContext & context); - - /** - * @brief Find and validate CHIP certificate. - * - * @param[in] subjectDN Subject distinguished name to use as certificate search parameter. - * @param[in] subjectKeyId Subject key identifier to use as certificate search parameter. - * @param[in] context Certificate validation context. - * @param[out] certData A slot to write a pointer to the CHIP certificate data that matches search criteria. - * - * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, ValidationContext & context, - const ChipCertificateData ** certData); - - /** - * @brief Verify CHIP certificate signature. - * - * @param cert Pointer to the CHIP certificate which signature should be validated. - * @param caCert Pointer to the CA certificate of the verified certificate. - * - * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise - **/ - static CHIP_ERROR VerifySignature(const ChipCertificateData * cert, const ChipCertificateData * caCert); - -private: - ChipCertificateData * mCerts; /**< Pointer to an array of certificate data. */ - uint8_t mCertCount; /**< Number of certificates in mCerts - array. We maintain the invariant that all - the slots at indices less than - mCertCount have been constructed and slots - at indices >= mCertCount have either never - had their constructor called, or have had - their destructor called since then. */ - uint8_t mMaxCerts; /**< Length of mCerts array. */ - bool mMemoryAllocInternal; /**< Indicates whether temporary memory buffers are allocated internally. */ - - /** - * @brief Find and validate CHIP certificate. - * - * @param[in] subjectDN Subject distinguished name to use as certificate search parameter. - * @param[in] subjectKeyId Subject key identifier to use as certificate search parameter. - * @param[in] context Certificate validation context. - * @param[in] validateFlags Certificate validation flags. - * @param[in] depth Depth of the current certificate in the certificate validation chain. - * @param[out] certData A slot to write a pointer to the CHIP certificate data that matches search criteria. - * - * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, ValidationContext & context, - BitFlags validateFlags, uint8_t depth, const ChipCertificateData ** certData); - - /** - * @brief Validate CHIP certificate. - * - * @param cert Pointer to the CHIP certificate to be validated. - * @param context Certificate validation context. - * @param validateFlags Certificate validation flags. - * @param depth Depth of the current certificate in the certificate validation chain. - * - * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise - **/ - CHIP_ERROR ValidateCert(const ChipCertificateData * cert, ValidationContext & context, - BitFlags validateFlags, uint8_t depth); -}; - /** * @brief Decode CHIP certificate. * It is required that the CHIP certificate in the chipCert buffer stays valid while @@ -778,14 +547,14 @@ CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParam /** * @brief - * Convert a certificate date/time (in the form of an ASN.1 universal time structure) into a CHIP Epoch UTC time. + * Convert a certificate date/time (in the form of an ASN.1 universal time structure) into a CHIP Epoch time. * * @note * This function makes no attempt to verify the correct range of the input time other than year. * Therefore callers must make sure the supplied values are valid prior to invocation. * * @param asn1Time The calendar date/time to be converted. - * @param epochTime A reference to an integer that will receive CHIP Epoch UTC time. + * @param epochTime A reference to an integer that will receive CHIP Epoch time. * * @retval #CHIP_NO_ERROR If the input time was successfully converted. * @retval #ASN1_ERROR_UNSUPPORTED_ENCODING If the input time contained a year value that could not @@ -967,6 +736,25 @@ CHIP_ERROR ExtractNodeIdFabricIdFromOpCert(const ByteSpan & opcert, NodeId * nod */ CHIP_ERROR ExtractPublicKeyFromChipCert(const ByteSpan & chipCert, P256PublicKeySpan & publicKey); +/** + * Extract Not Before Time from a chip certificate in ByteSpan TLV-encoded form. + * Output format is seconds referenced from the CHIP epoch. + * + * Special value 0 corresponds to the X.509/RFC5280 defined special time value + * 99991231235959Z meaning 'no well-defined expiration date'. However, as a + * NotBefore time, this does not require special handling when comparing to + * a CHIP epoch time source, as 0 (the epoch) is also the earliest representable + * uint32 time. + + * This does not perform any sort of validation on the certificate structure + * other than parsing it. + * + * @param chipCert CHIP certificate in TLV-encoded form + * @param notBeforeChipEpochTime (out) certificate NotBefore time as seconds from the CHIP epoch + * @return CHIP_NO_ERROR if certificate parsing was successful, else an appropriate CHIP_ERROR + */ +CHIP_ERROR ExtractNotBeforeFromChipCert(const ByteSpan & chipCert, chip::System::Clock::Seconds32 & notBeforeChipEpochTime); + /** * Extract Subject Key Identifier from a chip certificate in ByteSpan TLV-encoded form. * This does not perform any sort of validation on the certificate structure diff --git a/src/credentials/CHIPCertificateSet.h b/src/credentials/CHIPCertificateSet.h new file mode 100644 index 00000000000000..d3694467c74fbc --- /dev/null +++ b/src/credentials/CHIPCertificateSet.h @@ -0,0 +1,298 @@ +/* + * + * Copyright (c) 2020-2022 Project CHIP Authors + * Copyright (c) 2019 Google LLC. + * Copyright (c) 2013-2017 Nest Labs, Inc. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * This file defines data types and objects for modeling and + * working with CHIP certificates. + * + */ + +#pragma once + +#include +#include + +#include "CHIPCert.h" +#include "CertificateValidityPolicy.h" +#include + +namespace chip { +namespace Credentials { + +struct CurrentChipEpochTime : chip::System::Clock::Seconds32 +{ + template + CurrentChipEpochTime(Args &&... args) : chip::System::Clock::Seconds32(std::forward(args)...) + {} +}; + +struct LastKnownGoodChipEpochTime : chip::System::Clock::Seconds32 +{ + template + LastKnownGoodChipEpochTime(Args &&... args) : chip::System::Clock::Seconds32(std::forward(args)...) + {} +}; + +using EffectiveTime = Variant; + +/** + * @struct ValidationContext + * + * @brief + * Context information used during certification validation. + */ +struct ValidationContext +{ + EffectiveTime mEffectiveTime; /**< Current or last known good CHIP Epoch time. */ + const ChipCertificateData * mTrustAnchor; /**< Pointer to the Trust Anchor Certificate data structure. + This value is set during certificate validation process + to indicate to the caller the trust anchor of the + validated certificate. */ + BitFlags mRequiredKeyUsages; /**< Key usage extensions that should be present in the + validated certificate. */ + BitFlags mRequiredKeyPurposes; /**< Extended Key usage extensions that should be present + in the validated certificate. */ + uint8_t mRequiredCertType; /**< Required certificate type. */ + + CertificateValidityPolicy * mValidityPolicy = + nullptr; /**< Optional application policy to apply for certificate validity period evaluation. */ + + void Reset(); + + template + void SetEffectiveTime(chip::System::Clock::Seconds32 chipTime) + { + mEffectiveTime.Set(chipTime); + } + + template + CHIP_ERROR SetEffectiveTimeFromUnixTime(chip::System::Clock::Seconds32 unixTime) + { + uint32_t chipTime; + VerifyOrReturnError(UnixEpochToChipEpochTime(unixTime.count(), chipTime), CHIP_ERROR_INVALID_TIME); + SetEffectiveTime(chip::System::Clock::Seconds32(chipTime)); + return CHIP_NO_ERROR; + } + + template + CHIP_ERROR SetEffectiveTimeFromAsn1Time(const ASN1::ASN1UniversalTime & asn1Time) + { + uint32_t chipTime; + ReturnErrorOnFailure(ASN1ToChipEpochTime(asn1Time, chipTime)); + SetEffectiveTime(chip::System::Clock::Seconds32(chipTime)); + return CHIP_NO_ERROR; + } +}; + +/** + * @class ChipCertificateSet + * + * @brief + * Collection of CHIP certificate data providing methods for + * certificate validation and signature verification. + */ +class DLL_EXPORT ChipCertificateSet +{ +public: + ChipCertificateSet(); + ~ChipCertificateSet(); + + ChipCertificateSet & operator=(ChipCertificateSet && aOther) + { + mCerts = aOther.mCerts; + aOther.mCerts = nullptr; + mCertCount = aOther.mCertCount; + mMaxCerts = aOther.mMaxCerts; + mMemoryAllocInternal = aOther.mMemoryAllocInternal; + + return *this; + } + + /** + * @brief Initialize ChipCertificateSet. + * This initialization method is used when all memory structures needed for operation are + * allocated internally using chip::Platform::MemoryAlloc() and freed with chip::Platform::MemoryFree(). + * + * @param maxCertsArraySize Maximum number of CHIP certificates to be loaded to the set. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR Init(uint8_t maxCertsArraySize); + + /** + * @brief Initialize ChipCertificateSet. + * This initialization method is used when all memory structures needed for operation are + * allocated externally and methods in this class don't need to deal with memory allocations. + * + * @param certsArray A pointer to the array of the ChipCertificateData structures. + * @param certsArraySize Number of ChipCertificateData entries in the array. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR Init(ChipCertificateData * certsArray, uint8_t certsArraySize); + + /** + * @brief Release resources allocated by this class. + **/ + void Release(); + + /** + * @brief Clear certificate data loaded into this set. + **/ + void Clear(); + + /** + * @brief Load CHIP certificate into set. + * It is required that the CHIP certificate in the chipCert buffer stays valid while + * the certificate data in the set is used. + * In case of an error the certificate set is left in the same state as prior to this call. + * + * @param chipCert Buffer containing certificate encoded in CHIP format. + * @param decodeFlags Certificate decoding option flags. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR LoadCert(const ByteSpan chipCert, BitFlags decodeFlags); + + /** + * @brief Load CHIP certificate into set. + * It is required that the CHIP certificate in the reader's underlying buffer stays valid while + * the certificate data in the set is used. + * In case of an error the certificate set is left in the same state as prior to this call. + * + * @param reader A TLVReader positioned at the CHIP certificate TLV structure. + * @param decodeFlags Certificate decoding option flags. + * @param chipCert Buffer containing certificate encoded on CHIP format. It is required that this CHIP certificate + * in chipCert ByteSpan stays valid while the certificate data in the set is used. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR LoadCert(chip::TLV::TLVReader & reader, BitFlags decodeFlags, ByteSpan chipCert = ByteSpan()); + + CHIP_ERROR ReleaseLastCert(); + + /** + * @brief Find certificate in the set. + * + * @param subjectKeyId Subject key identifier of the certificate to be found in the set. + * + * @return A pointer to the certificate data On success. Otherwise, NULL if no certificate found. + **/ + const ChipCertificateData * FindCert(const CertificateKeyId & subjectKeyId) const; + + /** + * @return A pointer to the set of certificate data entries. + **/ + const ChipCertificateData * GetCertSet() const { return mCerts; } + + /** + * @return A pointer to the last certificate data in the set. Returns NULL if certificate set is empty. + **/ + const ChipCertificateData * GetLastCert() const { return (mCertCount > 0) ? &mCerts[mCertCount - 1] : nullptr; } + + /** + * @return Number of certificates loaded into the set. + **/ + uint8_t GetCertCount() const { return mCertCount; } + + /** + * @brief Check whether certificate is in the set. + * + * @param cert Pointer to the ChipCertificateData structures. + * + * @return True if certificate is in the set, false otherwise. + **/ + bool IsCertInTheSet(const ChipCertificateData * cert) const; + + /** + * @brief Validate CHIP certificate. + * + * @param cert Pointer to the CHIP certificate to be validated. The certificate is + * required to be in this set, otherwise this function returns error. + * @param context Certificate validation context. + * + * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR ValidateCert(const ChipCertificateData * cert, ValidationContext & context); + + /** + * @brief Find and validate CHIP certificate. + * + * @param[in] subjectDN Subject distinguished name to use as certificate search parameter. + * @param[in] subjectKeyId Subject key identifier to use as certificate search parameter. + * @param[in] context Certificate validation context. + * @param[out] certData A slot to write a pointer to the CHIP certificate data that matches search criteria. + * + * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, ValidationContext & context, + const ChipCertificateData ** certData); + + /** + * @brief Verify CHIP certificate signature. + * + * @param cert Pointer to the CHIP certificate which signature should be validated. + * @param caCert Pointer to the CA certificate of the verified certificate. + * + * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise + **/ + static CHIP_ERROR VerifySignature(const ChipCertificateData * cert, const ChipCertificateData * caCert); + +private: + ChipCertificateData * mCerts; /**< Pointer to an array of certificate data. */ + uint8_t mCertCount; /**< Number of certificates in mCerts + array. We maintain the invariant that all + the slots at indices less than + mCertCount have been constructed and slots + at indices >= mCertCount have either never + had their constructor called, or have had + their destructor called since then. */ + uint8_t mMaxCerts; /**< Length of mCerts array. */ + bool mMemoryAllocInternal; /**< Indicates whether temporary memory buffers are allocated internally. */ + + /** + * @brief Find and validate CHIP certificate. + * + * @param[in] subjectDN Subject distinguished name to use as certificate search parameter. + * @param[in] subjectKeyId Subject key identifier to use as certificate search parameter. + * @param[in] context Certificate validation context. + * @param[in] depth Depth of the current certificate in the certificate validation chain. + * @param[out] certData A slot to write a pointer to the CHIP certificate data that matches search criteria. + * + * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, ValidationContext & context, + uint8_t depth, const ChipCertificateData ** certData); + + /** + * @brief Validate CHIP certificate. + * + * @param cert Pointer to the CHIP certificate to be validated. + * @param context Certificate validation context. + * @param depth Depth of the current certificate in the certificate validation chain. + * + * @return Returns a CHIP_ERROR on validation or other error, CHIP_NO_ERROR otherwise + **/ + CHIP_ERROR ValidateCert(const ChipCertificateData * cert, ValidationContext & context, uint8_t depth); +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/CertificateValidityPolicy.h b/src/credentials/CertificateValidityPolicy.h new file mode 100644 index 00000000000000..0df351dff8390c --- /dev/null +++ b/src/credentials/CertificateValidityPolicy.h @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include +#include + +namespace chip { +namespace Credentials { + +enum class CertificateValidityResult +{ + kValid = 0, // current time is known and is within the validity period bounded by [notBefore, notAfter] + kNotYetValid = 1, // current time is known and falls before the validity period bounded by notBefore + kExpired = 2, // current time is known and falls after the validity period bounded by notAfter + kNotExpiredAtLastKnownGoodTime = 3, // Last Known Good Time is known and notAfter occurs at or after this + kExpiredAtLastKnownGoodTime = 4, // Last Known Good Time is known and notAfter occurs before this + kTimeUnknown = 5, // No time source is available +}; + +/// Callback to request application acceptance or rejection of the path +/// segment based upon the CertificateValidityResult. +class CertificateValidityPolicy +{ +public: + virtual ~CertificateValidityPolicy() {} + + /** + * If a policy is provided to CHIPCert, this method is invoked to + * determine what action an application determines is appropriate given + * CHIPCert's evaluation of certificate validity based upon the best + * available time source. If no policy is provided, CHIPCert enforces a + * default policy. + * + * @param cert CHIP Certificate from a peer certificate chain to be evaluated based upon application-enacted expiration policies + * @param depth the depth of the certificate in the chain, where the leaf is at depth 0 + * @return CHIP_NO_ERROR if CHIPCert should accept the certificate; an appropriate CHIP_ERROR if it should be rejected + */ + virtual CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) = 0; +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index 31db65b9a0703a..2a38d3923a7d3b 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -62,6 +62,7 @@ constexpr uint16_t kOpKeyVersion = 1; // Tags for our index list storage. constexpr TLV::Tag kNextAvailableFabricIndexTag = TLV::ContextTag(0); constexpr TLV::Tag kFabricIndicesTag = TLV::ContextTag(1); + } // anonymous namespace CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) @@ -150,7 +151,7 @@ CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage) { DefaultStorageKeyAllocator keyAlloc; - ChipLogProgress(Inet, "Loading from storage for fabric index 0x%x", static_cast(mFabricIndex)); + ChipLogProgress(FabricProvisioning, "Loading from storage for fabric index 0x%x", static_cast(mFabricIndex)); // Scopes for "size" so we don't forget to re-initialize it between gets, // since each get modifies it. @@ -319,7 +320,8 @@ CHIP_ERROR FabricInfo::DeleteFromStorage(PersistentStorageDelegate * storage, Fa } if (prevDeleteErr != CHIP_NO_ERROR) { - ChipLogDetail(Discovery, "Error deleting part of fabric %d: %" CHIP_ERROR_FORMAT, fabricIndex, prevDeleteErr.Format()); + ChipLogDetail(FabricProvisioning, "Error deleting part of fabric %d: %" CHIP_ERROR_FORMAT, fabricIndex, + prevDeleteErr.Format()); } return prevDeleteErr; } @@ -547,7 +549,7 @@ CHIP_ERROR FabricTable::Store(FabricIndex fabricIndex) exit: if (err == CHIP_NO_ERROR && mDelegateListRoot != nullptr) { - ChipLogProgress(Discovery, "Fabric (0x%x) persisted to storage. Calling OnFabricPersistedToStorage", + ChipLogProgress(FabricProvisioning, "Fabric (0x%x) persisted to storage. Calling OnFabricPersistedToStorage", static_cast(fabricIndex)); FabricTable::Delegate * delegate = mDelegateListRoot; while (delegate) @@ -570,7 +572,8 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric) FabricTable::Delegate * delegate = mDelegateListRoot; while (delegate) { - ChipLogProgress(Discovery, "Fabric (0x%x) loaded from storage", static_cast(fabric->GetFabricIndex())); + ChipLogProgress(FabricProvisioning, "Fabric (0x%x) loaded from storage", + static_cast(fabric->GetFabricIndex())); delegate->OnFabricRetrievedFromStorage(*this, fabric->GetFabricIndex()); delegate = delegate->next; } @@ -578,18 +581,36 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric) return CHIP_NO_ERROR; } -CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric) +CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric, Credentials::CertificateValidityPolicy * policy) { P256PublicKey pubkey; ValidationContext validContext; + // Note that we do NOT set a time in the validation context. This will + // cause the certificate chain NotBefore / NotAfter time validation logic + // to report CertificateValidityResult::kTimeUnknown. + // + // The default CHIPCert policy passes NotBefore / NotAfter validation for + // this case where time is unknown. If an override policy is passed, it + // will be up to the passed policy to decide how to handle this. + // + // In the FabricTable::AddNewFabric and FabricTable::UpdateFabric calls, + // the passed policy always passes for all questions of time validity. The + // rationale is that installed certificates should be valid at the time of + // installation by definition. If they are not and the commissionee and + // commissioner disagree enough on current time, CASE will fail and our + // fail-safe timer will expire. + // + // This then is ultimately how we validate that NotBefore / NotAfter in + // newly installed certificates is workable. validContext.Reset(); validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); + validContext.mValidityPolicy = policy; // Make sure to not modify any of our state until VerifyCredentials passes. PeerId operationalId; FabricId fabricId; - ChipLogProgress(Discovery, "Verifying the received credentials"); + ChipLogProgress(FabricProvisioning, "Verifying the received credentials"); CHIP_ERROR err = VerifyCredentials(newFabric.mNOCCert, newFabric.mICACert, newFabric.mRootCert, validContext, operationalId, fabricId, pubkey); if (err != CHIP_NO_ERROR && err != CHIP_ERROR_WRONG_NODE_ID) @@ -630,9 +651,9 @@ CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric) SetNOCCert(newFabric.mNOCCert); SetVendorId(newFabric.GetVendorId()); SetFabricLabel(newFabric.GetFabricLabel()); - ChipLogProgress(Discovery, "Added new fabric at index: 0x%x, Initialized: %d", static_cast(GetFabricIndex()), + ChipLogProgress(FabricProvisioning, "Added new fabric at index: 0x%x, Initialized: %d", static_cast(GetFabricIndex()), IsInitialized()); - ChipLogProgress(Discovery, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64, + ChipLogProgress(FabricProvisioning, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64, ChipLogValueX64(mOperationalId.GetCompressedFabricId()), ChipLogValueX64(mOperationalId.GetNodeId())); return CHIP_NO_ERROR; } @@ -675,7 +696,53 @@ CHIP_ERROR FabricTable::AddNewFabric(FabricInfo & newFabric, FabricIndex * outpu return AddNewFabricInner(newFabric, outputIndex); } -CHIP_ERROR FabricTable::AddNewFabricInner(FabricInfo & newFabric, FabricIndex * outputIndex) +/* + * A validation policy we can pass into VerifyCredentials to extract the + * latest NotBefore time in the certificate chain without having to load the + * certificates into memory again, and one which will pass validation for all + * questions of NotBefore / NotAfter validity. + * + * The rationale is that installed certificates should be valid at the time of + * installation by definition. If they are not and the commissionee and + * commissioner disagree enough on current time, CASE will fail and our + * fail-safe timer will expire. + * + * This then is ultimately how we validate that NotBefore / NotAfter in + * newly installed certificates is workable. + */ +class NotBeforeCollector : public Credentials::CertificateValidityPolicy +{ +public: + NotBeforeCollector() : mLatestNotBefore(0) {} + CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) override + { + if (cert->mNotBeforeTime > mLatestNotBefore.count()) + { + mLatestNotBefore = System::Clock::Seconds32(cert->mNotBeforeTime); + } + return CHIP_NO_ERROR; + } + System::Clock::Seconds32 mLatestNotBefore; +}; + +CHIP_ERROR FabricTable::UpdateFabric(FabricIndex fabricIndex, FabricInfo & newFabricInfo) +{ + FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + NotBeforeCollector notBeforeCollector; + ReturnErrorOnFailure(fabricInfo->SetFabricInfo(newFabricInfo, ¬BeforeCollector)); + ReturnErrorOnFailure(Store(fabricIndex)); + // Update failure of Last Known Good Time is non-fatal. If Last + // Known Good Time is unknown during incoming certificate validation + // for CASE and current time is also unknown, the certificate + // validity policy will see this condition and can act appropriately. + mLastKnownGoodTime.UpdateLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore); + return CHIP_NO_ERROR; +} + +CHIP_ERROR +FabricTable::AddNewFabricInner(FabricInfo & newFabric, FabricIndex * outputIndex) { if (!mNextAvailableFabricIndex.HasValue()) { @@ -688,10 +755,11 @@ CHIP_ERROR FabricTable::AddNewFabricInner(FabricInfo & newFabric, FabricIndex * { if (!fabric.IsInitialized()) { + NotBeforeCollector notBeforeCollector; FabricIndex newFabricIndex = mNextAvailableFabricIndex.Value(); fabric.mFabricIndex = newFabricIndex; - CHIP_ERROR err = fabric.SetFabricInfo(newFabric); - if (err != CHIP_NO_ERROR) + CHIP_ERROR err; + if ((err = fabric.SetFabricInfo(newFabric, ¬BeforeCollector)) != CHIP_NO_ERROR) { fabric.Reset(); return err; @@ -706,8 +774,12 @@ CHIP_ERROR FabricTable::AddNewFabricInner(FabricInfo & newFabric, FabricIndex * } UpdateNextAvailableFabricIndex(); - err = StoreFabricIndexInfo(); - if (err != CHIP_NO_ERROR) + // Update failure of Last Known Good Time is non-fatal. If Last + // Known Good Time is unknown during incoming certificate validation + // for CASE and current time is also unknown, the certificate + // validity policy will see this condition and can act appropriately. + mLastKnownGoodTime.UpdateLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore); + if ((err = StoreFabricIndexInfo()) != CHIP_NO_ERROR) { // Roll everything back. mNextAvailableFabricIndex.SetValue(newFabricIndex); @@ -764,12 +836,12 @@ CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex) { if (mFabricCount == 0) { - ChipLogError(Discovery, "Trying to delete a fabric, but the current fabric count is already 0"); + ChipLogError(FabricProvisioning, "Trying to delete a fabric, but the current fabric count is already 0"); } else { mFabricCount--; - ChipLogProgress(Discovery, "Fabric (0x%x) deleted. Calling OnFabricDeletedFromStorage", + ChipLogProgress(FabricProvisioning, "Fabric (0x%x) deleted. Calling OnFabricDeletedFromStorage", static_cast(fabricIndex)); } @@ -797,7 +869,7 @@ CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage) VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); mStorage = storage; - ChipLogDetail(Discovery, "Init fabric pairing table with server storage"); + ChipLogDetail(FabricProvisioning, "Init fabric pairing table with server storage"); // Load the current fabrics from the storage. This is done here, since ConstFabricIterator // iterator doesn't have mechanism to load fabric info from storage on demand. @@ -811,6 +883,12 @@ CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage) } mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); + // Init failure of Last Known Good Time is non-fatal. If Last Known Good + // Time is unknown during incoming certificate validation for CASE and + // current time is also unknown, the certificate validity policy will see + // this condition and can act appropriately. + mLastKnownGoodTime.Init(storage); + uint8_t buf[IndexInfoTLVMaxSize()]; uint16_t size = sizeof(buf); DefaultStorageKeyAllocator keyAlloc; @@ -877,6 +955,53 @@ void FabricTable::RemoveFabricDelegate(FabricTable::Delegate * delegateToRemove) } } +CHIP_ERROR FabricTable::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + // Find our latest NotBefore time for any installed certificate. + System::Clock::Seconds32 latestNotBefore = System::Clock::Seconds32(0); + for (auto & fabric : mStates) + { + if (!fabric.IsInitialized()) + { + continue; + } + { + ByteSpan rcac; + SuccessOrExit(err = fabric.GetRootCert(rcac)); + chip::System::Clock::Seconds32 rcacNotBefore; + SuccessOrExit(err = Credentials::ExtractNotBeforeFromChipCert(rcac, rcacNotBefore)); + latestNotBefore = rcacNotBefore > latestNotBefore ? rcacNotBefore : latestNotBefore; + } + { + ByteSpan icac; + SuccessOrExit(err = fabric.GetICACert(icac)); + if (!icac.empty()) + { + chip::System::Clock::Seconds32 icacNotBefore; + ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(icac, icacNotBefore)); + latestNotBefore = icacNotBefore > latestNotBefore ? icacNotBefore : latestNotBefore; + } + } + { + ByteSpan noc; + SuccessOrExit(err = fabric.GetNOCCert(noc)); + chip::System::Clock::Seconds32 nocNotBefore; + ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(noc, nocNotBefore)); + latestNotBefore = nocNotBefore > latestNotBefore ? nocNotBefore : latestNotBefore; + } + } + // Pass this to the LastKnownGoodTime object so it can make determination + // of the legality of our new proposed time. + SuccessOrExit(err = mLastKnownGoodTime.SetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, latestNotBefore)); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(FabricProvisioning, "Failed to update Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + return err; +} + namespace { // Increment a fabric index in a way that ensures that it stays in the valid // range [kMinValidFabricIndex, kMaxValidFabricIndex]. @@ -932,7 +1057,6 @@ CHIP_ERROR FabricTable::StoreFabricIndexInfo() const { writer.Put(TLV::AnonymousTag(), fabric.GetFabricIndex()); } - ReturnErrorOnFailure(writer.EndContainer(innerContainerType)); ReturnErrorOnFailure(writer.EndContainer(outerType)); @@ -996,6 +1120,7 @@ CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader) } ReturnErrorOnFailure(reader.ExitContainer(arrayType)); + ReturnErrorOnFailure(reader.ExitContainer(containerType)); ReturnErrorOnFailure(reader.VerifyEndOfContainer()); diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index ff350d687480f1..b88140b28d6018 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -25,6 +25,9 @@ #include #include +#include +#include +#include #include #include #if CHIP_CRYPTO_HSM @@ -202,8 +205,6 @@ class DLL_EXPORT FabricInfo mFabricIndex = kUndefinedFabricIndex; } - CHIP_ERROR SetFabricInfo(FabricInfo & fabric); - /* Generate a compressed peer ID (containing compressed fabric ID) using provided fabric ID, node ID and root public key of the provided root certificate. The generated compressed ID is returned via compressedPeerId output parameter */ @@ -248,6 +249,16 @@ class DLL_EXPORT FabricInfo CHIP_ERROR LoadFromStorage(PersistentStorageDelegate * storage); static CHIP_ERROR DeleteFromStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex); + /** + * Verify the validity of the passed fabric info, and then emplace into + * this. If a policy is passed, enact this for the fabric info validation. + * + * @param fabric fabric to emplace into this + * @param policy validation policy to apply, or nulllptr for none + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR SetFabricInfo(FabricInfo & fabric, Credentials::CertificateValidityPolicy * policy); + void ReleaseCert(MutableByteSpan & cert); void ReleaseOperationalCerts() { @@ -383,6 +394,15 @@ class DLL_EXPORT FabricTable // test-cases CHIP_ERROR AddNewFabricForTest(FabricInfo & newFabric, FabricIndex * outputIndex); + /** + * Update fabric at the specified fabric index with the passed fabric info. + * + * @param fabricIndex index at which to update fabric info + * @param fabricInfo fabric info to validate and copy into the specified index + * @return CHIP_NO_ERROR on success, an appropriate CHIP_ERROR on failure + */ + CHIP_ERROR UpdateFabric(FabricIndex fabricIndex, FabricInfo & fabricInfo); + FabricInfo * FindFabric(Credentials::P256PublicKeySpan rootPubKey, FabricId fabricId); FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex); FabricInfo * FindFabricWithCompressedId(CompressedFabricId fabricId); @@ -391,6 +411,53 @@ class DLL_EXPORT FabricTable CHIP_ERROR AddFabricDelegate(FabricTable::Delegate * delegate); void RemoveFabricDelegate(FabricTable::Delegate * delegate); + /** + * Get the current Last Known Good Time. + * + * @param lastKnownGoodChipEpochTime (out) the current last known good time, if any is known + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR GetLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const + { + return mLastKnownGoodTime.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime); + } + + /** + * Validate that the passed Last Known Good Time is within bounds and then + * store this and write back to storage. Legal values are those which are + * not earlier than firmware build time or any of our stored certificates' + * NotBefore times: + * + * 3.5.6.1. Last Known Good UTC Time + * + * A Node MAY adjust the Last Known Good UTC Time backwards if it + * believes the current Last Known Good UTC Time is incorrect and it has + * a good time value from a trusted source. The Node SHOULD NOT adjust + * the Last Known Good UTC to a time before the later of: + * • The build timestamp of its currently running software image + * • The not-before timestamp of any of its operational certificates + * + * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime); + + /* + * Commit the Last Known Good Time by deleting the fail-safe backup from + * storage. + * + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR CommitLastKnownGoodChipEpochTime() { return mLastKnownGoodTime.CommitLastKnownGoodChipEpochTime(); } + + /* + * Revert the Last Known Good Time to the fail-safe backup value in + * persistence if any exists. + * + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR RevertLastKnownGoodChipEpochTime() { return mLastKnownGoodTime.RevertLastKnownGoodChipEpochTime(); } + uint8_t FabricCount() const { return mFabricCount; } ConstFabricIterator cbegin() const { return ConstFabricIterator(mStates, 0, CHIP_CONFIG_MAX_FABRICS); } @@ -442,6 +509,7 @@ class DLL_EXPORT FabricTable // it can go and is full. Optional mNextAvailableFabricIndex; uint8_t mFabricCount = 0; + LastKnownGoodTime mLastKnownGoodTime; }; } // namespace chip diff --git a/src/credentials/LastKnownGoodTime.cpp b/src/credentials/LastKnownGoodTime.cpp new file mode 100644 index 00000000000000..18644a89546766 --- /dev/null +++ b/src/credentials/LastKnownGoodTime.cpp @@ -0,0 +1,267 @@ +/* + * + * Copyright (c) 2021-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @brief Defines a table of fabrics that have provisioned the device. + */ + +#include "LastKnownGoodTime.h" + +#include +#include +#include +#include + +namespace chip { + +namespace { +// Tags for Last Known Good Time. +constexpr TLV::Tag kLastKnownGoodChipEpochSecondsTag = TLV::ContextTag(0); +constexpr TLV::Tag kFailSafeLastKnownGoodChipEpochSecondsTag = TLV::ContextTag(1); +} // anonymous namespace + +void LastKnownGoodTime::LogTime(const char * msg, System::Clock::Seconds32 chipEpochTime) +{ + char buf[26] = { 0 }; // strlen("00000-000-000T000:000:000") == 25 + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + ChipEpochToCalendarTime(chipEpochTime.count(), year, month, day, hour, minute, second); + snprintf(buf, sizeof(buf), "%04u-%02u-%02uT%02u:%02u:%02u", year, month, day, hour, minute, second); + ChipLogProgress(TimeService, "%s%s", msg, buf); +} + +CHIP_ERROR LastKnownGoodTime::LoadLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime, + Optional & failSafeBackup) const +{ + uint8_t buf[LastKnownGoodTimeTLVMaxSize()]; + uint16_t size = sizeof(buf); + uint32_t seconds; + DefaultStorageKeyAllocator keyAlloc; + ReturnErrorOnFailure(mStorage->SyncGetKeyValue(keyAlloc.LastKnownGoodTimeKey(), buf, size)); + TLV::ContiguousBufferTLVReader reader; + reader.Init(buf, size); + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + TLV::TLVType containerType; + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + ReturnErrorOnFailure(reader.Next(kLastKnownGoodChipEpochSecondsTag)); + ReturnErrorOnFailure(reader.Get(seconds)); + lastKnownGoodChipEpochTime = System::Clock::Seconds32(seconds); + CHIP_ERROR err = reader.Next(); + if (err == CHIP_END_OF_TLV) + { + failSafeBackup = NullOptional; + return CHIP_NO_ERROR; // not an error; this tag is optional + } + VerifyOrReturnError(reader.GetTag() == kFailSafeLastKnownGoodChipEpochSecondsTag, CHIP_ERROR_UNEXPECTED_TLV_ELEMENT); + ReturnErrorOnFailure(reader.Get(seconds)); + failSafeBackup.Emplace(seconds); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LastKnownGoodTime::LoadLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const +{ + Optional failSafeBackup; + return LoadLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, failSafeBackup); +} + +CHIP_ERROR LastKnownGoodTime::StoreLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime, + const Optional & failSafeBackup) const +{ + uint8_t buf[LastKnownGoodTimeTLVMaxSize()]; + TLV::TLVWriter writer; + writer.Init(buf); + TLV::TLVType outerType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); + ReturnErrorOnFailure(writer.Put(kLastKnownGoodChipEpochSecondsTag, lastKnownGoodChipEpochTime.count())); + if (failSafeBackup.HasValue()) + { + ReturnErrorOnFailure(writer.Put(kFailSafeLastKnownGoodChipEpochSecondsTag, failSafeBackup.Value().count())); + } + ReturnErrorOnFailure(writer.EndContainer(outerType)); + const auto length = writer.GetLengthWritten(); + VerifyOrReturnError(CanCastTo(length), CHIP_ERROR_BUFFER_TOO_SMALL); + DefaultStorageKeyAllocator keyAlloc; + ReturnErrorOnFailure(mStorage->SyncSetKeyValue(keyAlloc.LastKnownGoodTimeKey(), buf, static_cast(length))); + return CHIP_NO_ERROR; +} + +CHIP_ERROR LastKnownGoodTime::StoreLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) const +{ + return StoreLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, NullOptional); +} + +CHIP_ERROR LastKnownGoodTime::Init(PersistentStorageDelegate * storage) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + mStorage = storage; + // 3.5.6.1 Last Known Good UTC Time: + // + // "A Node’s initial out-of-box Last Known Good UTC time SHALL be the + // compile-time of the firmware." + System::Clock::Seconds32 buildTime; + SuccessOrExit(err = DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(buildTime)); + System::Clock::Seconds32 storedLastKnownGoodChipEpochTime; + err = LoadLastKnownGoodChipEpochTime(storedLastKnownGoodChipEpochTime); + VerifyOrExit(err == CHIP_NO_ERROR || err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND, ;); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + ChipLogProgress(TimeService, "Last Known Good Time: [unknown]"); + } + else + { + LogTime("Last Known Good Time: ", storedLastKnownGoodChipEpochTime); + } + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND || buildTime > storedLastKnownGoodChipEpochTime) + { + // If we have no value in persistence, or the firmware build time is + // later than the value in persistence, set last known good time to the + // firmware build time and write back. + LogTime("Setting Last Known Good Time to firmware build time ", buildTime); + mLastKnownGoodChipEpochTime.SetValue(buildTime); + SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(buildTime)); + } + else + { + mLastKnownGoodChipEpochTime.SetValue(storedLastKnownGoodChipEpochTime); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(TimeService, "Failed to init Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + return err; +} + +CHIP_ERROR LastKnownGoodTime::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime, + System::Clock::Seconds32 notBefore) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE); + LogTime("Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value()); + LogTime("New proposed Last Known Good Time: ", lastKnownGoodChipEpochTime); + + // Verify that the passed value is not earlier than the firmware build time. + System::Clock::Seconds32 buildTime; + SuccessOrExit(err = DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(buildTime)); + VerifyOrExit(lastKnownGoodChipEpochTime >= buildTime, err = CHIP_ERROR_INVALID_ARGUMENT); + // Verify that the passed value is not earlier than the passed NotBefore time. + VerifyOrExit(lastKnownGoodChipEpochTime >= notBefore, err = CHIP_ERROR_INVALID_ARGUMENT); + + // Passed value is valid. Capture it and write back to persistence. + // + // Note that we are purposefully overwriting any previous last known + // good time that may have been stored as part of a fail-safe context. + // This is intentional: we don't promise not to change last known good + // time during the fail-safe timer. For instance, if the platform has a + // new, better time source, it is legal to capture it. If we do, we should + // both overwrite last known good time and discard the previous value stored + // for fail safe recovery, as together these comprise a transaction. By + // overwriting both, we are fully superseding that transaction with our + // own, which does not to have a revert feature. + SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime)); + mLastKnownGoodChipEpochTime.SetValue(lastKnownGoodChipEpochTime); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(TimeService, "Failed to update Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + else + { + LogTime("Updating Last Known Good Time to ", lastKnownGoodChipEpochTime); + } + return err; +} + +CHIP_ERROR LastKnownGoodTime::UpdateLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE); + LogTime("Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value()); + LogTime("New proposed Last Known Good Time: ", lastKnownGoodChipEpochTime); + if (lastKnownGoodChipEpochTime > mLastKnownGoodChipEpochTime.Value()) + { + LogTime("Current Last Known Good time retained in fail-safe context, updating to ", lastKnownGoodChipEpochTime); + // We have a later timestamp. Advance last known good time and store + // the fail-safe value. + SuccessOrExit( + err = StoreLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, MakeOptional(mLastKnownGoodChipEpochTime.Value()))); + mLastKnownGoodChipEpochTime.SetValue(lastKnownGoodChipEpochTime); + } + else + { + ChipLogProgress(TimeService, "Retaining current Last Known Good Time"); + // Our timestamp is not later. Retain the existing last known good time + // and discard any fail-safe value in persistence. + SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(mLastKnownGoodChipEpochTime.Value())); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(TimeService, "Failed to persist Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + return err; +} + +CHIP_ERROR LastKnownGoodTime::CommitLastKnownGoodChipEpochTime() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE); + LogTime("Committing Last Known Good Time to storage: ", mLastKnownGoodChipEpochTime.Value()); + // Writing with no fail-safe backup removes the fail-safe backup from + // storage, thus committing the new Last Known Good Time. + SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(mLastKnownGoodChipEpochTime.Value())); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(TimeService, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + return err; +} + +CHIP_ERROR LastKnownGoodTime::RevertLastKnownGoodChipEpochTime() +{ + CHIP_ERROR err = CHIP_NO_ERROR; + System::Clock::Seconds32 lastKnownGoodChipEpochTime; + Optional failSafeBackup; + VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE); + LogTime("Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value()); + SuccessOrExit(err = LoadLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, failSafeBackup)); + if (!failSafeBackup.HasValue()) + { + ChipLogProgress(TimeService, "No fail safe Last Known Good Time to revert to"); + return CHIP_NO_ERROR; // if there's no value to revert to, we are done + } + LogTime("Fail safe Last Known Good Time: ", failSafeBackup.Value()); + SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(failSafeBackup.Value())); + mLastKnownGoodChipEpochTime.SetValue(failSafeBackup.Value()); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(TimeService, "Failed to persist Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format()); + } + else + { + ChipLogProgress(TimeService, "Reverted Last Known Good Time to fail safe value"); + } + return err; +} + +} // namespace chip diff --git a/src/credentials/LastKnownGoodTime.h b/src/credentials/LastKnownGoodTime.h new file mode 100644 index 00000000000000..9c0798291188c9 --- /dev/null +++ b/src/credentials/LastKnownGoodTime.h @@ -0,0 +1,171 @@ +/* + * + * Copyright (c) 2021-2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @brief Defines a table of fabrics that have provisioned the device. + */ + +#pragma once + +#include +#include +#include +#include + +namespace chip { + +class LastKnownGoodTime +{ +public: + LastKnownGoodTime() {} + + /** + * Initialize Last Known Good Time to the later of firmware build time or + * last known good time persisted in storage. Persist the selected value + * if this differs from the value in storage or no value is yet persisted. + * + * @param storage storage delegate to persist Last Known Good Time + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR Init(PersistentStorageDelegate * storage); + + /** + * Get the current Last Known Good Time. + * + * @param lastKnownGoodChipEpochTime (out) the current last known good time, if any is known + * &return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR GetLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const + { + VerifyOrReturnError(mLastKnownGoodChipEpochTime.HasValue(), CHIP_ERROR_INCORRECT_STATE); + lastKnownGoodChipEpochTime = mLastKnownGoodChipEpochTime.Value(); + return CHIP_NO_ERROR; + } + + /** + * Validate that the passed Last Known Good Time is within bounds and then + * store this and write back to storage. Legal values are those which are + * not earlier than firmware build time or any of our stored certificates' + * NotBefore times: + * + * 3.5.6.1. Last Known Good UTC Time + * + * A Node MAY adjust the Last Known Good UTC Time backwards if it + * believes the current Last Known Good UTC Time is incorrect and it has + * a good time value from a trusted source. The Node SHOULD NOT adjust + * the Last Known Good UTC to a time before the later of: + * • The build timestamp of its currently running software image + * • The not-before timestamp of any of its operational certificates + * + * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch + * @param notBefore the latest NotBefore time of all installed certificates + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime, + System::Clock::Seconds32 notBefore); + + /** + * Update the Last Known Good Time to the later of the current value and + * the passed value and persist to storage. If the value is changed, also + * store the current value for fail-safe recovery. + * + * We can only support storage of a single fail-safe recovery value, so + * for nodes and fabric additions where fail-safe recovery is required, this + * should only be called from within a fail-safe context. + * + * @param lastKnownGoodChipEpochTime Last Known Good Time in seconds since CHIP epoch + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR UpdateLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime); + + /* + * Commit the Last Known Good Time by deleting the fail-safe backup from + * storage. + * + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR CommitLastKnownGoodChipEpochTime(); + + /* + * Revert the Last Known Good Time to the fail-safe backup value in + * persistence if any exists. + * + * @return CHIP_NO_ERROR on success, else an appopriate CHIP_ERROR + */ + CHIP_ERROR RevertLastKnownGoodChipEpochTime(); + +private: + static constexpr size_t LastKnownGoodTimeTLVMaxSize() + { + // We have Last Known Good Time and, optionally, a previous Last known + // good time for fail safe cleanup. + return TLV::EstimateStructOverhead(sizeof(uint32_t), sizeof(uint32_t)); + } + + /** + * Log the message, appending the passed CHIP epoch time in ISO8601 format. + * + * @param msg message to log with ISO8601 time appended + * @param chipEpochTime time in seconds from the CHIP epoch + */ + void LogTime(const char * msg, System::Clock::Seconds32 chipEpochTime); + + /** + * Load the Last Known Good Time from storage and, optionally, a fail-safe + * value to fall back to if any exists. + * + * @param lastKnownGoodChipEpochTime (out) Last Known Good Time as seconds from CHIP epoch + * @param failSafeBackup (out) an optional Fail Safe context last known good time value to fall back to, also in seconds from + * CHIP epoch from CHIP epoch + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR LoadLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime, + Optional & failSafeBackup) const; + + /** + * Load the Last Known Good Time from storage. + * + * @param lastKnownGoodChipEpochTime (out) Last Known Good Time as seconds from CHIP epoch + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR LoadLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const; + + /** + * Store the Last Known Good Time to storage, and optionally, a fail-safe + * value to fall back to if the fail safe timer expires. + * + * @param lastKnownGoodChipEpochTime Last Known Good Time as seconds from CHIP epoch + * @param failSafeBackup fail safe backup of the previous Last Known Good Time + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR StoreLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime, + const Optional & failSafeBackup) const; + + /** + * Store the Last Known Good Time to storage. This overload also clears + * the fail safe Last Known Good Time from storage. + * + * @param lastKnownGoodChipEpochTime Last Known Good Time as seconds from CHIP epoch + * @return CHIP_NO_ERROR on success, else an appropriate CHIP_ERROR + */ + CHIP_ERROR StoreLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) const; + + PersistentStorageDelegate * mStorage = nullptr; + Optional mLastKnownGoodChipEpochTime; +}; + +} // namespace chip diff --git a/src/credentials/examples/LastKnownGoodTimeCertificateValidityPolicyExample.h b/src/credentials/examples/LastKnownGoodTimeCertificateValidityPolicyExample.h new file mode 100644 index 00000000000000..408019dbc51d56 --- /dev/null +++ b/src/credentials/examples/LastKnownGoodTimeCertificateValidityPolicyExample.h @@ -0,0 +1,64 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace chip { +namespace Credentials { + +class LastKnownGoodTimeCertificateValidityPolicyExample : public CertificateValidityPolicy +{ +public: + ~LastKnownGoodTimeCertificateValidityPolicyExample() {} + + /** + * @brief + * + * This certificate validity policy will validate NotBefore / NotAfter if + * current time is known and also validates NotAfter if only Last Known + * Good Time is known. + * + * This provides an example for enforcing certificate expiration on nodes + * where no current time source is available. + * + * @param cert CHIP Certificate for which we are evaluating validity + * @param depth the depth of the certificate in the chain, where the leaf is at depth 0 + * @return CHIP_NO_ERROR if CHIPCert should accept the certificate; an appropriate CHIP_ERROR if it should be rejected + */ + CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) override + { + switch (result) + { + case CertificateValidityResult::kValid: + case CertificateValidityResult::kNotExpiredAtLastKnownGoodTime: + case CertificateValidityResult::kTimeUnknown: + return CHIP_NO_ERROR; + case CertificateValidityResult::kNotYetValid: + return CHIP_ERROR_CERT_NOT_VALID_YET; + case CertificateValidityResult::kExpired: + case CertificateValidityResult::kExpiredAtLastKnownGoodTime: + return CHIP_ERROR_CERT_EXPIRED; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + } +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/examples/StrictCertificateValidityPolicyExample.h b/src/credentials/examples/StrictCertificateValidityPolicyExample.h new file mode 100644 index 00000000000000..a8d63aba99e93e --- /dev/null +++ b/src/credentials/examples/StrictCertificateValidityPolicyExample.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (c) 2021 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace chip { +namespace Credentials { + +class StrictCertificateValidityPolicyExample : public CertificateValidityPolicy +{ +public: + ~StrictCertificateValidityPolicyExample() {} + + /** + * @brief + * + * This certificate validity policy is strict in that it rejects all + * certificates if any of wall clock time or last known good time show + * them to be invalid. This policy also rejects certificates if time + * is unknown. + * + * @param cert CHIP Certificate for which we are evaluating validity + * @param depth the depth of the certificate in the chain, where the leaf is at depth 0 + * @return CHIP_NO_ERROR if CHIPCert should accept the certificate; an appropriate CHIP_ERROR if it should be rejected + */ + CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) override + { + switch (result) + { + case CertificateValidityResult::kValid: + case CertificateValidityResult::kNotExpiredAtLastKnownGoodTime: + return CHIP_NO_ERROR; + case CertificateValidityResult::kNotYetValid: + return CHIP_ERROR_CERT_NOT_VALID_YET; + case CertificateValidityResult::kExpiredAtLastKnownGoodTime: + case CertificateValidityResult::kTimeUnknown: + case CertificateValidityResult::kExpired: + return CHIP_ERROR_CERT_EXPIRED; + default: + return CHIP_ERROR_INVALID_ARGUMENT; + } + } +}; + +} // namespace Credentials +} // namespace chip diff --git a/src/credentials/tests/CHIPCert_test_vectors.h b/src/credentials/tests/CHIPCert_test_vectors.h index 08085e66a43af4..30f36c8cd46455 100644 --- a/src/credentials/tests/CHIPCert_test_vectors.h +++ b/src/credentials/tests/CHIPCert_test_vectors.h @@ -27,6 +27,7 @@ #pragma once #include +#include #include namespace chip { diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp index 9f4ea1d33a1f27..bf9aae14a2394c 100644 --- a/src/credentials/tests/TestChipCert.cpp +++ b/src/credentials/tests/TestChipCert.cpp @@ -25,6 +25,8 @@ */ #include +#include +#include #include #include #include @@ -49,9 +51,6 @@ enum kStandardCertsCount = 3, }; -static const BitFlags sIgnoreNotBeforeFlag(CertValidateFlags::kIgnoreNotBefore); -static const BitFlags sIgnoreNotAfterFlag(CertValidateFlags::kIgnoreNotAfter); - static const BitFlags sNullDecodeFlag; static const BitFlags sGenTBSHashFlag(CertDecodeFlags::kGenerateTBSHash); static const BitFlags sTrustAnchorFlag(CertDecodeFlags::kIsTrustAnchor); @@ -119,19 +118,39 @@ static CHIP_ERROR LoadTestCertSet01(ChipCertificateSet & certSet) return err; } -static CHIP_ERROR SetEffectiveTime(ValidationContext & validContext, uint16_t year, uint8_t mon, uint8_t day, uint8_t hour = 0, - uint8_t min = 0, uint8_t sec = 0) +static CHIP_ERROR SetCurrentTime(ValidationContext & validContext, uint16_t year, uint8_t mon, uint8_t day, uint8_t hour = 0, + uint8_t min = 0, uint8_t sec = 0) +{ + ASN1UniversalTime currentTime; + + currentTime.Year = year; + currentTime.Month = mon; + currentTime.Day = day; + currentTime.Hour = hour; + currentTime.Minute = min; + currentTime.Second = sec; + + return validContext.SetEffectiveTimeFromAsn1Time(currentTime); +} + +static CHIP_ERROR SetLastKnownGoodTime(ValidationContext & validContext, uint16_t year, uint8_t mon, uint8_t day, uint8_t hour = 0, + uint8_t min = 0, uint8_t sec = 0) { - ASN1UniversalTime effectiveTime; + ASN1UniversalTime lastKnownGoodTime; - effectiveTime.Year = year; - effectiveTime.Month = mon; - effectiveTime.Day = day; - effectiveTime.Hour = hour; - effectiveTime.Minute = min; - effectiveTime.Second = sec; + lastKnownGoodTime.Year = year; + lastKnownGoodTime.Month = mon; + lastKnownGoodTime.Day = day; + lastKnownGoodTime.Hour = hour; + lastKnownGoodTime.Minute = min; + lastKnownGoodTime.Second = sec; + + return validContext.SetEffectiveTimeFromAsn1Time(lastKnownGoodTime); +} - return ASN1ToChipEpochTime(effectiveTime, validContext.mEffectiveTime); +static void ClearTimeSource(ValidationContext & validContext) +{ + validContext.mEffectiveTime = EffectiveTime{}; } static void TestChipCert_ChipToX509(nlTestSuite * inSuite, void * inContext) @@ -421,12 +440,11 @@ static void TestChipCert_CertValidation(nlTestSuite * inSuite, void * inContext) // Initialize the validation context. validContext.Reset(); - err = SetEffectiveTime(validContext, 2021, 1, 1); + err = SetCurrentTime(validContext, 2021, 1, 1); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kClientAuth); - validContext.mValidateFlags.SetRaw(testCase.mValidateFlags); validContext.mRequiredCertType = testCase.mRequiredCertType; // Locate the subject DN and key id that will be used as input the FindValidCert() method. @@ -469,63 +487,464 @@ static void TestChipCert_CertValidTime(nlTestSuite * inSuite, void * inContext) validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kClientAuth); - // Before certificate validity period. - err = SetEffectiveTime(validContext, 2020, 1, 3); + Credentials::StrictCertificateValidityPolicyExample strictCertificateValidityPolicy; + Credentials::LastKnownGoodTimeCertificateValidityPolicyExample lastKnownGoodTimeValidityPolicy; + + // No time source available. + ClearTimeSource(validContext); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - // TODO - enable check for certificate validity dates - // err = certSet.ValidateCert(certSet.GetLastCert(), validContext); - // NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); - // 1 second before validity period. - err = SetEffectiveTime(validContext, 2020, 10, 15, 14, 23, 42); + // Current time before certificate validity period. + err = SetCurrentTime(validContext, 2020, 1, 3); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + + // Current time 1 second before validity period. + err = SetCurrentTime(validContext, 2020, 10, 15, 14, 23, 42); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + + // Current time 1st second of validity period. + err = SetCurrentTime(validContext, 2020, 10, 15, 14, 23, 43); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - // TODO - enable check for certificate validity dates - // err = certSet.ValidateCert(certSet.GetLastCert(), validContext); - // NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); - // 1st second of validity period. - err = SetEffectiveTime(validContext, 2020, 10, 15, 14, 23, 43); + // Current time within validity period. + err = SetCurrentTime(validContext, 2022, 02, 23, 12, 30, 01); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - // Validity period. - err = SetEffectiveTime(validContext, 2022, 02, 23, 12, 30, 01); + // Current time at last second of validity period. + err = SetCurrentTime(validContext, 2040, 10, 15, 14, 23, 42); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - // Last second of validity period. - err = SetEffectiveTime(validContext, 2040, 10, 15, 14, 23, 42); + // Current time at 1 second after end of certificate validity period. + err = SetCurrentTime(validContext, 2040, 10, 15, 14, 23, 43); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + + // Current time after end of certificate validity period. + err = SetCurrentTime(validContext, 2042, 4, 25, 0, 0, 0); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); - // 1 second after end of certificate validity period. - err = SetEffectiveTime(validContext, 2040, 10, 15, 14, 23, 43); + // Last known good time before certificate validity period. + // We can't invalidate based on NotBefore with Last Known Good Time. + // Hence, we expect CHIP_NO_ERROR. + err = SetLastKnownGoodTime(validContext, 2020, 1, 3); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Last known good time 1 second before certificate validity period. + // We can't invalidate based on NotBefore with Last Known Good Time. + // Hence, we expect CHIP_NO_ERROR. + err = SetLastKnownGoodTime(validContext, 2020, 10, 15, 14, 23, 42); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Last Known Good Time 1st second of validity period. + err = SetLastKnownGoodTime(validContext, 2020, 10, 15, 14, 23, 43); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Last Known Good Time within validity period. + err = SetLastKnownGoodTime(validContext, 2022, 02, 23, 12, 30, 01); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Last Known Good Time at last second of validity period. + err = SetLastKnownGoodTime(validContext, 2040, 10, 15, 14, 23, 42); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Last Known Good Time at 1 second after end of certificate validity period. + err = SetLastKnownGoodTime(validContext, 2040, 10, 15, 14, 23, 43); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); - // After end of certificate validity period. - err = SetEffectiveTime(validContext, 2042, 4, 25, 0, 0, 0); + // Last Known Good Time after end of certificate validity period. + err = SetLastKnownGoodTime(validContext, 2042, 4, 25, 0, 0, 0); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Strict policy + validContext.mValidityPolicy = &strictCertificateValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimeValidityPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + + certSet.Release(); +} - // Ignore 'not before' time. - validContext.mValidateFlags.Set(sIgnoreNotBeforeFlag); - err = SetEffectiveTime(validContext, 2020, 4, 23, 23, 59, 59); +class AlwaysAcceptValidityPolicy : public CertificateValidityPolicy +{ +public: + ~AlwaysAcceptValidityPolicy() {} + + CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) override + { + return CHIP_NO_ERROR; + } +}; + +class AlwaysRejectValidityPolicy : public CertificateValidityPolicy +{ +public: + ~AlwaysRejectValidityPolicy() {} + + CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth, + CertificateValidityResult result) override + { + return CHIP_ERROR_CERT_EXPIRED; + } +}; + +static void TestChipCert_CertValidityPolicyInjection(nlTestSuite * inSuite, void * inContext) +{ + CHIP_ERROR err; + ChipCertificateSet certSet; + ValidationContext validContext; + StrictCertificateValidityPolicyExample strictPolicy; + LastKnownGoodTimeCertificateValidityPolicyExample lastKnownGoodTimePolicy; + AlwaysAcceptValidityPolicy alwaysAcceptPolicy; + AlwaysRejectValidityPolicy alwaysRejectPolicy; + + err = certSet.Init(kStandardCertsCount); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = LoadTestCertSet01(certSet); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + validContext.Reset(); + validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); + validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); + validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kClientAuth); + + // Current time unknown. + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimePolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Curent time before certificate validity period. + err = SetCurrentTime(validContext, 2020, 1, 3); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimePolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_NOT_VALID_YET); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Last known good time before certificate validity period. + err = SetLastKnownGoodTime(validContext, 2020, 1, 3); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Current time during validity period + err = SetCurrentTime(validContext, 2022, 02, 23, 12, 30, 01); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Last Known Good Time during validity period + err = SetLastKnownGoodTime(validContext, 2022, 02, 23, 12, 30, 01); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - // Ignore 'not after' time. - validContext.mValidateFlags.Set(sIgnoreNotAfterFlag); - err = SetEffectiveTime(validContext, 2042, 5, 25, 0, 0, 0); + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); - err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimePolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Current time after end of certificate validity period. + err = SetCurrentTime(validContext, 2042, 4, 25, 0, 0, 0); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimePolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); + + // Last Known Good Time after end of certificate validity period. + err = SetLastKnownGoodTime(validContext, 2042, 4, 25, 0, 0, 0); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Default policy + validContext.mValidityPolicy = nullptr; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Strict policy + validContext.mValidityPolicy = &strictPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Last Known Good Time policy + validContext.mValidityPolicy = &lastKnownGoodTimePolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_CERT_EXPIRED); + // Always accept policy + validContext.mValidityPolicy = &alwaysAcceptPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + // Always reject policy + validContext.mValidityPolicy = &alwaysRejectPolicy; + err = certSet.ValidateCert(certSet.GetLastCert(), validContext); + NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR); certSet.Release(); } @@ -632,7 +1051,7 @@ static void TestChipCert_CertUsage(nlTestSuite * inSuite, void * inContext) validContext.mRequiredKeyUsages = sUsageTestCases[i].mRequiredKeyUsages; validContext.mRequiredKeyPurposes = sUsageTestCases[i].mRequiredKeyPurposes; - err = SetEffectiveTime(validContext, 2020, 10, 16); + err = SetCurrentTime(validContext, 2020, 10, 16); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); err = certSet.ValidateCert(&certSet.GetCertSet()[sUsageTestCases[i].mCertIndex], validContext); @@ -1064,7 +1483,7 @@ static void TestChipCert_VerifyGeneratedCerts(nlTestSuite * inSuite, void * inCo ValidationContext validContext; validContext.Reset(); - NL_TEST_ASSERT(inSuite, SetEffectiveTime(validContext, 2022, 1, 1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, SetCurrentTime(validContext, 2022, 1, 1) == CHIP_NO_ERROR); validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kClientAuth); @@ -1130,7 +1549,7 @@ static void TestChipCert_VerifyGeneratedCertsNoICA(nlTestSuite * inSuite, void * ValidationContext validContext; validContext.Reset(); - NL_TEST_ASSERT(inSuite, SetEffectiveTime(validContext, 2022, 1, 1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, SetCurrentTime(validContext, 2022, 1, 1) == CHIP_NO_ERROR); validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kClientAuth); @@ -1542,6 +1961,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test CHIP Certificate Distinguish Name", TestChipCert_ChipDN), NL_TEST_DEF("Test CHIP Certificate Validation", TestChipCert_CertValidation), NL_TEST_DEF("Test CHIP Certificate Validation time", TestChipCert_CertValidTime), + NL_TEST_DEF("Test CHIP Certificate Validity Policy injection", TestChipCert_CertValidityPolicyInjection), NL_TEST_DEF("Test CHIP Certificate Usage", TestChipCert_CertUsage), NL_TEST_DEF("Test CHIP Certificate Type", TestChipCert_CertType), NL_TEST_DEF("Test CHIP Certificate ID", TestChipCert_CertId), diff --git a/src/credentials/tests/TestFabricTable.cpp b/src/credentials/tests/TestFabricTable.cpp index 90f29c3ab98c81..6edcfd20c920e8 100644 --- a/src/credentials/tests/TestFabricTable.cpp +++ b/src/credentials/tests/TestFabricTable.cpp @@ -28,8 +28,12 @@ #include +#include +#include #include +#include #include +#include #include using namespace chip; @@ -73,6 +77,306 @@ void TestGetCompressedFabricID(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, compressedId.GetNodeId() == 0xdeed); } +void TestLastKnownGoodTimeInit(nlTestSuite * inSuite, void * inContext) +{ + // Fabric table init should init Last Known Good Time to the firmware build time. + FabricTable fabricTable; + chip::TestPersistentStorageDelegate testStorage; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + System::Clock::Seconds32 lastKnownGoodChipEpochTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime) == CHIP_NO_ERROR); + System::Clock::Seconds32 firmwareBuildTime; + NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(firmwareBuildTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodChipEpochTime == firmwareBuildTime); +} + +/** + * Load a single test fabric with with the Root01:ICA01:Node01_01 identity. + */ +static CHIP_ERROR LoadTestFabric(nlTestSuite * inSuite, FabricTable & fabricTable) +{ + Crypto::P256SerializedKeypair opKeysSerialized; + Crypto::P256Keypair opKey; + FabricInfo fabricInfo; + FabricIndex fabricIndex; + memcpy((uint8_t *) (opKeysSerialized), TestCerts::sTestCert_Node01_01_PublicKey, TestCerts::sTestCert_Node01_01_PublicKey_Len); + memcpy((uint8_t *) (opKeysSerialized) + TestCerts::sTestCert_Node01_01_PublicKey_Len, TestCerts::sTestCert_Node01_01_PrivateKey, + TestCerts::sTestCert_Node01_01_PrivateKey_Len); + NL_TEST_ASSERT(inSuite, + opKeysSerialized.SetLength(TestCerts::sTestCert_Node01_02_PublicKey_Len + + TestCerts::sTestCert_Node01_02_PrivateKey_Len) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opKey.Deserialize(opKeysSerialized) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricInfo.SetOperationalKeypair(&opKey) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + fabricInfo.SetRootCert(ByteSpan(TestCerts::sTestCert_Root01_Chip, TestCerts::sTestCert_Root01_Chip_Len)) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + fabricInfo.SetICACert(ByteSpan(TestCerts::sTestCert_ICA01_Chip, TestCerts::sTestCert_ICA01_Chip_Len)) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + fabricInfo.SetNOCCert(ByteSpan(TestCerts::sTestCert_Node01_01_Chip, TestCerts::sTestCert_Node01_01_Chip_Len)) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricTable.AddNewFabric(fabricInfo, &fabricIndex) == CHIP_NO_ERROR); + return CHIP_NO_ERROR; +} + +void TestUpdateLastKnownGoodTime(nlTestSuite * inSuite, void * inContext) +{ + // Adding a fabric should advance Last Known Good Time if any certificate's + // NotBefore time is later than the build time, and else should leave it + // to the initial build time value. + + // Test certs all have this NotBefore: Oct 15 14:23:43 2020 GMT + const ASN1::ASN1UniversalTime asn1Expected = { 2020, 10, 15, 14, 23, 43 }; + uint32_t testCertNotBeforeSeconds; + NL_TEST_ASSERT(inSuite, Credentials::ASN1ToChipEpochTime(asn1Expected, testCertNotBeforeSeconds) == CHIP_NO_ERROR); + System::Clock::Seconds32 testCertNotBeforeTime = System::Clock::Seconds32(testCertNotBeforeSeconds); + + // Test that certificate NotBefore times that are before the Firmware build time + // do not advance Last Known Good Time. + System::Clock::Seconds32 afterNotBeforeBuildTimes[] = { System::Clock::Seconds32(testCertNotBeforeTime.count() + 1), + System::Clock::Seconds32(testCertNotBeforeTime.count() + 1000), + System::Clock::Seconds32(testCertNotBeforeTime.count() + 1000000) }; + for (auto buildTime : afterNotBeforeBuildTimes) + { + // Set build time to the desired value. + NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR); + chip::TestPersistentStorageDelegate testStorage; + { + // Initialize a fabric table. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Read back Last Known Good Time, which will have been initialized to firmware build time. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + + // Load a test fabric + NL_TEST_ASSERT(inSuite, LoadTestFabric(inSuite, fabricTable) == CHIP_NO_ERROR); + + // Read Last Known Good Time and verify that it hasn't moved forward. + // This test case was written after the test certs' NotBefore time and we + // are using a configuration manager that should reflect a real build time. + // Therefore, we expect that build time is after NotBefore and so Last + // Known Good Time will be set to the later of these, build time, even + // after installing the new fabric. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + } + { + // Test reloading last known good time from persistence. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Verify that last known good time was retained. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + + // Verify that we can call the fail-safe roll back interface. + // Because we didn't advance Last Known Good Time, this should be a + // no-op. + NL_TEST_ASSERT(inSuite, fabricTable.RevertLastKnownGoodChipEpochTime() == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + } + { + // Reload again from persistence to verify the fail-safe rollback + // left the time intact. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + } + } + + System::Clock::Seconds32 beforeNotBeforeBuildTimes[] = { testCertNotBeforeTime, + System::Clock::Seconds32(testCertNotBeforeTime.count() - 1), + System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000), + System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000000) }; + // Test that certificate NotBefore times that are at or after the Firmware + // build time do result in Last Known Good Times set to these. Then test + // that we can do a fail-safe roll back. + for (auto buildTime : beforeNotBeforeBuildTimes) + { + // Set build time to the desired value. + NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR); + chip::TestPersistentStorageDelegate testStorage; + { + // Initialize a fabric table. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Load a test fabric + NL_TEST_ASSERT(inSuite, LoadTestFabric(inSuite, fabricTable) == CHIP_NO_ERROR); + + // Read Last Known Good Time and verify that it is now set to the certificate + // NotBefore time, as this should be at or after firmware build time. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + } + { + // Test reloading last known good time from persistence. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Verify that last known good time was retained. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + + // Verify that we can do a fail-safe roll back. + NL_TEST_ASSERT(inSuite, fabricTable.RevertLastKnownGoodChipEpochTime() == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + } + { + // Reload again from persistence to verify the fail-safe rollback + // persisted the reverted time. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime); + } + } + // Test that certificate NotBefore times that are at or after the Firmware + // build time do result in Last Known Good Times set to these. Then test + // that we can commit these to storage. Attempted fail-safe roll back after + // commit will be a no-op. + for (auto buildTime : beforeNotBeforeBuildTimes) + { + // Set build time to the desired value. + NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR); + chip::TestPersistentStorageDelegate testStorage; + { + // Initialize a fabric table. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Load a test fabric + NL_TEST_ASSERT(inSuite, LoadTestFabric(inSuite, fabricTable) == CHIP_NO_ERROR); + + // Read Last Known Good Time and verify that it is now set to the certificate + // NotBefore time, as this should be at or after firmware build time. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + + // Commit to storage. + NL_TEST_ASSERT(inSuite, fabricTable.CommitLastKnownGoodChipEpochTime() == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + + // Verify that we can do a no-op fail-safe roll back. + NL_TEST_ASSERT(inSuite, fabricTable.RevertLastKnownGoodChipEpochTime() == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + } + { + // Test reloading last known good time from persistence. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Verify that last known good time was retained. + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime); + } + } +} + +void TestSetLastKnownGoodTime(nlTestSuite * inSuite, void * inContext) +{ + // It is desirable for nodes to set Last Known Good Time whenever a good + // time source is available, including cases where this would set the time + // backward. However, it is impermissible to set last known good time to + // any time before the Firmware Build time or the latest NotBefore of any + // installed certificate. + + // Test certs all have this NotBefore: Oct 15 14:23:43 2020 GMT + const ASN1::ASN1UniversalTime asn1Expected = { 2020, 10, 15, 14, 23, 43 }; + uint32_t testCertNotBeforeSeconds; + NL_TEST_ASSERT(inSuite, Credentials::ASN1ToChipEpochTime(asn1Expected, testCertNotBeforeSeconds) == CHIP_NO_ERROR); + System::Clock::Seconds32 testCertNotBeforeTime = System::Clock::Seconds32(testCertNotBeforeSeconds); + + // Iterate over two cases: one with build time prior to our certificates' NotBefore, one with build time after. + System::Clock::Seconds32 testCaseFirmwareBuildTimes[] = { System::Clock::Seconds32(testCertNotBeforeTime.count() - 100000), + System::Clock::Seconds32(testCertNotBeforeTime.count() + 100000) }; + + for (auto buildTime : testCaseFirmwareBuildTimes) + { + // Set build time to the desired value. + NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR); + chip::TestPersistentStorageDelegate testStorage; + System::Clock::Seconds32 newTime; + { + // Initialize a fabric table. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + + // Load a test fabric + NL_TEST_ASSERT(inSuite, LoadTestFabric(inSuite, fabricTable) == CHIP_NO_ERROR); + + // Verify the Last Known Good Time matches our expected initial value. + System::Clock::Seconds32 initialLastKnownGoodTime = + buildTime > testCertNotBeforeTime ? buildTime : testCertNotBeforeTime; + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime); + + // Read Last Known Good Time and verify that it hasn't moved forward, since + // build time is later than the test certs' NotBefore times. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime); + + // Attempt to set a Last Known Good Time that is before the firmware build time. This should fail. + newTime = System::Clock::Seconds32(buildTime.count() - 1000); + NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) != CHIP_NO_ERROR); + + // Verify Last Known Good Time is unchanged. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime); + + // Attempt to set a Last Known Good Time that is before our certificates' NotBefore times. This should fail. + newTime = System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000); + NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) != CHIP_NO_ERROR); + + // Verify Last Known Good Time is unchanged. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime); + + // Attempt to set a Last Known Good Time that at our current value. + NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + + // Verify Last Known Good Time is unchanged. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime); + + // Attempt to set Last Known Good Times that is after our current value. + newTime = System::Clock::Seconds32(initialLastKnownGoodTime.count() + 1000); + NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) == CHIP_NO_ERROR); + + // Verify Last Known Good Time is updated. + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == newTime); + } + { + // Verify that Last Known Good Time was persisted. + + // Initialize a new fabric table from persistence. + FabricTable fabricTable; + NL_TEST_ASSERT(inSuite, fabricTable.Init(&testStorage) == CHIP_NO_ERROR); + System::Clock::Seconds32 lastKnownGoodTime; + NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, lastKnownGoodTime == newTime); + } + } +} + // Test Suite /** @@ -81,7 +385,10 @@ void TestGetCompressedFabricID(nlTestSuite * inSuite, void * inContext) // clang-format off static const nlTest sTests[] = { - NL_TEST_DEF("Compressed Fabric ID", TestGetCompressedFabricID), + NL_TEST_DEF("Get Compressed Fabric ID", TestGetCompressedFabricID), + NL_TEST_DEF("Last Known Good Time Init", TestLastKnownGoodTimeInit), + NL_TEST_DEF("Update Last Known Good Time", TestUpdateLastKnownGoodTime), + NL_TEST_DEF("Set Last Known Good Time", TestSetLastKnownGoodTime), NL_TEST_SENTINEL() }; // clang-format on @@ -104,6 +411,7 @@ static nlTestSuite sSuite = */ int TestFabricTable_Setup(void * inContext) { + DeviceLayer::SetConfigurationMgr(&DeviceLayer::ConfigurationManagerImpl::GetDefaultInstance()); return chip::Platform::MemoryInit() == CHIP_NO_ERROR ? SUCCESS : FAILURE; } diff --git a/src/include/platform/BuildTime.h b/src/include/platform/BuildTime.h new file mode 100644 index 00000000000000..8015a534778a19 --- /dev/null +++ b/src/include/platform/BuildTime.h @@ -0,0 +1,71 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Adapted from https://stackoverflow.com/questions/11697820/how-to-use-date-and-time-predefined-macros-in-as-two-integers-then-stri + +#pragma once + +#include + +// Expects input as provided by __DATE__ and __TIME__ macros. +// +// Example of __DATE__ string: "Jul 27 2012" +// Example of __DATE__ string: "Jul 2 2012" -> note leading whitespace for single-digit day of month +// Example of __TIME__ string: "21:06:19" + +#define COMPUTE_BUILD_YEAR(_date) \ + (((_date)[7] - '0') * 1000 + ((_date)[8] - '0') * 100 + ((_date)[9] - '0') * 10 + ((_date)[10] - '0')) + +#define COMPUTE_BUILD_DAY(_date) ((((_date)[4] >= '0') ? ((_date)[4] - '0') * 10 : 0) + ((_date)[5] - '0')) + +#define BUILD_MONTH_IS_JAN(_date) ((_date)[0] == 'J' && (_date)[1] == 'a') +#define BUILD_MONTH_IS_FEB(_date) ((_date)[0] == 'F') +#define BUILD_MONTH_IS_MAR(_date) ((_date)[0] == 'M' && (_date)[1] == 'a' && (_date)[2] == 'r') +#define BUILD_MONTH_IS_APR(_date) ((_date)[0] == 'A' && (_date)[1] == 'p') +#define BUILD_MONTH_IS_MAY(_date) ((_date)[0] == 'M' && (_date)[1] == 'a' && (_date)[2] == 'y') +#define BUILD_MONTH_IS_JUN(_date) ((_date)[0] == 'J' && (_date)[1] == 'u' && (_date)[2] == 'n') +#define BUILD_MONTH_IS_JUL(_date) ((_date)[0] == 'J' && (_date)[1] == 'u' && (_date)[2] == 'l') +#define BUILD_MONTH_IS_AUG(_date) ((_date)[0] == 'A' && (_date)[1] == 'u') +#define BUILD_MONTH_IS_SEP(_date) ((_date)[0] == 'S') +#define BUILD_MONTH_IS_OCT(_date) ((_date)[0] == 'O') +#define BUILD_MONTH_IS_NOV(_date) ((_date)[0] == 'N') +#define BUILD_MONTH_IS_DEC(_date) ((_date)[0] == 'D') + +#define COMPUTE_BUILD_MONTH(_date) \ + ((BUILD_MONTH_IS_JAN(_date)) \ + ? 1 \ + : (BUILD_MONTH_IS_FEB(_date)) ? 2 \ + : (BUILD_MONTH_IS_MAR(_date)) ? 3 \ + : (BUILD_MONTH_IS_APR(_date)) ? 4 \ + : (BUILD_MONTH_IS_MAY(_date)) \ + ? 5 \ + : (BUILD_MONTH_IS_JUN(_date)) ? 6 \ + : (BUILD_MONTH_IS_JUL(_date)) ? 7 \ + : (BUILD_MONTH_IS_AUG(_date)) \ + ? 8 \ + : (BUILD_MONTH_IS_SEP(_date)) ? 9 \ + : (BUILD_MONTH_IS_OCT(_date)) ? 10 \ + : (BUILD_MONTH_IS_NOV(_date)) \ + ? 11 \ + : (BUILD_MONTH_IS_DEC(_date)) ? 12 : /* error default */ 99) + +#define COMPUTE_BUILD_HOUR(_time) (((_time)[0] - '0') * 10 + (_time)[1] - '0') +#define COMPUTE_BUILD_MIN(_time) (((_time)[3] - '0') * 10 + (_time)[4] - '0') +#define COMPUTE_BUILD_SEC(_time) (((_time)[6] - '0') * 10 + (_time)[7] - '0') + +#define BUILD_DATE_IS_BAD(_date) ((_date) == nullptr || strlen(_date) < strlen("Jan 01 2000") || (_date)[0] == '?') +#define BUILD_TIME_IS_BAD(_time) ((_time) == nullptr || strlen(_time) < strlen("00:00:00") || (_time)[0] == '?') diff --git a/src/include/platform/CHIPDeviceConfig.h b/src/include/platform/CHIPDeviceConfig.h index 8c84494a9d6c0e..286efee3a7c1a2 100644 --- a/src/include/platform/CHIPDeviceConfig.h +++ b/src/include/platform/CHIPDeviceConfig.h @@ -1176,8 +1176,8 @@ * * Specifies the date of the build. Useful for deterministic builds. */ -#ifndef CHIP_DEVICE_CONFIG_FIRWMARE_BUILD_DATE -#define CHIP_DEVICE_CONFIG_FIRWMARE_BUILD_DATE __DATE__ +#ifndef CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE +#define CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE __DATE__ #endif /** diff --git a/src/include/platform/ConfigurationManager.h b/src/include/platform/ConfigurationManager.h index b58b22dae2a3a5..7b3be4d4b6bf4a 100644 --- a/src/include/platform/ConfigurationManager.h +++ b/src/include/platform/ConfigurationManager.h @@ -85,15 +85,17 @@ class ConfigurationManager kMaxLanguageTagLength = 5 // ISO 639-1 standard language codes }; - virtual CHIP_ERROR GetVendorName(char * buf, size_t bufSize) = 0; - virtual CHIP_ERROR GetVendorId(uint16_t & vendorId) = 0; - virtual CHIP_ERROR GetProductName(char * buf, size_t bufSize) = 0; - virtual CHIP_ERROR GetProductId(uint16_t & productId) = 0; - virtual CHIP_ERROR GetPrimaryMACAddress(MutableByteSpan buf) = 0; - virtual CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) = 0; - virtual CHIP_ERROR GetPrimary802154MACAddress(uint8_t * buf) = 0; - virtual CHIP_ERROR GetSoftwareVersionString(char * buf, size_t bufSize) = 0; - virtual CHIP_ERROR GetSoftwareVersion(uint32_t & softwareVer) = 0; + virtual CHIP_ERROR GetVendorName(char * buf, size_t bufSize) = 0; + virtual CHIP_ERROR GetVendorId(uint16_t & vendorId) = 0; + virtual CHIP_ERROR GetProductName(char * buf, size_t bufSize) = 0; + virtual CHIP_ERROR GetProductId(uint16_t & productId) = 0; + virtual CHIP_ERROR GetPrimaryMACAddress(MutableByteSpan buf) = 0; + virtual CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) = 0; + virtual CHIP_ERROR GetPrimary802154MACAddress(uint8_t * buf) = 0; + virtual CHIP_ERROR GetSoftwareVersionString(char * buf, size_t bufSize) = 0; + virtual CHIP_ERROR GetSoftwareVersion(uint32_t & softwareVer) = 0; + virtual CHIP_ERROR GetFirmwareBuildChipEpochTime(System::Clock::Seconds32 & buildTime) = 0; + virtual CHIP_ERROR SetFirmwareBuildChipEpochTime(System::Clock::Seconds32 buildTime) { return CHIP_ERROR_NOT_IMPLEMENTED; } #if CHIP_ENABLE_ROTATING_DEVICE_ID && defined(CHIP_DEVICE_CONFIG_ROTATING_DEVICE_ID_UNIQUE_ID) // Lifetime counter is monotonic counter that is incremented upon each commencement of advertising virtual CHIP_ERROR GetLifetimeCounter(uint16_t & lifetimeCounter) = 0; diff --git a/src/include/platform/internal/GenericConfigurationManagerImpl.h b/src/include/platform/internal/GenericConfigurationManagerImpl.h index 43ceb552cfc927..db479f90643b00 100644 --- a/src/include/platform/internal/GenericConfigurationManagerImpl.h +++ b/src/include/platform/internal/GenericConfigurationManagerImpl.h @@ -72,6 +72,8 @@ class GenericConfigurationManagerImpl : public ConfigurationManager CHIP_ERROR GetSoftwareVersionString(char * buf, size_t bufSize) override; CHIP_ERROR GetSoftwareVersion(uint32_t & softwareVer) override; CHIP_ERROR StoreSoftwareVersion(uint32_t softwareVer) override; + CHIP_ERROR GetFirmwareBuildChipEpochTime(System::Clock::Seconds32 & buildTime) override; + CHIP_ERROR SetFirmwareBuildChipEpochTime(System::Clock::Seconds32 buildTime) override; CHIP_ERROR StoreSerialNumber(const char * serialNum, size_t serialNumLen) override; CHIP_ERROR GetPrimaryMACAddress(MutableByteSpan buf) override; CHIP_ERROR GetPrimaryWiFiMACAddress(uint8_t * buf) override; diff --git a/src/include/platform/internal/GenericConfigurationManagerImpl.ipp b/src/include/platform/internal/GenericConfigurationManagerImpl.ipp index 4122abcb26bc50..89003e2787086b 100644 --- a/src/include/platform/internal/GenericConfigurationManagerImpl.ipp +++ b/src/include/platform/internal/GenericConfigurationManagerImpl.ipp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,8 @@ namespace chip { namespace DeviceLayer { namespace Internal { +static Optional sFirmwareBuildChipEpochTime; + #if CHIP_USE_TRANSITIONAL_DEVICE_INSTANCE_INFO_PROVIDER template class LegacyDeviceInstanceInfoProvider : public DeviceInstanceInfoProvider @@ -472,6 +475,42 @@ inline CHIP_ERROR GenericConfigurationManagerImpl::StoreSoftwareVer return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; } +template +CHIP_ERROR GenericConfigurationManagerImpl::GetFirmwareBuildChipEpochTime(System::Clock::Seconds32 & chipEpochTime) +{ + // If the setter was called and we have a value in memory, return this. + if (sFirmwareBuildChipEpochTime.HasValue()) + { + chipEpochTime = sFirmwareBuildChipEpochTime.Value(); + return CHIP_NO_ERROR; + } + // Else, attempt to read the hard-coded values. + VerifyOrReturnError(!BUILD_DATE_IS_BAD(CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE), CHIP_ERROR_INTERNAL); + VerifyOrReturnError(!BUILD_TIME_IS_BAD(CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_TIME), CHIP_ERROR_INTERNAL); + const char * date = CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE; + const char * time = CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_TIME; + uint32_t seconds; + auto good = CalendarToChipEpochTime(COMPUTE_BUILD_YEAR(date), COMPUTE_BUILD_MONTH(date), COMPUTE_BUILD_DAY(date), COMPUTE_BUILD_HOUR(time), COMPUTE_BUILD_MIN(time), COMPUTE_BUILD_SEC(time), seconds); + if (good) + { + chipEpochTime = chip::System::Clock::Seconds32(seconds); + } + return good ? CHIP_NO_ERROR : CHIP_ERROR_INVALID_ARGUMENT; +} + +template +CHIP_ERROR GenericConfigurationManagerImpl::SetFirmwareBuildChipEpochTime(System::Clock::Seconds32 chipEpochTime) +{ + // The setter is sticky in that once the hard-coded time is overriden, it + // will be for the lifetime of the configuration manager singleton. + // However, this is not persistent across boots. + // + // Implementations that can't use the hard-coded time for whatever reason + // should set this at each init. + sFirmwareBuildChipEpochTime.SetValue(chipEpochTime); + return CHIP_NO_ERROR; +} + template CHIP_ERROR GenericConfigurationManagerImpl::GetDeviceTypeId(uint32_t & deviceType) { diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h index ee433fded170b0..b17b0b3d6f905b 100644 --- a/src/lib/support/DefaultStorageKeyAllocator.h +++ b/src/lib/support/DefaultStorageKeyAllocator.h @@ -54,6 +54,9 @@ class DefaultStorageKeyAllocator const char * FailSafeContextKey() { return Format("g/fs/c"); } static const char * FailSafeNetworkConfig() { return "g/fs/n"; } + // LastKnownGoodTime + const char * LastKnownGoodTimeKey() { return Format("g/lkgt"); } + // Session resumption const char * FabricSession(FabricIndex fabric, NodeId nodeId) { diff --git a/src/lib/support/logging/CHIPLogging.cpp b/src/lib/support/logging/CHIPLogging.cpp index 97876d08fb56f9..35be0f3145f594 100644 --- a/src/lib/support/logging/CHIPLogging.cpp +++ b/src/lib/support/logging/CHIPLogging.cpp @@ -101,7 +101,7 @@ const char ModuleNames[] = "-\0\0" // None "SP\0" // ServiceProvisioning "SWU" // SoftwareUpdate "TP\0" // TokenPairing - "TS\0" // TimeServices + "TS\0" // TimeService "HB\0" // Heartbeat "CSL" // chipSystemLayer "EVL" // Event Logging diff --git a/src/platform/Ameba/BLEManagerImpl.cpp b/src/platform/Ameba/BLEManagerImpl.cpp old mode 100755 new mode 100644 diff --git a/src/platform/BUILD.gn b/src/platform/BUILD.gn index c6933290d1ac9b..3dde9e09081332 100644 --- a/src/platform/BUILD.gn +++ b/src/platform/BUILD.gn @@ -299,6 +299,7 @@ if (chip_device_platform != "none") { output_name = "libDeviceLayer" sources = [ + "../include/platform/BuildTime.h", "../include/platform/CHIPDeviceConfig.h", "../include/platform/CHIPDeviceError.h", "../include/platform/CHIPDeviceEvent.h", diff --git a/src/platform/ESP32/SystemTimeSupport.cpp b/src/platform/ESP32/SystemTimeSupport.cpp index 229fe4f48610d0..8199439b87376f 100644 --- a/src/platform/ESP32/SystemTimeSupport.cpp +++ b/src/platform/ESP32/SystemTimeSupport.cpp @@ -50,6 +50,9 @@ Milliseconds64 ClockImpl::GetMonotonicMilliseconds64(void) CHIP_ERROR ClockImpl::GetClock_RealTime(Microseconds64 & aCurTime) { + // TODO(19081): This platform does not properly error out if wall clock has + // not been set. For now, short circuit this. + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; struct timeval tv; if (gettimeofday(&tv, nullptr) != 0) { @@ -103,6 +106,9 @@ CHIP_ERROR InitClock_RealTime() Clock::Microseconds64((static_cast(CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD) * UINT64_C(1000000))); // Use CHIP_SYSTEM_CONFIG_VALID_REAL_TIME_THRESHOLD as the initial value of RealTime. // Then the RealTime obtained from GetClock_RealTime will be always valid. + // + // TODO(19081): This is broken because it causes the platform to report + // that it does have wall clock time when it actually doesn't. return System::SystemClock().SetClock_RealTime(curTime); } diff --git a/src/platform/fake/ConfigurationManagerImpl.h b/src/platform/fake/ConfigurationManagerImpl.h index c65c525973cb87..f01d29f61465a9 100644 --- a/src/platform/fake/ConfigurationManagerImpl.h +++ b/src/platform/fake/ConfigurationManagerImpl.h @@ -41,6 +41,16 @@ class ConfigurationManagerImpl : public ConfigurationManager CHIP_ERROR GetProductId(uint16_t & productId) override { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR StoreHardwareVersion(uint16_t hardwareVer) override { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR GetSoftwareVersionString(char * buf, size_t bufSize) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR GetFirmwareBuildChipEpochTime(System::Clock::Seconds32 & buildTime) override + { + buildTime = mFirmwareBuildChipEpochTime; + return CHIP_NO_ERROR; + } + CHIP_ERROR SetFirmwareBuildChipEpochTime(System::Clock::Seconds32 buildTime) override + { + mFirmwareBuildChipEpochTime = buildTime; + return CHIP_NO_ERROR; + } CHIP_ERROR GetSoftwareVersion(uint32_t & softwareVer) override { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR StoreSoftwareVersion(uint32_t softwareVer) override { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR StoreSerialNumber(const char * serialNum, size_t serialNumLen) override { return CHIP_ERROR_NOT_IMPLEMENTED; } @@ -98,7 +108,8 @@ class ConfigurationManagerImpl : public ConfigurationManager return CHIP_ERROR_NOT_IMPLEMENTED; } - // NOTE: Other public interface methods are implemented by GenericConfigurationManagerImpl<>. +private: + System::Clock::Seconds32 mFirmwareBuildChipEpochTime = System::Clock::Seconds32(0); }; } // namespace DeviceLayer diff --git a/src/platform/tests/TestConfigurationMgr.cpp b/src/platform/tests/TestConfigurationMgr.cpp index 1337140fc493d7..9073f3f5ade5da 100644 --- a/src/platform/tests/TestConfigurationMgr.cpp +++ b/src/platform/tests/TestConfigurationMgr.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include @@ -150,6 +151,200 @@ static void TestConfigurationMgr_HardwareVersion(nlTestSuite * inSuite, void * i NL_TEST_ASSERT(inSuite, hardwareVer == 1234); } +static int SnprintfBuildDate(char * s, size_t n, uint16_t year, uint8_t month, uint8_t day) +{ + // Print the calendar date to a human readable string as would + // given from the __DATE__ macro. + const char * monthString = nullptr; + switch (month) + { + case 1: + monthString = "Jan"; + break; + case 2: + monthString = "Feb"; + break; + case 3: + monthString = "Mar"; + break; + case 4: + monthString = "Apr"; + break; + case 5: + monthString = "May"; + break; + case 6: + monthString = "Jun"; + break; + case 7: + monthString = "Jul"; + break; + case 8: + monthString = "Aug"; + break; + case 9: + monthString = "Sep"; + break; + case 10: + monthString = "Oct"; + break; + case 11: + monthString = "Nov"; + break; + case 12: + monthString = "Dec"; + break; + } + if (monthString == nullptr) + { + return -1; + } + return snprintf(s, n, "%s %2u %u", monthString, day, year); +} + +static int SnprintfBuildDate(char * s, size_t n, System::Clock::Seconds32 chipEpochBuildTime) +{ + // Convert to a calendar date-time. + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + ChipEpochToCalendarTime(chipEpochBuildTime.count(), year, month, day, hour, minute, second); + return SnprintfBuildDate(s, n, year, month, day); +} + +static int SnprintfBuildTimeOfDay(char * s, size_t n, uint8_t hour, uint8_t minute, uint8_t second) +{ + // Print the time of day to a human readable string as would + // given from the __TIME__ macro. + return snprintf(s, n, "%02u:%02u:%02u", hour, minute, second); +} + +static int SnprintfBuildTimeOfDay(char * s, size_t n, System::Clock::Seconds32 chipEpochBuildTime) +{ + // Convert to a calendar date-time. + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + ChipEpochToCalendarTime(chipEpochBuildTime.count(), year, month, day, hour, minute, second); + return SnprintfBuildTimeOfDay(s, n, hour, minute, second); +} + +static void TestConfigurationMgr_FirmwareBuildTime(nlTestSuite * inSuite, void * inContext) +{ + // Read the firmware build time from the configuration manager. + // This is referenced to the CHIP epoch. + System::Clock::Seconds32 chipEpochTime; + NL_TEST_ASSERT(inSuite, ConfigurationMgr().GetFirmwareBuildChipEpochTime(chipEpochTime) == CHIP_NO_ERROR); + + // Override the hard-coded build time with the setter and verify operation. + System::Clock::Seconds32 overrideValue = System::Clock::Seconds32(rand()); + NL_TEST_ASSERT(inSuite, ConfigurationMgr().SetFirmwareBuildChipEpochTime(overrideValue) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, ConfigurationMgr().GetFirmwareBuildChipEpochTime(chipEpochTime) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, overrideValue == chipEpochTime); + + // Verify that the BuildTime.h parser can parse current CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE / TIME. + do + { + const char * date = CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_DATE; + const char * timeOfDay = CHIP_DEVICE_CONFIG_FIRMWARE_BUILD_TIME; + + // Check that strings look good. + NL_TEST_ASSERT(inSuite, !BUILD_DATE_IS_BAD(date)); + NL_TEST_ASSERT(inSuite, !BUILD_TIME_IS_BAD(timeOfDay)); + if (BUILD_DATE_IS_BAD(date) || BUILD_TIME_IS_BAD(timeOfDay)) + { + break; + } + + // Parse. + uint16_t year = COMPUTE_BUILD_YEAR(date); + uint8_t month = COMPUTE_BUILD_MONTH(date); + uint8_t day = COMPUTE_BUILD_DAY(date); + uint8_t hour = COMPUTE_BUILD_HOUR(timeOfDay); + uint8_t minute = COMPUTE_BUILD_MIN(timeOfDay); + uint8_t second = COMPUTE_BUILD_SEC(timeOfDay); + + // Print the date to a string as would be given by the __DATE__ macro. + char parsedDate[14] = { 0 }; // strlen("Jan 000 00000") == 13 + { + int printed; + printed = SnprintfBuildDate(parsedDate, sizeof(parsedDate), year, month, day); + NL_TEST_ASSERT(inSuite, printed > 0 && printed < static_cast(sizeof(parsedDate))); + } + + // Print the time of day to a straing as would be given by the __TIME__ macro. + char parsedTimeOfDay[12] = { 0 }; // strlen("000:000:000") == 11 + { + int printed; + printed = SnprintfBuildTimeOfDay(parsedTimeOfDay, sizeof(parsedTimeOfDay), hour, minute, second); + NL_TEST_ASSERT(inSuite, printed > 0 && printed < static_cast(sizeof(parsedTimeOfDay))); + } + + // Verify match. + NL_TEST_ASSERT(inSuite, strcmp(date, parsedDate) == 0); + NL_TEST_ASSERT(inSuite, strcmp(timeOfDay, parsedTimeOfDay) == 0); + } while (0); + + // Generate random chip epoch times and verify that our BuildTime.h parser + // macros also work for these. + for (int i = 0; i < 10000; ++i) + { + char date[14] = { 0 }; // strlen("Jan 000 00000") == 13 + char timeOfDay[12] = { 0 }; // strlen("000:000:000") == 11 + + chipEpochTime = System::Clock::Seconds32(rand()); + + // rand() will only give us [0, 0x7FFFFFFF]. Give us coverage for + // times in the upper half of the chip epoch time range as well. + chipEpochTime = i % 2 ? chipEpochTime : System::Clock::Seconds32(chipEpochTime.count() | 0x80000000); + + // Print the date to a string as would be given by the __DATE__ macro. + { + int printed; + printed = SnprintfBuildDate(date, sizeof(date), chipEpochTime); + NL_TEST_ASSERT(inSuite, printed > 0 && printed < static_cast(sizeof(date))); + } + + // Print the time of day to a straing as would be given by the __TIME__ macro. + { + int printed; + printed = SnprintfBuildTimeOfDay(timeOfDay, sizeof(timeOfDay), chipEpochTime); + NL_TEST_ASSERT(inSuite, printed > 0 && printed < static_cast(sizeof(timeOfDay))); + } + + // Check that strings look good. + NL_TEST_ASSERT(inSuite, !BUILD_DATE_IS_BAD(date)); + NL_TEST_ASSERT(inSuite, !BUILD_TIME_IS_BAD(timeOfDay)); + if (BUILD_DATE_IS_BAD(date) || BUILD_TIME_IS_BAD(timeOfDay)) + { + continue; + } + + // Convert from chip epoch seconds to calendar time. + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + ChipEpochToCalendarTime(chipEpochTime.count(), year, month, day, hour, minute, second); + + // Verify that our BuildTime.h macros can correctly parse the date / time strings. + NL_TEST_ASSERT(inSuite, year == COMPUTE_BUILD_YEAR(date)); + NL_TEST_ASSERT(inSuite, month == COMPUTE_BUILD_MONTH(date)); + NL_TEST_ASSERT(inSuite, day == COMPUTE_BUILD_DAY(date)); + NL_TEST_ASSERT(inSuite, hour == COMPUTE_BUILD_HOUR(timeOfDay)); + NL_TEST_ASSERT(inSuite, minute == COMPUTE_BUILD_MIN(timeOfDay)); + NL_TEST_ASSERT(inSuite, second == COMPUTE_BUILD_SEC(timeOfDay)); + } +} + static void TestConfigurationMgr_CountryCode(nlTestSuite * inSuite, void * inContext) { CHIP_ERROR err = CHIP_NO_ERROR; @@ -223,6 +418,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test ConfigurationMgr::UniqueId", TestConfigurationMgr_UniqueId), NL_TEST_DEF("Test ConfigurationMgr::ManufacturingDate", TestConfigurationMgr_ManufacturingDate), NL_TEST_DEF("Test ConfigurationMgr::HardwareVersion", TestConfigurationMgr_HardwareVersion), + NL_TEST_DEF("Test ConfigurationMgr::FirmwareBuildTime", TestConfigurationMgr_FirmwareBuildTime), NL_TEST_DEF("Test ConfigurationMgr::CountryCode", TestConfigurationMgr_CountryCode), NL_TEST_DEF("Test ConfigurationMgr::GetPrimaryMACAddress", TestConfigurationMgr_GetPrimaryMACAddress), NL_TEST_DEF("Test ConfigurationMgr::GetFailSafeArmed", TestConfigurationMgr_GetFailSafeArmed), diff --git a/src/protocols/secure_channel/CASEServer.cpp b/src/protocols/secure_channel/CASEServer.cpp index 4ecbd28547ad8b..f7cab3a9c4236a 100644 --- a/src/protocols/secure_channel/CASEServer.cpp +++ b/src/protocols/secure_channel/CASEServer.cpp @@ -31,17 +31,19 @@ namespace chip { CHIP_ERROR CASEServer::ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, SessionManager * sessionManager, FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage, + Credentials::CertificateValidityPolicy * certificateValidityPolicy, Credentials::GroupDataProvider * responderGroupDataProvider) { VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(responderGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - mSessionManager = sessionManager; - mSessionResumptionStorage = sessionResumptionStorage; - mFabrics = fabrics; - mExchangeManager = exchangeManager; - mGroupDataProvider = responderGroupDataProvider; + mSessionManager = sessionManager; + mSessionResumptionStorage = sessionResumptionStorage; + mCertificateValidityPolicy = certificateValidityPolicy; + mFabrics = fabrics; + mExchangeManager = exchangeManager; + mGroupDataProvider = responderGroupDataProvider; Cleanup(); return CHIP_NO_ERROR; @@ -53,9 +55,9 @@ CHIP_ERROR CASEServer::InitCASEHandshake(Messaging::ExchangeContext * ec) // Setup CASE state machine using the credentials for the current fabric. GetSession().SetGroupDataProvider(mGroupDataProvider); - ReturnErrorOnFailure( - GetSession().ListenForSessionEstablishment(*mSessionManager, mFabrics, mSessionResumptionStorage, this, - Optional::Value(GetLocalMRPConfig()))); + ReturnErrorOnFailure(GetSession().ListenForSessionEstablishment( + *mSessionManager, mFabrics, mSessionResumptionStorage, mCertificateValidityPolicy, this, + Optional::Value(GetLocalMRPConfig()))); // Hand over the exchange context to the CASE session. ec->SetDelegate(&GetSession()); diff --git a/src/protocols/secure_channel/CASEServer.h b/src/protocols/secure_channel/CASEServer.h index d50ad6708c0583..f2a44ea33e1623 100644 --- a/src/protocols/secure_channel/CASEServer.h +++ b/src/protocols/secure_channel/CASEServer.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include #include @@ -40,6 +41,7 @@ class CASEServer : public SessionEstablishmentDelegate, CHIP_ERROR ListenForSessionEstablishment(Messaging::ExchangeManager * exchangeManager, SessionManager * sessionManager, FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage, + Credentials::CertificateValidityPolicy * policy, Credentials::GroupDataProvider * responderGroupDataProvider); //////////// SessionEstablishmentDelegate Implementation /////////////// @@ -58,8 +60,9 @@ class CASEServer : public SessionEstablishmentDelegate, virtual CASESession & GetSession() { return mPairingSession; } private: - Messaging::ExchangeManager * mExchangeManager = nullptr; - SessionResumptionStorage * mSessionResumptionStorage = nullptr; + Messaging::ExchangeManager * mExchangeManager = nullptr; + SessionResumptionStorage * mSessionResumptionStorage = nullptr; + Credentials::CertificateValidityPolicy * mCertificateValidityPolicy = nullptr; CASESession mPairingSession; SessionManager * mSessionManager = nullptr; diff --git a/src/protocols/secure_channel/CASESession.cpp b/src/protocols/secure_channel/CASESession.cpp index 78e01a294a0cad..0c39895322ec2c 100644 --- a/src/protocols/secure_channel/CASESession.cpp +++ b/src/protocols/secure_channel/CASESession.cpp @@ -144,12 +144,14 @@ void CASESession::Clear() mState = State::kInitialized; Crypto::ClearSecretData(mIPK); - mLocalNodeId = kUndefinedNodeId; - mPeerNodeId = kUndefinedNodeId; - mFabricInfo = nullptr; + mLocalNodeId = kUndefinedNodeId; + mPeerNodeId = kUndefinedNodeId; + mFabricsTable = nullptr; + mFabricIndex = kUndefinedFabricIndex; } -CHIP_ERROR CASESession::Init(SessionManager & sessionManager, SessionEstablishmentDelegate * delegate) +CHIP_ERROR CASESession::Init(SessionManager & sessionManager, Credentials::CertificateValidityPolicy * policy, + SessionEstablishmentDelegate * delegate) { VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(mGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -164,6 +166,7 @@ CHIP_ERROR CASESession::Init(SessionManager & sessionManager, SessionEstablishme mValidContext.Reset(); mValidContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature); mValidContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth); + mValidContext.mValidityPolicy = policy; return CHIP_NO_ERROR; } @@ -171,11 +174,11 @@ CHIP_ERROR CASESession::Init(SessionManager & sessionManager, SessionEstablishme CHIP_ERROR CASESession::ListenForSessionEstablishment(SessionManager & sessionManager, FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage, - SessionEstablishmentDelegate * delegate, + Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, Optional mrpConfig) { VerifyOrReturnError(fabrics != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - ReturnErrorOnFailure(Init(sessionManager, delegate)); + ReturnErrorOnFailure(Init(sessionManager, policy, delegate)); mRole = CryptoContext::SessionRole::kResponder; mFabricsTable = fabrics; @@ -187,18 +190,23 @@ CASESession::ListenForSessionEstablishment(SessionManager & sessionManager, Fabr return CHIP_NO_ERROR; } -CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricInfo * fabric, NodeId peerNodeId, - ExchangeContext * exchangeCtxt, SessionResumptionStorage * sessionResumptionStorage, - SessionEstablishmentDelegate * delegate, Optional mrpConfig) +CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, FabricIndex fabricIndex, + NodeId peerNodeId, ExchangeContext * exchangeCtxt, + SessionResumptionStorage * sessionResumptionStorage, + Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, + Optional mrpConfig) { MATTER_TRACE_EVENT_SCOPE("EstablishSession", "CASESession"); - CHIP_ERROR err = CHIP_NO_ERROR; + CHIP_ERROR err = CHIP_NO_ERROR; + FabricInfo * fabricInfo = nullptr; // Return early on error here, as we have not initialized any state yet ReturnErrorCodeIf(exchangeCtxt == nullptr, CHIP_ERROR_INVALID_ARGUMENT); - ReturnErrorCodeIf(fabric == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(fabricTable == nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(fabricIndex == kUndefinedFabricIndex, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError((fabricInfo = fabricTable->FindFabricWithIndex(fabricIndex)) != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - err = Init(sessionManager, delegate); + err = Init(sessionManager, policy, delegate); mRole = CryptoContext::SessionRole::kInitiator; @@ -210,16 +218,17 @@ CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, Fabric // been initialized SuccessOrExit(err); - mFabricInfo = fabric; + mFabricsTable = fabricTable; + mFabricIndex = fabricIndex; mSessionResumptionStorage = sessionResumptionStorage; mLocalMRPConfig = mrpConfig; mExchangeCtxt->SetResponseTimeout(kSigma_Response_Timeout + mExchangeCtxt->GetSessionHandle()->GetAckTimeout()); mPeerNodeId = peerNodeId; - mLocalNodeId = fabric->GetNodeId(); + mLocalNodeId = fabricInfo->GetNodeId(); ChipLogProgress(SecureChannel, "Initiating session on local FabricIndex %u from 0x" ChipLogFormatX64 " -> 0x" ChipLogFormatX64, - static_cast(fabric->GetFabricIndex()), ChipLogValueX64(mLocalNodeId), ChipLogValueX64(mPeerNodeId)); + static_cast(mFabricIndex), ChipLogValueX64(mLocalNodeId), ChipLogValueX64(mPeerNodeId)); err = SendSigma1(); SuccessOrExit(err); @@ -290,9 +299,8 @@ CHIP_ERROR CASESession::DeriveSecureSession(CryptoContext & session) const CHIP_ERROR CASESession::RecoverInitiatorIpk() { Credentials::GroupDataProvider::KeySet ipkKeySet; - FabricIndex fabricIndex = mFabricInfo->GetFabricIndex(); - CHIP_ERROR err = mGroupDataProvider->GetIpkKeySet(fabricIndex, ipkKeySet); + CHIP_ERROR err = mGroupDataProvider->GetIpkKeySet(mFabricIndex, ipkKeySet); if (err != CHIP_NO_ERROR) { @@ -339,6 +347,10 @@ CHIP_ERROR CASESession::SendSigma1() TLV::TLVType outerContainerType = TLV::kTLVType_NotSpecified; uint8_t destinationIdentifier[kSHA256_Hash_Length] = { 0 }; + // Lookup fabric info. + auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); + // Validate that we have a session ID allocated. VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE); @@ -359,16 +371,14 @@ CHIP_ERROR CASESession::SendSigma1() ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), GetLocalSessionId().Value())); // Generate a Destination Identifier based on the node we are attempting to reach { - ReturnErrorCodeIf(mFabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); - // Obtain originator IPK matching the fabric where we are trying to open a session. mIPK // will be properly set thereafter. ReturnErrorOnFailure(RecoverInitiatorIpk()); - FabricId fabricId = mFabricInfo->GetFabricId(); + FabricId fabricId = fabricInfo->GetFabricId(); uint8_t rootPubKeyBuf[Crypto::kP256_Point_Length]; Credentials::P256PublicKeySpan rootPubKeySpan(rootPubKeyBuf); - ReturnErrorOnFailure(mFabricInfo->GetRootPubkey(rootPubKeySpan)); + ReturnErrorOnFailure(fabricInfo->GetRootPubkey(rootPubKeySpan)); MutableByteSpan destinationIdSpan(destinationIdentifier); ReturnErrorOnFailure(GenerateCaseDestinationId(ByteSpan(mIPK), ByteSpan(mInitiatorRandom), rootPubKeySpan, fabricId, @@ -389,7 +399,7 @@ CHIP_ERROR CASESession::SendSigma1() bool resuming = false; if (mSessionResumptionStorage != nullptr) { - CHIP_ERROR err = mSessionResumptionStorage->FindByScopedNodeId(mFabricInfo->GetScopedNodeIdForNode(mPeerNodeId), + CHIP_ERROR err = mSessionResumptionStorage->FindByScopedNodeId(fabricInfo->GetScopedNodeIdForNode(mPeerNodeId), mResumeResumptionId, mSharedSecret, mPeerCATs); if (err == CHIP_NO_ERROR) { @@ -470,7 +480,7 @@ CHIP_ERROR CASESession::FindLocalNodeFromDestionationId(const ByteSpan & destina found = true; MutableByteSpan ipkSpan(mIPK); CopySpanToMutableSpan(candidateIpkSpan, ipkSpan); - mFabricInfo = &fabricInfo; + mFabricIndex = fabricInfo.GetFabricIndex(); mLocalNodeId = nodeId; break; } @@ -488,8 +498,8 @@ CHIP_ERROR CASESession::FindLocalNodeFromDestionationId(const ByteSpan & destina CHIP_ERROR CASESession::TryResumeSession(SessionResumptionStorage::ConstResumptionIdView resumptionId, ByteSpan resume1MIC, ByteSpan initiatorRandom) { - if (mSessionResumptionStorage == nullptr) - return CHIP_ERROR_INCORRECT_STATE; + VerifyOrReturnError(mSessionResumptionStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE); SessionResumptionStorage::ConstResumptionIdView resumptionIdSpan(resumptionId); ScopedNodeId node; @@ -499,12 +509,12 @@ CHIP_ERROR CASESession::TryResumeSession(SessionResumptionStorage::ConstResumpti ReturnErrorOnFailure( ValidateSigmaResumeMIC(resume1MIC, initiatorRandom, resumptionId, ByteSpan(kKDFS1RKeyInfo), ByteSpan(kResume1MIC_Nonce))); - mFabricInfo = mFabricsTable->FindFabricWithIndex(node.GetFabricIndex()); - if (mFabricInfo == nullptr) - return CHIP_ERROR_INTERNAL; + auto fabricInfo = mFabricsTable->FindFabricWithIndex(node.GetFabricIndex()); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); + mFabricIndex = node.GetFabricIndex(); mPeerNodeId = node.GetNodeId(); - mLocalNodeId = mFabricInfo->GetNodeId(); + mLocalNodeId = fabricInfo->GetNodeId(); return CHIP_NO_ERROR; } @@ -558,9 +568,9 @@ CHIP_ERROR CASESession::HandleSigma1(System::PacketBufferHandle && msg) if (err == CHIP_NO_ERROR) { ChipLogProgress(SecureChannel, "CASE matched destination ID: fabricIndex %u, NodeID 0x" ChipLogFormatX64, - static_cast(mFabricInfo->GetFabricIndex()), ChipLogValueX64(mLocalNodeId)); + static_cast(mFabricIndex), ChipLogValueX64(mLocalNodeId)); - // Side-effect of FindLocalNodeFromDestionationId success was that mFabricInfo/mLocalNodeId are now + // Side-effect of FindLocalNodeFromDestionationId success was that mFabricIndex/mLocalNodeId are now // set to the local fabric and associated NodeId that was targeted by the initiator. } else @@ -651,14 +661,15 @@ CHIP_ERROR CASESession::SendSigma2() MATTER_TRACE_EVENT_SCOPE("SendSigma2", "CASESession"); VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE); - - VerifyOrReturnError(mFabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE); + auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); ByteSpan icaCert; - ReturnErrorOnFailure(mFabricInfo->GetICACert(icaCert)); + ReturnErrorOnFailure(fabricInfo->GetICACert(icaCert)); ByteSpan nocCert; - ReturnErrorOnFailure(mFabricInfo->GetNOCCert(nocCert)); + ReturnErrorOnFailure(fabricInfo->GetNOCCert(nocCert)); // Fill in the random value uint8_t msg_rand[kSigmaParamRandomNumberSize]; @@ -694,11 +705,11 @@ CHIP_ERROR CASESession::SendSigma2() ByteSpan(mRemotePubKey, mRemotePubKey.Length()), msg_R2_Signed.Get(), msg_r2_signed_len)); // Generate a Signature - VerifyOrReturnError(mFabricInfo->GetOperationalKey() != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(fabricInfo->GetOperationalKey() != nullptr, CHIP_ERROR_INCORRECT_STATE); P256ECDSASignature tbsData2Signature; ReturnErrorOnFailure( - mFabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R2_Signed.Get(), msg_r2_signed_len, tbsData2Signature)); + fabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R2_Signed.Get(), msg_r2_signed_len, tbsData2Signature)); msg_R2_Signed.Free(); @@ -1012,7 +1023,8 @@ CHIP_ERROR CASESession::HandleSigma2(System::PacketBufferHandle && msg) CHIP_ERROR CASESession::SendSigma3() { MATTER_TRACE_EVENT_SCOPE("SendSigma3", "CASESession"); - CHIP_ERROR err = CHIP_NO_ERROR; + CHIP_ERROR err = CHIP_NO_ERROR; + FabricInfo * fabricInfo = nullptr; MutableByteSpan messageDigestSpan(mMessageDigest); System::PacketBufferHandle msg_R3; @@ -1035,10 +1047,12 @@ CHIP_ERROR CASESession::SendSigma3() ByteSpan icaCert; ByteSpan nocCert; - VerifyOrExit(mFabricInfo != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + VerifyOrExit(mFabricsTable != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + VerifyOrExit(fabricInfo != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - SuccessOrExit(err = mFabricInfo->GetICACert(icaCert)); - SuccessOrExit(err = mFabricInfo->GetNOCCert(nocCert)); + SuccessOrExit(err = fabricInfo->GetICACert(icaCert)); + SuccessOrExit(err = fabricInfo->GetNOCCert(nocCert)); // Prepare Sigma3 TBS Data Blob msg_r3_signed_len = TLV::EstimateStructOverhead(icaCert.size(), nocCert.size(), kP256_PublicKey_Length, kP256_PublicKey_Length); @@ -1049,8 +1063,8 @@ CHIP_ERROR CASESession::SendSigma3() ByteSpan(mRemotePubKey, mRemotePubKey.Length()), msg_R3_Signed.Get(), msg_r3_signed_len)); // Generate a signature - VerifyOrExit(mFabricInfo->GetOperationalKey() != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - err = mFabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R3_Signed.Get(), msg_r3_signed_len, tbsData3Signature); + VerifyOrExit(fabricInfo->GetOperationalKey() != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + err = fabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R3_Signed.Get(), msg_r3_signed_len, tbsData3Signature); SuccessOrExit(err); // Prepare Sigma3 TBE Data Blob @@ -1396,15 +1410,17 @@ CHIP_ERROR CASESession::ValidateSigmaResumeMIC(const ByteSpan & resumeMIC, const CHIP_ERROR CASESession::ValidatePeerIdentity(const ByteSpan & peerNOC, const ByteSpan & peerICAC, NodeId & peerNodeId, Crypto::P256PublicKey & peerPublicKey) { - ReturnErrorCodeIf(mFabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(mFabricsTable == nullptr, CHIP_ERROR_INCORRECT_STATE); + auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(SetEffectiveTime()); PeerId peerId; FabricId peerNOCFabricId; - ReturnErrorOnFailure(mFabricInfo->VerifyCredentials(peerNOC, peerICAC, mValidContext, peerId, peerNOCFabricId, peerPublicKey)); + ReturnErrorOnFailure(fabricInfo->VerifyCredentials(peerNOC, peerICAC, mValidContext, peerId, peerNOCFabricId, peerPublicKey)); - VerifyOrReturnError(mFabricInfo->GetFabricId() == peerNOCFabricId, CHIP_ERROR_INVALID_CASE_PARAMETER); + VerifyOrReturnError(fabricInfo->GetFabricId() == peerNOCFabricId, CHIP_ERROR_INVALID_CASE_PARAMETER); peerNodeId = peerId.GetNodeId(); @@ -1441,39 +1457,41 @@ CHIP_ERROR CASESession::ConstructTBSData(const ByteSpan & senderNOC, const ByteS return CHIP_NO_ERROR; } -CHIP_ERROR CASESession::GetHardcodedTime() -{ - using namespace ASN1; - ASN1UniversalTime effectiveTime; - - effectiveTime.Year = 2022; - effectiveTime.Month = 1; - effectiveTime.Day = 1; - effectiveTime.Hour = 10; - effectiveTime.Minute = 10; - effectiveTime.Second = 10; - - return ASN1ToChipEpochTime(effectiveTime, mValidContext.mEffectiveTime); -} - CHIP_ERROR CASESession::SetEffectiveTime() { - System::Clock::Milliseconds64 currentTimeMS; - CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(currentTimeMS); + System::Clock::Milliseconds64 currentUnixTimeMS; + CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(currentUnixTimeMS); - if (err != CHIP_NO_ERROR) + if (err == CHIP_NO_ERROR) + { + // If the system has given us a wall clock time, we must use it or + // fail. Conversion failures here are therefore always an error. + System::Clock::Seconds32 currentUnixTime = std::chrono::duration_cast(currentUnixTimeMS); + ReturnErrorOnFailure(mValidContext.SetEffectiveTimeFromUnixTime(currentUnixTime)); + } + else { + // If we don't have wall clock time, the spec dictates that we should + // fall back to Last Known Good Time. Ultimately, the calling application's + // validity policy will determine whether this is permissible. + System::Clock::Seconds32 lastKnownGoodChipEpochTime; ChipLogError(SecureChannel, - "The device does not support GetClock_RealTimeMS() API. This will eventually result in CASE session setup " - "failures. API error: %" CHIP_ERROR_FORMAT, + "The device does not support GetClock_RealTimeMS() API: %" CHIP_ERROR_FORMAT + ". Falling back to Last Known Good UTC Time", err.Format()); - // TODO: Remove use of hardcoded time during CASE setup - return GetHardcodedTime(); + VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE); + err = mFabricsTable->GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime); + if (err != CHIP_NO_ERROR) + { + // If we have no time available, the Validity Policy will + // determine what to do. + ChipLogError(SecureChannel, "Failed to retrieve Last Known Good UTC Time"); + } + else + { + mValidContext.SetEffectiveTime(lastKnownGoodChipEpochTime); + } } - - System::Clock::Seconds32 currentTime = std::chrono::duration_cast(currentTimeMS); - VerifyOrReturnError(UnixEpochToChipEpochTime(currentTime.count(), mValidContext.mEffectiveTime), CHIP_ERROR_INVALID_TIME); - return CHIP_NO_ERROR; } diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h index 1914f3a2cc3507..eb2a94e28314f9 100644 --- a/src/protocols/secure_channel/CASESession.h +++ b/src/protocols/secure_channel/CASESession.h @@ -30,6 +30,7 @@ #if CHIP_CRYPTO_HSM #include #endif +#include #include #include #include @@ -72,13 +73,14 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, * * @param sessionManager session manager from which to allocate a secure session object * @param fabrics Table of fabrics that are currently configured on the device + * @param policy Optional application-provided certificate validity policy * @param delegate Callback object * * @return CHIP_ERROR The result of initialization */ CHIP_ERROR ListenForSessionEstablishment( SessionManager & sessionManager, FabricTable * fabrics, SessionResumptionStorage * sessionResumptionStorage, - SessionEstablishmentDelegate * delegate, + Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, Optional mrpConfig = Optional::Missing()); /** @@ -86,17 +88,19 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, * Create and send session establishment request using device's operational credentials. * * @param sessionManager session manager from which to allocate a secure session object - * @param fabric The fabric that should be used for connecting with the peer + * @param fabricTable The fabric table to be used for connecting with the peer + * @param fabricIndex The index of the fabric to be used for connecting with the peer * @param peerNodeId Node id of the peer node * @param exchangeCtxt The exchange context to send and receive messages with the peer + * @param policy Optional application-provided certificate validity policy * @param delegate Callback object * * @return CHIP_ERROR The result of initialization */ CHIP_ERROR - EstablishSession(SessionManager & sessionManager, FabricInfo * fabric, NodeId peerNodeId, + EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, FabricIndex fabricIndex, NodeId peerNodeId, Messaging::ExchangeContext * exchangeCtxt, SessionResumptionStorage * sessionResumptionStorage, - SessionEstablishmentDelegate * delegate, + Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, Optional mrpConfig = Optional::Missing()); /** @@ -157,7 +161,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, //// SessionDelegate //// void OnSessionReleased() override; - FabricIndex GetFabricIndex() const { return mFabricInfo != nullptr ? mFabricInfo->GetFabricIndex() : kUndefinedFabricIndex; } + FabricIndex GetFabricIndex() const { return mFabricIndex; } // TODO: remove Clear, we should create a new instance instead reset the old instance. /** @brief This function zeroes out and resets the memory used by the object. @@ -177,7 +181,8 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, kFinishedViaResume = 7, }; - CHIP_ERROR Init(SessionManager & sessionManager, SessionEstablishmentDelegate * delegate); + CHIP_ERROR Init(SessionManager & sessionManager, Credentials::CertificateValidityPolicy * policy, + SessionEstablishmentDelegate * delegate); // On success, sets mIpk to the correct value for outgoing Sigma1 based on internal state CHIP_ERROR RecoverInitiatorIpk(); @@ -241,10 +246,10 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, SessionResumptionStorage * mSessionResumptionStorage = nullptr; - FabricTable * mFabricsTable = nullptr; - const FabricInfo * mFabricInfo = nullptr; - NodeId mPeerNodeId = kUndefinedNodeId; - NodeId mLocalNodeId = kUndefinedNodeId; + FabricTable * mFabricsTable = nullptr; + FabricIndex mFabricIndex = kUndefinedFabricIndex; + NodeId mPeerNodeId = kUndefinedNodeId; + NodeId mLocalNodeId = kUndefinedNodeId; CATValues mPeerCATs; SessionResumptionStorage::ResumptionIdStorage mResumeResumptionId; // ResumptionId which is used to resume this session diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index 08d1adef9bc6f5..816682767df078 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -198,10 +198,13 @@ void CASE_SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext) pairing.SetGroupDataProvider(&gDeviceGroupDataProvider); NL_TEST_ASSERT(inSuite, - pairing.ListenForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr) == CHIP_ERROR_INVALID_ARGUMENT); - NL_TEST_ASSERT( - inSuite, pairing.ListenForSessionEstablishment(sessionManager, nullptr, nullptr, &delegate) == CHIP_ERROR_INVALID_ARGUMENT); - NL_TEST_ASSERT(inSuite, pairing.ListenForSessionEstablishment(sessionManager, &fabrics, nullptr, &delegate) == CHIP_NO_ERROR); + pairing.ListenForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, nullptr) == + CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, + pairing.ListenForSessionEstablishment(sessionManager, nullptr, nullptr, nullptr, &delegate) == + CHIP_ERROR_INVALID_ARGUMENT); + NL_TEST_ASSERT(inSuite, + pairing.ListenForSessionEstablishment(sessionManager, &fabrics, nullptr, nullptr, &delegate) == CHIP_NO_ERROR); } void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) @@ -214,21 +217,21 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) CASESession pairing; pairing.SetGroupDataProvider(&gCommissionerGroupDataProvider); - FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); - NL_TEST_ASSERT(inSuite, fabric != nullptr); - ExchangeContext * context = ctx.NewUnauthenticatedExchangeToBob(&pairing); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, nullptr, Node01_01, nullptr, nullptr, nullptr) != CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, nullptr, gCommissionerFabricIndex, Node01_01, nullptr, nullptr, nullptr, + nullptr) != CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, fabric, Node01_01, nullptr, nullptr, nullptr) != CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, nullptr, + nullptr, nullptr, nullptr) != CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, fabric, Node01_01, context, nullptr, &delegate) == CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, context, + nullptr, nullptr, &delegate) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); auto & loopback = ctx.GetLoopback(); @@ -249,8 +252,8 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) ExchangeContext * context1 = ctx.NewUnauthenticatedExchangeToBob(&pairing1); NL_TEST_ASSERT(inSuite, - pairing1.EstablishSession(sessionManager, fabric, Node01_01, context1, nullptr, &delegate) == - CHIP_ERROR_BAD_REQUEST); + pairing1.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, context1, + nullptr, nullptr, &delegate) == CHIP_ERROR_BAD_REQUEST); ctx.DrainAndServiceIO(); loopback.mMessageSendError = CHIP_NO_ERROR; @@ -278,16 +281,14 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(&pairingCommissioner); - FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); - NL_TEST_ASSERT(inSuite, fabric != nullptr); - pairingAccessory.SetGroupDataProvider(&gDeviceGroupDataProvider); NL_TEST_ASSERT(inSuite, - pairingAccessory.ListenForSessionEstablishment(sessionManager, &gDeviceFabrics, nullptr, &delegateAccessory, + pairingAccessory.ListenForSessionEstablishment(sessionManager, &gDeviceFabrics, nullptr, nullptr, + &delegateAccessory, MakeOptional(verySleepyAccessoryRmpConfig)) == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, - pairingCommissioner.EstablishSession(sessionManager, fabric, Node01_01, contextCommissioner, nullptr, - &delegateCommissioner, + pairingCommissioner.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, + contextCommissioner, nullptr, nullptr, &delegateCommissioner, MakeOptional(nonSleepyCommissionerRmpConfig)) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); @@ -330,7 +331,7 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte // components may work simultaneously on a single device. NL_TEST_ASSERT(inSuite, gPairingServer.ListenForSessionEstablishment(&ctx.GetExchangeManager(), &ctx.GetSecureSessionManager(), - &gDeviceFabrics, nullptr, + &gDeviceFabrics, nullptr, nullptr, &gDeviceGroupDataProvider) == CHIP_NO_ERROR); ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner); @@ -339,8 +340,9 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte NL_TEST_ASSERT(inSuite, fabric != nullptr); NL_TEST_ASSERT(inSuite, - pairingCommissioner->EstablishSession(ctx.GetSecureSessionManager(), fabric, Node01_01, contextCommissioner, - nullptr, &delegateCommissioner) == CHIP_NO_ERROR); + pairingCommissioner->EstablishSession(ctx.GetSecureSessionManager(), &gCommissionerFabrics, + gCommissionerFabricIndex, Node01_01, contextCommissioner, nullptr, nullptr, + &delegateCommissioner) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, loopback.mSentMessageCount == sTestCaseMessageCount); @@ -356,7 +358,8 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte ExchangeContext * contextCommissioner1 = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner1); NL_TEST_ASSERT(inSuite, - pairingCommissioner1->EstablishSession(ctx.GetSecureSessionManager(), fabric, Node01_01, contextCommissioner1, + pairingCommissioner1->EstablishSession(ctx.GetSecureSessionManager(), &gCommissionerFabrics, + gCommissionerFabricIndex, Node01_01, contextCommissioner1, nullptr, nullptr, &delegateCommissioner) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); @@ -757,12 +760,12 @@ static void CASE_SessionResumptionStorage(nlTestSuite * inSuite, void * inContex loopback.mSentMessageCount = 0; NL_TEST_ASSERT(inSuite, gPairingServer.ListenForSessionEstablishment(&ctx.GetExchangeManager(), &ctx.GetSecureSessionManager(), - &gDeviceFabrics, &testVectors[i].responderStorage, + &gDeviceFabrics, &testVectors[i].responderStorage, nullptr, &gDeviceGroupDataProvider) == CHIP_NO_ERROR); ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner); - auto establishmentReturnVal = - pairingCommissioner->EstablishSession(ctx.GetSecureSessionManager(), fabric, Node01_01, contextCommissioner, - &testVectors[i].initiatorStorage, &delegateCommissioner); + auto establishmentReturnVal = pairingCommissioner->EstablishSession( + ctx.GetSecureSessionManager(), &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, contextCommissioner, + &testVectors[i].initiatorStorage, nullptr, &delegateCommissioner); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, establishmentReturnVal == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, loopback.mSentMessageCount == testVectors[i].expectedSentMessageCount); diff --git a/src/tools/chip-cert/Cmd_ValidateCert.cpp b/src/tools/chip-cert/Cmd_ValidateCert.cpp index 269f0a1485c86c..aed84676f86dd4 100644 --- a/src/tools/chip-cert/Cmd_ValidateCert.cpp +++ b/src/tools/chip-cert/Cmd_ValidateCert.cpp @@ -25,6 +25,7 @@ */ #include "chip-cert.h" +#include #include "vector" @@ -182,7 +183,9 @@ bool Cmd_ValidateCert(int argc, char * argv[]) certToBeValidated = certSet.GetLastCert(); context.Reset(); - res = chip::UnixEpochToChipEpochTime(static_cast(time(nullptr)), context.mEffectiveTime); + uint32_t currentTime; + res = chip::UnixEpochToChipEpochTime(static_cast(time(nullptr)), currentTime); + context.mEffectiveTime.Set(currentTime); VerifyTrueOrExit(res); err = certSet.FindValidCert(certToBeValidated->mSubjectDN, certToBeValidated->mSubjectKeyId, context, &validatedCert); diff --git a/src/tools/chip-cert/chip-cert.h b/src/tools/chip-cert/chip-cert.h index 1f8b69399f0182..21c24b15cc790b 100644 --- a/src/tools/chip-cert/chip-cert.h +++ b/src/tools/chip-cert/chip-cert.h @@ -51,6 +51,7 @@ #include #include +#include #include #include #include