diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 02194ed59b4096..4e10e67e74b2e8 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -71,10 +71,12 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() #endif ReturnLogErrorOnFailure(mDefaultStorage.Init()); + ReturnLogErrorOnFailure(mOperationalKeystore.Init(&mDefaultStorage)); chip::Controller::FactoryInitParams factoryInitParams; factoryInitParams.fabricIndependentStorage = &mDefaultStorage; + factoryInitParams.operationalKeystore = &mOperationalKeystore; // Init group data provider that will be used for all group keys and IPKs for the // chip-tool-configured fabrics. This is OK to do once since the fabric tables diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index 3fbe81781a15f8..1313fadeb1f664 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -23,6 +23,7 @@ #include #include #include +#include #pragma once @@ -115,6 +116,8 @@ class CHIPCommand : public Command PersistentStorage mDefaultStorage; PersistentStorage mCommissionerStorage; + chip::PersistentStorageOperationalKeystore mOperationalKeystore; + chip::Credentials::GroupDataProviderImpl mGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric }; CredentialIssuerCommands * mCredIssuerCmds; diff --git a/src/app/CASEClient.cpp b/src/app/CASEClient.cpp index 0abae541ef10d4..31564a43d904d4 100644 --- a/src/app/CASEClient.cpp +++ b/src/app/CASEClient.cpp @@ -30,6 +30,8 @@ CHIP_ERROR CASEClient::EstablishSession(PeerId peer, const Transport::PeerAddres const ReliableMessageProtocolConfig & remoteMRPConfig, SessionEstablishmentDelegate * delegate) { + VerifyOrReturnError(mInitParams.fabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + // Create a UnauthenticatedSession for CASE pairing. Optional session = mInitParams.sessionManager->CreateUnauthenticatedSession(peerAddress, remoteMRPConfig); VerifyOrReturnError(session.HasValue(), CHIP_ERROR_NO_MEMORY); @@ -45,7 +47,7 @@ CHIP_ERROR CASEClient::EstablishSession(PeerId peer, const Transport::PeerAddres mCASESession.SetGroupDataProvider(mInitParams.groupDataProvider); ReturnErrorOnFailure(mCASESession.EstablishSession( - *mInitParams.sessionManager, mInitParams.fabricTable, mInitParams.fabricIndex, peer.GetNodeId(), exchange, + *mInitParams.sessionManager, mInitParams.fabricTable, ScopedNodeId{ peer.GetNodeId(), mInitParams.fabricIndex }, exchange, mInitParams.sessionResumptionStorage, mInitParams.certificateValidityPolicy, delegate, mInitParams.mrpLocalConfig)); return CHIP_NO_ERROR; diff --git a/src/app/OperationalDeviceProxy.h b/src/app/OperationalDeviceProxy.h index dd13479d3a3a23..168181673c39cc 100644 --- a/src/app/OperationalDeviceProxy.h +++ b/src/app/OperationalDeviceProxy.h @@ -199,7 +199,7 @@ class DLL_EXPORT OperationalDeviceProxy : public DeviceProxy, } /** - * @brief Get the raw Fabric ID assigned to the device. + * @brief Get the fabricIndex */ FabricIndex GetFabricIndex() const { return mFabricIndex; } diff --git a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp index fa8d755fc23467..64aa0c9520954b 100644 --- a/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp +++ b/src/app/clusters/general-commissioning-server/general-commissioning-server.cpp @@ -155,6 +155,9 @@ bool emberAfGeneralCommissioningClusterArmFailSafeCallback(app::CommandHandler * FailSafeContext & failSafeContext = DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); Commands::ArmFailSafeResponse::Type response; + ChipLogProgress(FailSafe, "GeneralCommissioning: Received ArmFailSafe (%us)", + static_cast(commandData.expiryLengthSeconds)); + /* * If the fail-safe timer is not fully disarmed, don't allow arming a new fail-safe. * If the fail-safe timer was not currently armed, then the fail-safe timer SHALL be armed. @@ -214,7 +217,10 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( MATTER_TRACE_EVENT_SCOPE("CommissioningComplete", "GeneralCommissioning"); DeviceControlServer * server = &DeviceLayer::DeviceControlServer::DeviceControlSvr(); - const auto & failSafe = server->GetFailSafeContext(); + auto & failSafe = server->GetFailSafeContext(); + auto & fabricTable = Server::GetInstance().GetFabricTable(); + + ChipLogProgress(FailSafe, "GeneralCommissioning: Received CommissioningComplete"); Commands::CommissioningCompleteResponse::Type response; if (!failSafe.IsFailSafeArmed()) @@ -231,16 +237,34 @@ bool emberAfGeneralCommissioningClusterCommissioningCompleteCallback( !failSafe.MatchesFabricIndex(commandObj->GetAccessingFabricIndex())) { response.errorCode = CommissioningError::kInvalidAuthentication; + ChipLogError(FailSafe, "GeneralCommissioning: Got commissioning complete in invalid security context"); } else { + if (failSafe.NocCommandHasBeenInvoked()) + { + CHIP_ERROR err = fabricTable.CommitPendingFabricData(); + if (err != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "GeneralCommissioning: Failed to commit pending fabric data: %" CHIP_ERROR_FORMAT, + err.Format()); + } + else + { + ChipLogProgress(FailSafe, "GeneralCommissioning: Successfully commited pending fabric data"); + } + CheckSuccess(err, Failure); + } + /* * Pass fabric of commissioner to DeviceControlSvr. * This allows device to send messages back to commissioner. * Once bindings are implemented, this may no longer be needed. */ - CheckSuccess(server->CommissioningComplete(handle->AsSecureSession()->GetPeerNodeId(), handle->GetFabricIndex()), - Failure); + failSafe.DisarmFailSafe(); + CheckSuccess( + server->PostCommissioningCompleteEvent(handle->AsSecureSession()->GetPeerNodeId(), handle->GetFabricIndex()), + Failure); Breadcrumb::Set(commandPath.mEndpointId, 0); response.errorCode = CommissioningError::kOk; diff --git a/src/app/clusters/network-commissioning/network-commissioning.cpp b/src/app/clusters/network-commissioning/network-commissioning.cpp index 99c586f9c99678..b00ddc0bc08788 100644 --- a/src/app/clusters/network-commissioning/network-commissioning.cpp +++ b/src/app/clusters/network-commissioning/network-commissioning.cpp @@ -486,7 +486,7 @@ void Instance::OnResult(Status commissioningError, CharSpan debugText, int32_t i } if (commissioningError == Status::kSuccess) { - DeviceLayer::DeviceControlServer::DeviceControlSvr().ConnectNetworkForOperational( + DeviceLayer::DeviceControlServer::DeviceControlSvr().PostConnectedToOperationalNetworkEvent( ByteSpan(mLastNetworkID, mLastNetworkIDLen)); mLastConnectErrorValue.SetNull(); } 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 57daf899ddced5..667f7cb87d458d 100644 --- a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp +++ b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp @@ -281,7 +281,7 @@ CHIP_ERROR DeleteFabricFromTable(FabricIndex fabricIndex) return CHIP_NO_ERROR; } -void CleanupFabricContext(SessionManager & sessionMgr, FabricIndex fabricIndex) +void CleanupSessionsForFabric(SessionManager & sessionMgr, FabricIndex fabricIndex) { InteractionModelEngine::GetInstance()->CloseTransactionsFromFabricIndex(fabricIndex); sessionMgr.ExpireAllPairingsForFabric(fabricIndex); @@ -308,9 +308,12 @@ void FailSafeCleanup(const chip::DeviceLayer::ChipDeviceEvent * event) } SessionManager & sessionMgr = Server::GetInstance().GetSecureSessionManager(); - CleanupFabricContext(sessionMgr, fabricIndex); + CleanupSessionsForFabric(sessionMgr, fabricIndex); } + auto & fabricTable = Server::GetInstance().GetFabricTable(); + fabricTable.RevertPendingFabricData(); + // If an AddNOC command had been successfully invoked, achieve the equivalent effect of invoking the RemoveFabric command // against the Fabric Index stored in the Fail-Safe Context for the Fabric Index that was the subject of the AddNOC // command. @@ -343,27 +346,13 @@ void FailSafeCleanup(const chip::DeviceLayer::ChipDeviceEvent * event) } } -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()); - } -} - void OnPlatformEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg) { if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired) { + ChipLogError(Zcl, "OpCreds: Got FailSafeTimerExpired"); FailSafeCleanup(event); } - if (event->Type == DeviceLayer::DeviceEventType::kCommissioningComplete) - { - CommissioningComplete(event); - } } } // anonymous namespace @@ -485,7 +474,7 @@ class FabricCleanupExchangeDelegate : public chip::Messaging::ExchangeDelegate { SessionManager * sessionManager = ec->GetExchangeMgr()->GetSessionManager(); FabricIndex currentFabricIndex = ec->GetSessionHandle()->GetFabricIndex(); - CleanupFabricContext(*sessionManager, currentFabricIndex); + CleanupSessionsForFabric(*sessionManager, currentFabricIndex); } }; @@ -551,7 +540,7 @@ bool emberAfOperationalCredentialsClusterRemoveFabricCallback(app::CommandHandle else { SessionManager * sessionManager = ec->GetExchangeMgr()->GetSessionManager(); - CleanupFabricContext(*sessionManager, fabricBeingRemoved); + CleanupSessionsForFabric(*sessionManager, fabricBeingRemoved); } } return true; @@ -694,6 +683,7 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co FabricIndex fabricIndex = 0; Credentials::GroupDataProvider::KeySet keyset; FabricInfo * newFabricInfo = nullptr; + auto & fabricTable = Server::GetInstance().GetFabricTable(); auto * secureSession = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession(); FailSafeContext & failSafeContext = DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); @@ -714,6 +704,10 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), nonDefaultStatus = Status::ConstraintError); + // Must have had a previous CSR request, not tagged for UpdateNOC + VerifyOrExit(fabricTable.HasPendingOperationalKey(), nocResponse = OperationalCertStatus::kMissingCsr); + VerifyOrExit(!failSafeContext.IsCsrRequestForUpdateNoc(), nonDefaultStatus = Status::ConstraintError); + // Internal error that would prevent IPK from being added VerifyOrExit(groupDataProvider != nullptr, nonDefaultStatus = Status::Failure); @@ -730,9 +724,26 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co gFabricBeingCommissioned.SetVendorId(adminVendorId); - err = Server::GetInstance().GetFabricTable().AddNewFabric(gFabricBeingCommissioned, &fabricIndex); + // TODO(#16443): Stop committing fabric table right away, only do it on commissioning complete + err = fabricTable.AddNewFabric(gFabricBeingCommissioned, &fabricIndex); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); + // Activate the operational key previously generated + { + Crypto::P256PublicKey nocSubjectPublicKey; + Credentials::P256PublicKeySpan nocSubjectPublicKeySpan; + + err = Credentials::ExtractPublicKeyFromChipCert(NOCValue, nocSubjectPublicKeySpan); + VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = OperationalCertStatus::kInvalidNOC); + nocSubjectPublicKey = Crypto::P256PublicKey(nocSubjectPublicKeySpan); + + err = fabricTable.ActivatePendingOperationalKey(nocSubjectPublicKey); + VerifyOrExit(err != CHIP_ERROR_INVALID_PUBLIC_KEY, nocResponse = OperationalCertStatus::kInvalidPublicKey); + + // Other errors that are not CHIP_ERROR_INVALID_PUBLIC_KEY are internal failures + VerifyOrExit(err == CHIP_NO_ERROR, nonDefaultStatus = Status::Failure); + } + // Set the Identity Protection Key (IPK) // The IPK SHALL be the operational group key under GroupKeySetID of 0 keyset.keyset_id = Credentials::GroupDataProvider::kIdentityProtectionKeySetId; @@ -740,7 +751,7 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co keyset.num_keys_used = 1; memcpy(keyset.epoch_keys[0].key, ipkValue.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES); - newFabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); + newFabricInfo = fabricTable.FindFabricWithIndex(fabricIndex); VerifyOrExit(newFabricInfo != nullptr, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INTERNAL)); err = newFabricInfo->GetCompressedId(compressed_fabric_id); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); @@ -777,7 +788,7 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co err = failSafeContext.SetAddNocCommandInvoked(fabricIndex); if (err != CHIP_NO_ERROR) { - Server::GetInstance().GetFabricTable().Delete(fabricIndex); + fabricTable.Delete(fabricIndex); nocResponse = ConvertToNOCResponseStatus(err); SuccessOrExit(err); } @@ -829,6 +840,7 @@ bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * ChipLogProgress(Zcl, "OpCreds: Received an UpdateNOC command"); + auto & fabricTable = Server::GetInstance().GetFabricTable(); FailSafeContext & failSafeContext = DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); FabricInfo * fabric = RetrieveCurrentFabric(commandObj); ByteSpan rcac; @@ -841,6 +853,10 @@ bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), nonDefaultStatus = Status::ConstraintError); + // Must have had a previous CSR request, tagged for UpdateNOC + VerifyOrExit(fabricTable.HasPendingOperationalKey(), nocResponse = OperationalCertStatus::kMissingCsr); + VerifyOrExit(failSafeContext.IsCsrRequestForUpdateNoc(), nonDefaultStatus = Status::ConstraintError); + // If current fabric is not available, command was invoked over PASE which is not legal VerifyOrExit(fabric != nullptr, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INSUFFICIENT_PRIVILEGE)); fabricIndex = fabric->GetFabricIndex(); @@ -870,9 +886,26 @@ bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); } - err = Server::GetInstance().GetFabricTable().UpdateFabric(fabricIndex, gFabricBeingCommissioned); + // TODO(#18633): Stop committing fabric table right away, only do it on commissioning complete + err = fabricTable.UpdateFabric(fabricIndex, gFabricBeingCommissioned); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); + // Activate the operational key previously generated + { + Crypto::P256PublicKey nocSubjectPublicKey; + Credentials::P256PublicKeySpan nocSubjectPublicKeySpan; + + err = Credentials::ExtractPublicKeyFromChipCert(NOCValue, nocSubjectPublicKeySpan); + VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = OperationalCertStatus::kInvalidNOC); + nocSubjectPublicKey = Crypto::P256PublicKey(nocSubjectPublicKeySpan); + + err = fabricTable.ActivatePendingOperationalKey(nocSubjectPublicKey); + VerifyOrExit(err != CHIP_ERROR_INVALID_PUBLIC_KEY, nocResponse = OperationalCertStatus::kInvalidPublicKey); + + // Other errors that are not CHIP_ERROR_INVALID_PUBLIC_KEY are internal failures + VerifyOrExit(err == CHIP_NO_ERROR, nonDefaultStatus = Status::Failure); + } + // Flag on the fail-safe context that the UpdateNOC command was invoked. err = failSafeContext.SetUpdateNocCommandInvoked(); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); @@ -1044,48 +1077,38 @@ bool emberAfOperationalCredentialsClusterCSRRequestCallback(app::CommandHandler MATTER_TRACE_EVENT_SCOPE("CSRRequest", "OperationalCredentials"); ChipLogProgress(Zcl, "OpCreds: Received a CSRRequest command"); - Platform::ScopedMemoryBuffer csr; chip::Platform::ScopedMemoryBuffer nocsrElements; MutableByteSpan nocsrElementsSpan; - auto finalStatus = Status::Failure; // Start with CHIP_ERROR_INVALID_ARGUMENT so that cascading errors yield correct // logs by the end. We use finalStatus as our overall success marker, not error CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT; + auto & fabricTable = Server::GetInstance().GetFabricTable(); FailSafeContext & failSafeContext = DeviceControlServer::DeviceControlSvr().GetFailSafeContext(); - auto & CSRNonce = commandData.CSRNonce; + auto & CSRNonce = commandData.CSRNonce; + bool isForUpdateNoc = commandData.isForUpdateNOC.ValueOr(false); + + failSafeContext.SetCsrRequestForUpdateNoc(isForUpdateNoc); + FabricInfo * fabricInfo = RetrieveCurrentFabric(commandObj); + VerifyOrExit(CSRNonce.size() == Credentials::kExpectedAttestationNonceSize, finalStatus = Status::InvalidCommand); + // If current fabric is not available, command was invoked over PASE which is not legal if IsForUpdateNOC is true. + VerifyOrExit(!isForUpdateNoc || (fabricInfo != nullptr), finalStatus = Status::InvalidCommand); + VerifyOrExit(failSafeContext.IsFailSafeArmed(commandObj->GetAccessingFabricIndex()), finalStatus = Status::FailsafeRequired); VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), finalStatus = Status::ConstraintError); // Prepare NOCSRElements structure { -#ifdef ENABLE_HSM_CASE_OPS_KEY - Crypto::P256KeypairHSM keypair; -#else - Crypto::P256Keypair keypair; -#endif - size_t csrLength = Crypto::kMAX_CSR_Length; + constexpr size_t csrLength = Crypto::kMAX_CSR_Length; size_t nocsrLengthEstimate = 0; ByteSpan kNoVendorReserved; - - // Always generate a new operational keypair for any new CSRRequest - if (gFabricBeingCommissioned.GetOperationalKey() != nullptr) - { - gFabricBeingCommissioned.GetOperationalKey()->Clear(); - } - -#ifdef ENABLE_HSM_CASE_OPS_KEY - keypair.CreateOperationalKey(gFabricBeingCommissioned.GetFabricIndex()); -#else - keypair.Initialize(); -#endif - err = gFabricBeingCommissioned.SetOperationalKeypair(&keypair); - VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure); + Platform::ScopedMemoryBuffer csr; + MutableByteSpan csrSpan; // Generate the actual CSR from the ephemeral key if (!csr.Alloc(csrLength)) @@ -1093,19 +1116,31 @@ bool emberAfOperationalCredentialsClusterCSRRequestCallback(app::CommandHandler err = CHIP_ERROR_NO_MEMORY; VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::ResourceExhausted); } + csrSpan = MutableByteSpan{ csr.Get(), csrLength }; + + Optional fabricIndexForCsr; + if (isForUpdateNoc) + { + fabricIndexForCsr.SetValue(commandObj->GetAccessingFabricIndex()); + } + + err = fabricTable.AllocatePendingOperationalKey(fabricIndexForCsr, csrSpan); + + if (csrSpan.size() > Crypto::kMAX_CSR_Length) + { + err = CHIP_ERROR_INTERNAL; + } - err = gFabricBeingCommissioned.GetOperationalKey()->NewCertificateSigningRequest(csr.Get(), csrLength); if (err != CHIP_NO_ERROR) { - ChipLogError(Zcl, "OpCreds: NewCertificateSigningRequest returned %" CHIP_ERROR_FORMAT, err.Format()); + ChipLogError(Zcl, "OpCreds: AllocatePendingOperationalKey returned %" CHIP_ERROR_FORMAT, err.Format()); VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure); } - ChipLogProgress(Zcl, "OpCreds: NewCertificateSigningRequest succeeded"); - VerifyOrExit(csrLength <= Crypto::kMAX_CSR_Length, finalStatus = Status::Failure); + ChipLogProgress(Zcl, "OpCreds: AllocatePendingOperationalKey succeeded"); // Encode the NOCSR elements with the CSR and Nonce - nocsrLengthEstimate = TLV::EstimateStructOverhead(csrLength, // CSR buffer + nocsrLengthEstimate = TLV::EstimateStructOverhead(csrSpan.size(), // CSR buffer CSRNonce.size(), // CSR Nonce 0u // no vendor reserved data ); @@ -1118,8 +1153,8 @@ bool emberAfOperationalCredentialsClusterCSRRequestCallback(app::CommandHandler nocsrElementsSpan = MutableByteSpan{ nocsrElements.Get(), nocsrLengthEstimate }; - err = Credentials::ConstructNOCSRElements(ByteSpan{ csr.Get(), csrLength }, CSRNonce, kNoVendorReserved, kNoVendorReserved, - kNoVendorReserved, nocsrElementsSpan); + err = Credentials::ConstructNOCSRElements(ByteSpan{ csrSpan.data(), csrSpan.size() }, CSRNonce, kNoVendorReserved, + kNoVendorReserved, kNoVendorReserved, nocsrElementsSpan); VerifyOrExit((err == CHIP_NO_ERROR) && (nocsrElementsSpan.size() <= kMaxRspLen), finalStatus = Status::Failure); } diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 8143bceabba693..f57e1f89363ed2 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -124,6 +125,7 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) // Initialize PersistentStorageDelegate-based storage mDeviceStorage = initParams.persistentStorageDelegate; mSessionResumptionStorage = initParams.sessionResumptionStorage; + mOperationalKeystore = initParams.operationalKeystore; mCertificateValidityPolicy = initParams.certificateValidityPolicy; @@ -132,7 +134,7 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) SuccessOrExit(mAttributePersister.Init(mDeviceStorage)); SetAttributePersistenceProvider(&mAttributePersister); - err = mFabrics.Init(mDeviceStorage); + err = mFabrics.Init(mDeviceStorage, mOperationalKeystore); SuccessOrExit(err); SuccessOrExit(err = mAccessControl.Init(initParams.accessDelegate, sDeviceTypeResolver)); @@ -158,6 +160,9 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) // This initializes clusters, so should come after lower level initialization. InitDataModelHandler(&mExchangeMgr); + // Clean-up previously standing fail-safes + InitFailSafe(); + // Init transport before operations with secure session mgr. err = mTransports.Init(UdpListenParameters(DeviceLayer::UDPEndPointManager()) .SetAddressType(IPAddressType::kIPv6) @@ -297,6 +302,39 @@ CHIP_ERROR Server::Init(const ServerInitParams & initParams) return err; } +void Server::InitFailSafe() +{ + bool failSafeArmed = false; + + CHIP_ERROR err = CHIP_NO_ERROR; + + // If the fail-safe was armed when the device last shutdown, initiate cleanup based on the pending Fail Safe Context with + // which the fail-safe timer was armed. + if (DeviceLayer::ConfigurationMgr().GetFailSafeArmed(failSafeArmed) == CHIP_NO_ERROR && failSafeArmed) + { + FabricIndex fabricIndex; + bool addNocCommandInvoked; + bool updateNocCommandInvoked; + + ChipLogProgress(AppServer, "Detected fail-safe armed on reboot"); + + err = DeviceLayer::FailSafeContext::LoadFromStorage(fabricIndex, addNocCommandInvoked, updateNocCommandInvoked); + if (err == CHIP_NO_ERROR) + { + DeviceLayer::DeviceControlServer::DeviceControlSvr().GetFailSafeContext().ScheduleFailSafeCleanup( + fabricIndex, addNocCommandInvoked, updateNocCommandInvoked); + } + else + { + // This should not happen, but we should not fail system init based on it! + ChipLogError(DeviceLayer, "Failed to load fail-safe context from storage (err= %" CHIP_ERROR_FORMAT "), cleaning-up!", + err.Format()); + (void) DeviceLayer::ConfigurationMgr().SetFailSafeArmed(false); + err = CHIP_NO_ERROR; + } + } +} + void Server::RejoinExistingMulticastGroups() { ChipLogProgress(AppServer, "Joining Multicast groups"); diff --git a/src/app/server/Server.h b/src/app/server/Server.h index b8e21e9058e619..a5b084cce6e56a 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -112,6 +114,8 @@ struct ServerInitParams // Optional. Support test event triggers when provided. Must be initialized before being // provided. TestEventTriggerDelegate * testEventTriggerDelegate = nullptr; + // Operational keystore with access to the operational keys: MUST be injected. + Crypto::OperationalKeystore * operationalKeystore = nullptr; }; /** @@ -161,6 +165,7 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams virtual CHIP_ERROR InitializeStaticResourcesBeforeServerInit() { static chip::KvsPersistentStorageDelegate sKvsPersistenStorageDelegate; + static chip::PersistentStorageOperationalKeystore sPersistentStorageOperationalKeystore; static chip::Credentials::GroupDataProviderImpl sGroupDataProvider; #if CHIP_CONFIG_ENABLE_SESSION_RESUMPTION static chip::SimpleSessionResumptionStorage sSessionResumptionStorage; @@ -168,17 +173,30 @@ struct CommonCaseDeviceServerInitParams : public ServerInitParams static chip::app::DefaultAclStorage sAclStorage; // KVS-based persistent storage delegate injection - chip::DeviceLayer::PersistedStorage::KeyValueStoreManager & kvsManager = DeviceLayer::PersistedStorage::KeyValueStoreMgr(); - ReturnErrorOnFailure(sKvsPersistenStorageDelegate.Init(&kvsManager)); - this->persistentStorageDelegate = &sKvsPersistenStorageDelegate; + if (persistentStorageDelegate == nullptr) + { + chip::DeviceLayer::PersistedStorage::KeyValueStoreManager & kvsManager = + DeviceLayer::PersistedStorage::KeyValueStoreMgr(); + ReturnErrorOnFailure(sKvsPersistenStorageDelegate.Init(&kvsManager)); + this->persistentStorageDelegate = &sKvsPersistenStorageDelegate; + } + + // PersistentStorageDelegate "software-based" operational key access injection + if (this->operationalKeystore == nullptr) + { + // WARNING: PersistentStorageOperationalKeystore::Finish() is never called. It's fine for + // for examples and for now. + ReturnErrorOnFailure(sPersistentStorageOperationalKeystore.Init(this->persistentStorageDelegate)); + this->operationalKeystore = &sPersistentStorageOperationalKeystore; + } // Group Data provider injection - sGroupDataProvider.SetStorageDelegate(&sKvsPersistenStorageDelegate); + sGroupDataProvider.SetStorageDelegate(this->persistentStorageDelegate); ReturnErrorOnFailure(sGroupDataProvider.Init()); this->groupDataProvider = &sGroupDataProvider; #if CHIP_CONFIG_ENABLE_SESSION_RESUMPTION - ReturnErrorOnFailure(sSessionResumptionStorage.Init(&sKvsPersistenStorageDelegate)); + ReturnErrorOnFailure(sSessionResumptionStorage.Init(this->persistentStorageDelegate)); this->sessionResumptionStorage = &sSessionResumptionStorage; #else this->sessionResumptionStorage = nullptr; @@ -251,6 +269,8 @@ class Server TestEventTriggerDelegate * GetTestEventTriggerDelegate() { return mTestEventTriggerDelegate; } + Crypto::OperationalKeystore * GetOperationalKeystore() { return mOperationalKeystore; } + /** * This function send the ShutDown event before stopping * the event loop. @@ -268,6 +288,8 @@ class Server static Server sServer; + void InitFailSafe(); + class GroupDataProviderListener final : public Credentials::GroupDataProvider::GroupListener { public: @@ -407,6 +429,7 @@ class Server app::AclStorage * mAclStorage; TestEventTriggerDelegate * mTestEventTriggerDelegate; + Crypto::OperationalKeystore * mOperationalKeystore; uint16_t mOperationalServicePort; uint16_t mUserDirectedCommissioningPort; diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index e215fc7fa92c5c..870ffdebfaf60e 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -152,11 +152,18 @@ CHIP_ERROR DeviceController::InitControllerNOCChain(const ControllerInitParams & Credentials::P256PublicKeySpan rootPublicKey; FabricId fabricId; + // There are three possibilities here in terms of what happens with our + // operational key: + // 1) We have an externally owned operational keypair. + // 2) We have an operational keypair that the fabric table should clone via + // serialize/deserialize. + // 3) We have no keypair at all, and the fabric table has been initialized + // with a key store. if (params.hasExternallyOwnedOperationalKeypair) { ReturnErrorOnFailure(newFabric.SetExternallyOwnedOperationalKeypair(params.operationalKeypair)); } - else + else if (params.operationalKeypair) { ReturnErrorOnFailure(newFabric.SetOperationalKeypair(params.operationalKeypair)); } diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index fa63a50f702b69..63343aede01464 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -60,6 +60,7 @@ CHIP_ERROR DeviceControllerFactory::Init(FactoryInitParams params) // created-but-shut-down system state. mListenPort = params.listenPort; mFabricIndependentStorage = params.fabricIndependentStorage; + mOperationalKeystore = params.operationalKeystore; mEnableServerInteractions = params.enableServerInteractions; CHIP_ERROR err = InitSystemState(params); @@ -83,6 +84,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState() params.enableServerInteractions = mEnableServerInteractions; params.groupDataProvider = mSystemState->GetGroupDataProvider(); params.fabricTable = mSystemState->Fabrics(); + params.operationalKeystore = mOperationalKeystore; } return InitSystemState(params); @@ -162,7 +164,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) if (stateParams.fabricTable == nullptr) { stateParams.fabricTable = tempFabricTable = chip::Platform::New(); - ReturnErrorOnFailure(stateParams.fabricTable->Init(params.fabricIndependentStorage)); + ReturnErrorOnFailure(stateParams.fabricTable->Init(params.fabricIndependentStorage, params.operationalKeystore)); } ReturnErrorOnFailure(sessionResumptionStorage->Init(params.fabricIndependentStorage)); @@ -318,6 +320,7 @@ void DeviceControllerFactory::Shutdown() mSystemState = nullptr; } mFabricIndependentStorage = nullptr; + mOperationalKeystore = nullptr; } CHIP_ERROR DeviceControllerSystemState::Shutdown() diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index aee73089d0a660..e090926811d94b 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -77,8 +77,8 @@ struct SetupParams CommissioningDelegate * defaultCommissioner = nullptr; }; -// TODO everything other than the fabric storage and group data provider here should be removed. -// We're blocked because of the need to support !CHIP_DEVICE_LAYER +// TODO everything other than the fabric storage, group data provider and OperationalKeystore +// here should be removed. We're blocked because of the need to support !CHIP_DEVICE_LAYER struct FactoryInitParams { System::Layer * systemLayer = nullptr; @@ -88,6 +88,7 @@ struct FactoryInitParams Inet::EndPointManager * tcpEndPointManager = nullptr; Inet::EndPointManager * udpEndPointManager = nullptr; FabricTable * fabricTable = nullptr; + OperationalKeystore * operationalKeystore = nullptr; #if CONFIG_NETWORK_LAYER_BLE Ble::BleLayer * bleLayer = nullptr; #endif @@ -213,6 +214,7 @@ class DeviceControllerFactory uint16_t mListenPort; DeviceControllerSystemState * mSystemState = nullptr; PersistentStorageDelegate * mFabricIndependentStorage = nullptr; + Crypto::OperationalKeystore * mOperationalKeystore = nullptr; bool mEnableServerInteractions = false; }; diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index 2a38d3923a7d3b..d4246a4337f40b 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -51,14 +51,6 @@ static_assert(CHIP_CONFIG_MAX_FABRICS <= kMaxValidFabricIndex, "Max fabric count constexpr TLV::Tag kVendorIdTag = TLV::ContextTag(0); constexpr TLV::Tag kFabricLabelTag = TLV::ContextTag(1); -// Tags for our operational keypair storage. -constexpr TLV::Tag kOpKeyVersionTag = TLV::ContextTag(0); -constexpr TLV::Tag kOpKeyDataTag = TLV::ContextTag(1); - -// If this version grows beyond UINT16_MAX, adjust OpKeypairTLVMaxSize -// accordingly. -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); @@ -98,32 +90,6 @@ CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) ReturnErrorOnFailure( storage->SyncSetKeyValue(keyAlloc.FabricNOC(mFabricIndex), mNOCCert.data(), static_cast(mNOCCert.size()))); - { - uint8_t buf[OpKeyTLVMaxSize()]; - TLV::TLVWriter writer; - writer.Init(buf); - - TLV::TLVType outerType; - ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); - - ReturnErrorOnFailure(writer.Put(kOpKeyVersionTag, kOpKeyVersion)); - - // If key storage is externally managed, key is not stored here, - // and when loading is done later, it will be ignored. - if (!mHasExternallyOwnedOperationalKey && (mOperationalKey != nullptr)) - { - Crypto::P256SerializedKeypair serializedOpKey; - ReturnErrorOnFailure(mOperationalKey->Serialize(serializedOpKey)); - ReturnErrorOnFailure(writer.Put(kOpKeyDataTag, ByteSpan(serializedOpKey.Bytes(), serializedOpKey.Length()))); - } - - ReturnErrorOnFailure(writer.EndContainer(outerType)); - - const auto opKeyLength = writer.GetLengthWritten(); - VerifyOrReturnError(CanCastTo(opKeyLength), CHIP_ERROR_BUFFER_TOO_SMALL); - ReturnErrorOnFailure(storage->SyncSetKeyValue(keyAlloc.FabricOpKey(mFabricIndex), buf, static_cast(opKeyLength))); - } - { uint8_t buf[MetadataTLVMaxSize()]; TLV::TLVWriter writer; @@ -144,6 +110,8 @@ CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) storage->SyncSetKeyValue(keyAlloc.FabricMetadata(mFabricIndex), buf, static_cast(metadataLength))); } + // NOTE: Operational Key is never saved to storage here. See OperationalKeystore interface for how it is accessed + return CHIP_NO_ERROR; } @@ -192,60 +160,6 @@ CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage) ReturnErrorOnFailure(SetNOCCert(nocCert)); } - { - // Use a CapacityBoundBuffer to get RAII secret data clearing on scope exit. - Crypto::CapacityBoundBuffer buf; - uint16_t size = static_cast(buf.Capacity()); - ReturnErrorOnFailure(storage->SyncGetKeyValue(keyAlloc.FabricOpKey(mFabricIndex), buf.Bytes(), size)); - buf.SetLength(static_cast(size)); - - TLV::ContiguousBufferTLVReader reader; - reader.Init(buf.Bytes(), buf.Length()); - - ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); - TLV::TLVType containerType; - ReturnErrorOnFailure(reader.EnterContainer(containerType)); - - ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag)); - uint16_t opKeyVersion; - ReturnErrorOnFailure(reader.Get(opKeyVersion)); - VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH); - - CHIP_ERROR err = reader.Next(kOpKeyDataTag); - if (err == CHIP_NO_ERROR) - { - ByteSpan keyData; - ReturnErrorOnFailure(reader.GetByteView(keyData)); - - // Unfortunately, we have to copy the data into a P256SerializedKeypair. - Crypto::P256SerializedKeypair serializedOpKey; - VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); - - memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size()); - serializedOpKey.SetLength(keyData.size()); - - if (mOperationalKey == nullptr) - { -#ifdef ENABLE_HSM_CASE_OPS_KEY - mOperationalKey = chip::Platform::New(); -#else - mOperationalKey = chip::Platform::New(); -#endif - } - VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY); - ReturnErrorOnFailure(mOperationalKey->Deserialize(serializedOpKey)); - } - else - { - // Key was absent: set mOperationalKey to null, for another caller to set - // it. This may happen if externally owned. - mOperationalKey = nullptr; - } - - ReturnErrorOnFailure(reader.ExitContainer(containerType)); - ReturnErrorOnFailure(reader.VerifyEndOfContainer()); - } - { uint8_t buf[MetadataTLVMaxSize()]; uint16_t size = sizeof(buf); @@ -271,6 +185,8 @@ CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage) ReturnErrorOnFailure(reader.VerifyEndOfContainer()); } + // NOTE: Operational Key is never loaded here. See OperationalKeystore interface for how it is accessed + return CHIP_NO_ERROR; } @@ -304,8 +220,7 @@ CHIP_ERROR FabricInfo::DeleteFromStorage(PersistentStorageDelegate * storage, Fa // Try to delete all the state even if one of the deletes fails. typedef const char * (DefaultStorageKeyAllocator::*KeyGetter)(FabricIndex); constexpr KeyGetter keyGetters[] = { &DefaultStorageKeyAllocator::FabricNOC, &DefaultStorageKeyAllocator::FabricICAC, - &DefaultStorageKeyAllocator::FabricRCAC, &DefaultStorageKeyAllocator::FabricMetadata, - &DefaultStorageKeyAllocator::FabricOpKey }; + &DefaultStorageKeyAllocator::FabricRCAC, &DefaultStorageKeyAllocator::FabricMetadata }; CHIP_ERROR prevDeleteErr = CHIP_NO_ERROR; @@ -320,8 +235,8 @@ CHIP_ERROR FabricInfo::DeleteFromStorage(PersistentStorageDelegate * storage, Fa } if (prevDeleteErr != CHIP_NO_ERROR) { - ChipLogDetail(FabricProvisioning, "Error deleting part of fabric %d: %" CHIP_ERROR_FORMAT, fabricIndex, - prevDeleteErr.Format()); + ChipLogError(FabricProvisioning, "Error deleting part of fabric %d: %" CHIP_ERROR_FORMAT, fabricIndex, + prevDeleteErr.Format()); } return prevDeleteErr; } @@ -366,6 +281,57 @@ CHIP_ERROR FabricInfo::SetExternallyOwnedOperationalKeypair(P256Keypair * keyPai return CHIP_NO_ERROR; } +CHIP_ERROR FabricInfo::ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, + FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy, + PeerId & outOperationalId, FabricId & outFabricId, + Crypto::P256PublicKey & outNocPubkey) +{ + Credentials::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; + + ChipLogProgress(FabricProvisioning, "Validating NOC chain"); + CHIP_ERROR err = FabricInfo::VerifyCredentials(noc, icac, rcac, validContext, outOperationalId, outFabricId, outNocPubkey); + if (err != CHIP_NO_ERROR && err != CHIP_ERROR_WRONG_NODE_ID) + { + err = CHIP_ERROR_UNSUPPORTED_CERT_FORMAT; + } + if (err != CHIP_NO_ERROR) + { + ChipLogError(FabricProvisioning, "Failed NOC chain validation: %" CHIP_ERROR_FORMAT, err.Format()); + } + ReturnErrorOnFailure(err); + + // Validate fabric ID match for cases like UpdateNOC. + if (existingFabricId != kUndefinedFabricId) + { + VerifyOrReturnError(existingFabricId == outFabricId, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); + } + + ChipLogProgress(FabricProvisioning, "NOC chain validation successful"); + return CHIP_NO_ERROR; +} + void FabricInfo::ReleaseCert(MutableByteSpan & cert) { if (cert.data() != nullptr) @@ -394,6 +360,13 @@ CHIP_ERROR FabricInfo::SetCert(MutableByteSpan & dstCert, const ByteSpan & srcCe return CHIP_NO_ERROR; } +CHIP_ERROR FabricInfo::SignWithOpKeypair(ByteSpan message, P256ECDSASignature & outSignature) const +{ + VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_KEY_NOT_FOUND); + + return mOperationalKey->ECDSA_sign_msg(message.data(), message.size(), outSignature); +} + CHIP_ERROR FabricInfo::VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, ValidationContext & context, PeerId & nocPeerId, FabricId & fabricId, Crypto::P256PublicKey & nocPubkey) const { @@ -518,6 +491,24 @@ FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex) return nullptr; } +const FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex) const +{ + for (const auto & fabric : mStates) + { + if (!fabric.IsInitialized()) + { + continue; + } + + if (fabric.GetFabricIndex() == fabricIndex) + { + return &fabric; + } + } + + return nullptr; +} + FabricInfo * FabricTable::FindFabricWithCompressedId(CompressedFabricId fabricId) { for (auto & fabric : mStates) @@ -583,65 +574,37 @@ CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric) 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; + auto * operationalKey = newFabric.mOperationalKey; - // Make sure to not modify any of our state until VerifyCredentials passes. + // Make sure to not modify any of our state until ValidateIncomingNOCChain passes. + P256PublicKey pubkey; PeerId operationalId; FabricId fabricId; - 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) - { - err = CHIP_ERROR_UNSUPPORTED_CERT_FORMAT; - } - ReturnErrorOnFailure(err); - - auto * operationalKey = newFabric.GetOperationalKey(); - if (operationalKey == nullptr) - { - return CHIP_ERROR_INCORRECT_STATE; - } - // Verify that public key in NOC matches public key generated by node and sent in CSRResponse message. - VerifyOrReturnError(operationalKey->Pubkey().Length() == pubkey.Length(), CHIP_ERROR_INVALID_PUBLIC_KEY); - VerifyOrReturnError(memcmp(operationalKey->Pubkey().ConstBytes(), pubkey.Bytes(), pubkey.Length()) == 0, - CHIP_ERROR_INVALID_PUBLIC_KEY); + ReturnErrorOnFailure(ValidateIncomingNOCChain(newFabric.mNOCCert, newFabric.mICACert, newFabric.mRootCert, mFabricId, policy, + operationalId, fabricId, pubkey)); - if (mFabricId != kUndefinedFabricId) + if (operationalKey != nullptr) { - VerifyOrReturnError(mFabricId == fabricId, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); - } - - if (newFabric.mHasExternallyOwnedOperationalKey) - { - ReturnErrorOnFailure(SetExternallyOwnedOperationalKeypair(operationalKey)); - } - else - { - ReturnErrorOnFailure(SetOperationalKeypair(operationalKey)); + // Verify that public key in NOC matches public key of the provided keypair. + // When operational key is not injected (e.g. when mOperationalKeystore != nullptr) + // the check is done by the keystore in `ActivatePendingOperationalKey`. + VerifyOrReturnError(operationalKey->Pubkey().Length() == pubkey.Length(), CHIP_ERROR_INVALID_PUBLIC_KEY); + VerifyOrReturnError(memcmp(operationalKey->Pubkey().ConstBytes(), pubkey.ConstBytes(), pubkey.Length()) == 0, + CHIP_ERROR_INVALID_PUBLIC_KEY); + + if (newFabric.mHasExternallyOwnedOperationalKey) + { + ReturnErrorOnFailure(SetExternallyOwnedOperationalKeypair(operationalKey)); + } + else if (operationalKey != nullptr) + { + ReturnErrorOnFailure(SetOperationalKeypair(operationalKey)); + } + else + { + return CHIP_ERROR_INCORRECT_STATE; + } } SetRootCert(newFabric.mRootCert); @@ -658,6 +621,27 @@ CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric, Credentials::Certif return CHIP_NO_ERROR; } +CHIP_ERROR FabricInfo::TestOnlyBuildFabric(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan nocKey) +{ + Reset(); + + ReturnErrorOnFailure(SetRootCert(rootCert)); + ReturnErrorOnFailure(SetICACert(icacCert)); + ReturnErrorOnFailure(SetNOCCert(nocCert)); + + // NOTE: this requres ENABLE_HSM_CASE_OPS_KEY is not defined + P256SerializedKeypair opKeysSerialized; + memcpy(static_cast(opKeysSerialized), nocKey.data(), nocKey.size()); + ReturnErrorOnFailure(opKeysSerialized.SetLength(nocKey.size())); + + P256Keypair opKey; + ReturnErrorOnFailure(opKey.Deserialize(opKeysSerialized)); + ReturnErrorOnFailure(SetOperationalKeypair(&opKey)); + + // NOTE: mVendorId and mFabricLabel are not initialized, because they are not used in tests. + return CHIP_NO_ERROR; +} + CHIP_ERROR FabricTable::AddNewFabricForTest(FabricInfo & newFabric, FabricIndex * outputIndex) { VerifyOrReturnError(outputIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -805,13 +789,29 @@ CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex) FabricInfo * fabric = FindFabricWithIndex(fabricIndex); bool fabricIsInitialized = fabric != nullptr && fabric->IsInitialized(); CHIP_ERROR err = FabricInfo::DeleteFromStorage(mStorage, fabricIndex); // Delete from storage regardless + + CHIP_ERROR opKeyErr = CHIP_NO_ERROR; + if (mOperationalKeystore != nullptr) + { + opKeyErr = mOperationalKeystore->RemoveOpKeypairForFabric(fabricIndex); + // Not having found data is not an error, we may just have gotten here + // on a fail-safe expiry after `RevertPendingFabricData`. + if (opKeyErr == CHIP_ERROR_INVALID_FABRIC_INDEX) + { + opKeyErr = CHIP_NO_ERROR; + } + } + if (!fabricIsInitialized) { // Make sure to return the error our API promises, not whatever storage // chose to return. return CHIP_ERROR_NOT_FOUND; } + + // TODO: The error chain below can cause partial state storage. We must refactor. ReturnErrorOnFailure(err); + ReturnErrorOnFailure(opKeyErr); // Since fabricIsInitialized was true, fabric is not null. fabric->Reset(); @@ -869,7 +869,7 @@ CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage) VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); mStorage = storage; - ChipLogDetail(FabricProvisioning, "Init fabric pairing table with server storage"); + ChipLogDetail(FabricProvisioning, "Initializing FabricTable from persistent 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. @@ -909,6 +909,12 @@ CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage) return CHIP_NO_ERROR; } +CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage, OperationalKeystore * operationalKeystore) +{ + mOperationalKeystore = operationalKeystore; + return Init(storage); +} + CHIP_ERROR FabricTable::AddFabricDelegate(FabricTable::Delegate * delegate) { VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); @@ -1069,6 +1075,21 @@ CHIP_ERROR FabricTable::StoreFabricIndexInfo() const return CHIP_NO_ERROR; } +void FabricTable::EnsureNextAvailableFabricIndexUpdated() +{ + if (!mNextAvailableFabricIndex.HasValue() && mFabricCount < kMaxValidFabricIndex) + { + // We must have a fabric index available here. This situation could + // happen if we fail to store fabric index info when deleting a + // fabric. + mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); + if (FindFabricWithIndex(kMinValidFabricIndex)) + { + UpdateNextAvailableFabricIndex(); + } + } +} + CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader) { ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); @@ -1124,40 +1145,133 @@ CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader) ReturnErrorOnFailure(reader.ExitContainer(containerType)); ReturnErrorOnFailure(reader.VerifyEndOfContainer()); - if (!mNextAvailableFabricIndex.HasValue() && mFabricCount < kMaxValidFabricIndex) + EnsureNextAvailableFabricIndexUpdated(); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR FabricTable::SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, P256ECDSASignature & outSignature) const +{ + const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_KEY_NOT_FOUND); + + if (fabricInfo->HasOperationalKey()) { - // We must have a fabric index available here. This situation could - // happen if we fail to store fabric index info when deleting a - // fabric. - mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex); - if (FindFabricWithIndex(kMinValidFabricIndex)) - { - UpdateNextAvailableFabricIndex(); - } + // Legacy case of manually injected FabricInfo: delegate to FabricInfo directly + return fabricInfo->SignWithOpKeypair(message, outSignature); + } + if (mOperationalKeystore != nullptr) + { + return mOperationalKeystore->SignWithOpKeypair(fabricIndex, message, outSignature); } - return CHIP_NO_ERROR; + return CHIP_ERROR_KEY_NOT_FOUND; } -CHIP_ERROR FabricInfo::TestOnlyBuildFabric(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan nocKey) +bool FabricTable::HasPendingOperationalKey() const { - Reset(); + // We can only manage commissionable pending fail-safe state if we have a keystore + return (mOperationalKeystore != nullptr) ? mOperationalKeystore->HasPendingOpKeypair() : false; +} - ReturnErrorOnFailure(SetRootCert(rootCert)); - ReturnErrorOnFailure(SetICACert(icacCert)); - ReturnErrorOnFailure(SetNOCCert(nocCert)); +CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional fabricIndex, MutableByteSpan & outputCsr) +{ + // We can only manage commissionable pending fail-safe state if we have a keystore + VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE); - // NOTE: this requres ENABLE_HSM_CASE_OPS_KEY is not defined - P256SerializedKeypair opKeysSerialized; - memcpy(static_cast(opKeysSerialized), nocKey.data(), nocKey.size()); - ReturnErrorOnFailure(opKeysSerialized.SetLength(nocKey.size())); + // We can only allocate a pending key if no pending state (NOC, ICAC) already present, + // since there can only be one pending state per fail-safe. + VerifyOrReturnError(!mIsPendingFabricDataPresent, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(outputCsr.size() >= Crypto::kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL); - P256Keypair opKey; - ReturnErrorOnFailure(opKey.Deserialize(opKeysSerialized)); - ReturnErrorOnFailure(SetOperationalKeypair(&opKey)); + EnsureNextAvailableFabricIndexUpdated(); - // NOTE: mVendorId and mFabricLabel are not initialize, because they are not used in tests. - return CHIP_NO_ERROR; + if (fabricIndex.HasValue()) + { + // Fabric udpate case (e.g. UpdateNOC): we already know the fabric index + mFabricIndexWithPendingState = fabricIndex.Value(); + } + else if (mNextAvailableFabricIndex.HasValue()) + { + // Fabric addition case (e.g. AddNOC): we need to allocate for the next pending fabric index + mFabricIndexWithPendingState = mNextAvailableFabricIndex.Value(); + } + else + { + // Fabric addition, but adding NOC would fail on table full: let's not allocate a key + mFabricIndexWithPendingState = kUndefinedFabricIndex; + return CHIP_ERROR_NO_MEMORY; + } + + VerifyOrReturnError(IsValidFabricIndex(mFabricIndexWithPendingState), CHIP_ERROR_INVALID_FABRIC_INDEX); + + return mOperationalKeystore->NewOpKeypairForFabric(mFabricIndexWithPendingState, outputCsr); +} + +CHIP_ERROR FabricTable::ActivatePendingOperationalKey(const Crypto::P256PublicKey & nocSubjectPublicKey) +{ + // We can only manage commissionable pending fail-safe state if we have a keystore + VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE); + + VerifyOrReturnError(!mIsPendingFabricDataPresent, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(mFabricIndexWithPendingState), CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = mOperationalKeystore->ActivateOpKeypairForFabric(mFabricIndexWithPendingState, nocSubjectPublicKey); + + if (err == CHIP_NO_ERROR) + { + // TODO: Refactor to set mIsPendingFabricDataPresent to true more "directly" when a NOC add/update for + // pending fabric occurs. Can only be done when we have shadow fabric. + mIsPendingFabricDataPresent = true; + } + + return err; +} + +// Currently only operational key and last known good time are managed by this API. +CHIP_ERROR FabricTable::CommitPendingFabricData() +{ + // We can only manage commissionable pending fail-safe state if we have a keystore + VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE); + + // If there was nothing pending, it's no-op success. + if (!mIsPendingFabricDataPresent) + { + return CHIP_NO_ERROR; + } + + VerifyOrReturnError(IsValidFabricIndex(mFabricIndexWithPendingState), CHIP_ERROR_INCORRECT_STATE); + + CHIP_ERROR err = mOperationalKeystore->CommitOpKeypairForFabric(mFabricIndexWithPendingState); + + if (err == CHIP_NO_ERROR) + { + mIsPendingFabricDataPresent = false; + mFabricIndexWithPendingState = kUndefinedFabricIndex; + } + + CHIP_ERROR lkgtErr = CommitLastKnownGoodChipEpochTime(); + if (lkgtErr != CHIP_NO_ERROR) + { + ChipLogError(FabricProvisioning, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format()); + } + + return err; +} + +void FabricTable::RevertPendingFabricData() +{ + if (mIsPendingFabricDataPresent) + { + ChipLogError(FabricProvisioning, "Reverting pending fabric data for fabric 0x%u", + static_cast(mFabricIndexWithPendingState)); + } + + mIsPendingFabricDataPresent = false; + mFabricIndexWithPendingState = kUndefinedFabricIndex; + + VerifyOrReturn(mOperationalKeystore != nullptr); + mOperationalKeystore->RevertPendingKeypair(); } } // namespace chip diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index b88140b28d6018..39e06dcaf1f794 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #if CHIP_CRYPTO_HSM #include @@ -106,8 +107,6 @@ class DLL_EXPORT FabricInfo void SetVendorId(uint16_t vendorId) { mVendorId = vendorId; } - Crypto::P256Keypair * GetOperationalKey() const { return mOperationalKey; } - /** * Sets the P256Keypair used for this fabric. This will make a copy of the keypair * via the P256Keypair::Serialize and P256Keypair::Deserialize methods. @@ -144,6 +143,8 @@ class DLL_EXPORT FabricInfo bool IsInitialized() const { return IsOperationalNodeId(mOperationalId.GetNodeId()); } + bool HasOperationalKey() const { return mOperationalKey != nullptr; } + // TODO - Refactor storing and loading of fabric info from persistent storage. // The op cert array doesn't need to be in RAM except when it's being // transmitted to peer node during CASE session setup. @@ -186,6 +187,15 @@ class DLL_EXPORT FabricInfo Credentials::ValidationContext & context, PeerId & nocPeerId, FabricId & fabricId, Crypto::P256PublicKey & nocPubkey); + // Validate an NOC chain at time of adding/updating a fabric (uses VerifyCredentials with additional checks). + // The `existingFabricId` is passed for UpdateNOC, and must match the Fabric, to make sure that we are + // not trying to change FabricID with UpdateNOC. If set to kUndefinedFabricIndex, we are doing an add and + // we don't need to check match to pre-existing fabric. + static CHIP_ERROR ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, + FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy, + PeerId & outOperationalId, FabricId & outFabricId, + Crypto::P256PublicKey & outNocPubkey); + /** * Reset the state to a completely uninitialized status. */ @@ -205,17 +215,38 @@ class DLL_EXPORT FabricInfo mFabricIndex = kUndefinedFabricIndex; } + /** + * 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 newFabric 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 & newFabric, Credentials::CertificateValidityPolicy * policy); + /* 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 */ static CHIP_ERROR GeneratePeerId(const ByteSpan & rcac, FabricId fabricId, NodeId nodeId, PeerId * compressedPeerId); - friend class FabricTable; - // Test-only, build a fabric using given root cert and NOC CHIP_ERROR TestOnlyBuildFabric(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan nocKey); -private: + friend class FabricTable; + +protected: + /** + * @brief Sign a message with the fabric's operational private key. This ONLY + * works if `SetOperationalKeypair` or `SetExternallyOwnedOperationalKeypair` + * had been called and is an API that is present ONLY to be called by FabricTable. + * + * @param message - message to sign + * @param outSignature - buffer to hold the signature + * @return CHIP_NO_ERROR on success or another CHIP_ERROR on crypto internal errors + */ + CHIP_ERROR SignWithOpKeypair(ByteSpan message, Crypto::P256ECDSASignature & outSignature) const; + static constexpr size_t MetadataTLVMaxSize() { return TLV::EstimateStructOverhead(sizeof(VendorId), kFabricLabelMaxLengthInBytes); @@ -249,16 +280,6 @@ 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() { @@ -405,9 +426,12 @@ class DLL_EXPORT FabricTable FabricInfo * FindFabric(Credentials::P256PublicKeySpan rootPubKey, FabricId fabricId); FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex); + const FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex) const; FabricInfo * FindFabricWithCompressedId(CompressedFabricId fabricId); CHIP_ERROR Init(PersistentStorageDelegate * storage); + CHIP_ERROR Init(PersistentStorageDelegate * storage, Crypto::OperationalKeystore * operationalKeystore); + CHIP_ERROR AddFabricDelegate(FabricTable::Delegate * delegate); void RemoveFabricDelegate(FabricTable::Delegate * delegate); @@ -465,6 +489,86 @@ class DLL_EXPORT FabricTable ConstFabricIterator begin() const { return cbegin(); } ConstFabricIterator end() const { return cend(); } + /** + * @brief Sign a message with a given fabric's operational keypair. This is used for + * CASE and the only way the key should be used. + * + * This will use a pending key activated with `ActivatePendingOperationalKey` but + * not yet persisted, if one is available for the fabric. + * + * @param fabricIndex - Fabric index whose operational key touse + * @param message - Message to sign + * @param outSignature - Signature object to receive the signature + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if no active key is found for the given `fabricIndex` or if + * `fabricIndex` is invalid. + * @retval other CHIP_ERROR value on internal errors + */ + CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, Crypto::P256ECDSASignature & outSignature) const; + + /** + * This initializes a new keypair for the given fabric and generates a CSR for it, + * so that it can be passed in a CSRResponse. + * + * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either + * `ActivatePendingOperationalKey` is called. It is destroyed if + * `RevertPendingFabricData` is called before `CommitPendingFabricData`. + * If a pending keypair already existed, it is replaced by this call. + * + * Only one pending operational keypair is supported at a time. + * + * @param fabricIndex - Existing FabricIndex for which a new keypair must be made available. If it + * doesn't have a value, the key will be marked pending for the next available + * fabric index that would apply for `AddNewFabric`. + * @param outputCsr - Buffer to contain the CSR. Must be at least `kMAX_CSR_Length` large. + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outputCsr` buffer is too small + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is already a pending keypair for another `fabricIndex` value + * or if fabricIndex is an invalid value. + * @retval other CHIP_ERROR value on internal errors + */ + CHIP_ERROR AllocatePendingOperationalKey(Optional fabricIndex, MutableByteSpan & outputCsr); + + /** + * @brief Temporarily activates the operational keypair last generated with `AllocatePendingOperationalKey`, + * so that `SignWithOpKeypair` starts using it, but only if it matches the public key passed + * in `nocSubjectPublicKey` gotten from a matching NOC. + * + * This is to be used by AddNOC and UpdateNOC so that a prior key generated by AllocatePendingOperationalKey + * can be used for CASE while not committing it yet to permanent storage to remain after fail-safe. + * + * @param nocSubjectPublicKey - Subject public key associated with an incoming NOC + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no pending operational keypair + * @retval CHIP_ERROR_INVALID_PUBLIC_KEY if `nocSubjectPublicKey` does not match the public key associated + * with the key pair from last `AllocatePendingOperationalKey`. + * @retval other CHIP_ERROR value on internal errors + */ + CHIP_ERROR ActivatePendingOperationalKey(const Crypto::P256PublicKey & nocSubjectPublicKey); + + /** + * @brief Returns whether an operational key is pending (true if `AllocatePendingOperationalKey` was + * previously successfully called, false otherwise + */ + bool HasPendingOperationalKey() const; + + /** + * @brief Commit any pending temporary FabricTable state. This is used mostly for affecting + * CommissioningComplete. + * + * @return CHIP_NO_ERROR on success or any toher CHIO_ERROR value on internal errors + */ + CHIP_ERROR CommitPendingFabricData(); + + /** + * @brief Revert any pending state. This is used to handle fail-safe expiry of partially + * configured fabrics. + */ + void RevertPendingFabricData(); + private: static constexpr size_t IndexInfoTLVMaxSize() { @@ -484,6 +588,13 @@ class DLL_EXPORT FabricTable */ void UpdateNextAvailableFabricIndex(); + /** + * Ensure that we have a valid next available fabric index, if that's at all possible. This covers + * some FabricIndex allocation corner cases. After this is called, the only way we can fail to have + * a next available fabric index is if our fabric table is max-sized (254 entries) and full. + */ + void EnsureNextAvailableFabricIndexUpdated(); + /** * Store our current fabric index state: what our next available index is * and what indices we're using right now. @@ -499,7 +610,8 @@ class DLL_EXPORT FabricTable CHIP_ERROR AddNewFabricInner(FabricInfo & fabric, FabricIndex * assignedIndex); FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS]; - PersistentStorageDelegate * mStorage = nullptr; + PersistentStorageDelegate * mStorage = nullptr; + Crypto::OperationalKeystore * mOperationalKeystore = nullptr; // FabricTable::Delegate link to first node, since FabricTable::Delegate is a form // of intrusive linked-list item. @@ -509,6 +621,15 @@ class DLL_EXPORT FabricTable // it can go and is full. Optional mNextAvailableFabricIndex; uint8_t mFabricCount = 0; + + // If true, we are in the process of a fail-safe and there was at least one + // operation that caused partial data in the fabric table. + bool mIsPendingFabricDataPresent = false; + + // When mIsPendingFabricDataPresent is true, this holds the index of the fabric + // for which there is currently pending data. + FabricIndex mFabricIndexWithPendingState = kUndefinedFabricIndex; + LastKnownGoodTime mLastKnownGoodTime; }; diff --git a/src/crypto/BUILD.gn b/src/crypto/BUILD.gn index 45ab4dc4355b68..98bbfe0b5e199f 100644 --- a/src/crypto/BUILD.gn +++ b/src/crypto/BUILD.gn @@ -62,6 +62,9 @@ static_library("crypto") { sources = [ "CHIPCryptoPAL.cpp", "CHIPCryptoPAL.h", + "OperationalKeystore.h", + "PersistentStorageOperationalKeystore.cpp", + "PersistentStorageOperationalKeystore.h", "RandUtils.cpp", "RandUtils.h", ] @@ -72,6 +75,7 @@ static_library("crypto") { ":crypto_buildconfig", "${chip_root}/src/lib/asn1", "${chip_root}/src/lib/core", + "${chip_root}/src/lib/support", "${nlassert_root}:nlassert", ] diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index d89c42af6e9d52..4116e994c420c3 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -187,6 +187,19 @@ void ClearSecretData(uint8_t (&buf)[N]) ClearSecretData(buf, N); } +/** + * @brief Constant-time buffer comparison + * + * This function implements constant time memcmp. It's good practice + * to use constant time functions for cryptographic functions. + * + * @param a Pointer to first buffer + * @param b Pointer to Second buffer + * @param n Number of bytes to compare + * @return true if `n` first bytes of both buffers are equal, false otherwise + */ +bool IsBufferContentEqualConstantTime(const void * a, const void * b, size_t n); + template class ECPKey { @@ -200,6 +213,12 @@ class ECPKey virtual const uint8_t * ConstBytes() const = 0; virtual uint8_t * Bytes() = 0; + virtual bool Matches(const ECPKey & other) const + { + return (this->Length() == other.Length()) && + IsBufferContentEqualConstantTime(this->ConstBytes(), other.ConstBytes(), this->Length()); + } + virtual CHIP_ERROR ECDSA_validate_msg_signature(const uint8_t * msg, const size_t msg_length, const Sig & signature) const = 0; virtual CHIP_ERROR ECDSA_validate_hash_signature(const uint8_t * hash, const size_t hash_length, const Sig & signature) const = 0; diff --git a/src/crypto/CHIPCryptoPALOpenSSL.cpp b/src/crypto/CHIPCryptoPALOpenSSL.cpp index 24ed1b2c1ccdd1..44af20001c812d 100644 --- a/src/crypto/CHIPCryptoPALOpenSSL.cpp +++ b/src/crypto/CHIPCryptoPALOpenSSL.cpp @@ -913,6 +913,11 @@ void ClearSecretData(uint8_t * buf, size_t len) OPENSSL_cleanse(buf, len); } +bool IsBufferContentEqualConstantTime(const void * a, const void * b, size_t n) +{ + return CRYPTO_memcmp(a, b, n) == 0; +} + static CHIP_ERROR P256PublicKeyFromECKey(EC_KEY * ec_key, P256PublicKey & pubkey) { ERR_clear_error(); diff --git a/src/crypto/CHIPCryptoPALmbedTLS.cpp b/src/crypto/CHIPCryptoPALmbedTLS.cpp index 1c849cd78e8559..6092ef2d0c1dcd 100644 --- a/src/crypto/CHIPCryptoPALmbedTLS.cpp +++ b/src/crypto/CHIPCryptoPALmbedTLS.cpp @@ -697,6 +697,32 @@ void ClearSecretData(uint8_t * buf, size_t len) mbedtls_platform_zeroize(buf, len); } +// THE BELOW IS FROM `third_party/openthread/repo/third_party/mbedtls/repo/library/constant_time.c` since +// mbedtls_ct_memcmp is not available on Linux somehow :( +int mbedtls_ct_memcmp_copy(const void * a, const void * b, size_t n) +{ + size_t i; + volatile const unsigned char * A = (volatile const unsigned char *) a; + volatile const unsigned char * B = (volatile const unsigned char *) b; + volatile unsigned char diff = 0; + + for (i = 0; i < n; i++) + { + /* Read volatile data in order before computing diff. + * This avoids IAR compiler warning: + * 'the order of volatile accesses is undefined ..' */ + unsigned char x = A[i], y = B[i]; + diff |= x ^ y; + } + + return ((int) diff); +} + +bool IsBufferContentEqualConstantTime(const void * a, const void * b, size_t n) +{ + return mbedtls_ct_memcmp_copy(a, b, n) == 0; +} + CHIP_ERROR P256Keypair::Initialize() { CHIP_ERROR error = CHIP_NO_ERROR; @@ -1032,24 +1058,6 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::Mac(const uint8_t * key, size_t key_le return CHIP_NO_ERROR; } -/** - * This function implements constant time memcmp. It's good practice - * to use constant time functions for cryptographic functions. - */ -static inline int constant_time_memcmp(const void * a, const void * b, size_t n) -{ - const uint8_t * A = (const uint8_t *) a; - const uint8_t * B = (const uint8_t *) b; - uint8_t diff = 0; - - for (size_t i = 0; i < n; i++) - { - diff |= (A[i] ^ B[i]); - } - - return diff; -} - CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::MacVerify(const uint8_t * key, size_t key_len, const uint8_t * mac, size_t mac_len, const uint8_t * in, size_t in_len) { @@ -1063,7 +1071,7 @@ CHIP_ERROR Spake2p_P256_SHA256_HKDF_HMAC::MacVerify(const uint8_t * key, size_t SuccessOrExit(error = Mac(key, key_len, in, in_len, computed_mac_span)); VerifyOrExit(computed_mac_span.size() == mac_len, error = CHIP_ERROR_INTERNAL); - VerifyOrExit(constant_time_memcmp(mac, computed_mac, kSHA256_Hash_Length) == 0, error = CHIP_ERROR_INTERNAL); + VerifyOrExit(IsBufferContentEqualConstantTime(mac, computed_mac, kSHA256_Hash_Length), error = CHIP_ERROR_INTERNAL); exit: _log_mbedTLS_error(result); diff --git a/src/crypto/OperationalKeystore.h b/src/crypto/OperationalKeystore.h new file mode 100644 index 00000000000000..5db98109547e87 --- /dev/null +++ b/src/crypto/OperationalKeystore.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include +#include + +namespace chip { +namespace Crypto { + +class OperationalKeystore +{ +public: + virtual ~OperationalKeystore() {} + + // ==== API designed for commisionables to support fail-safe (although can be used by controllers) ==== + + /** + * @brief Returns true if a pending operational key exists from a previous + * `NewOpKeypairForFabric` before any `CommitOpKeypairForFabric` or + * `RevertOpKeypairForFabric`. This returns true even if the key is + * NOT ACTIVE (i.e after `NewOpKeypairForFabric` but before + * `ActivateOpKeypairForFabric`). + */ + virtual bool HasPendingOpKeypair() const = 0; + + /** + * @brief Returns whether a usable operational key exists for the given fabric. + * + * Returns true even if the key is not persisted, such as if `ActivateOpKeypairForFabric` + * had been successfully called for a given fabric. Only returns true if a key + * is presently usable such that `SignWithOpKeypair` would succeed for the fabric. Therefore + * if there was no previously persisted key and `NewOpKeypairForFabric` had been called + * but not `ActivateOpKeypairForFabric`, there is only an inactive key, and this would return false. + * + * @param fabricIndex - FabricIndex for which availability of keypair will be checked. + * @return true if there an active operational keypair for the given FabricIndex, false otherwise. + */ + virtual bool HasOpKeypairForFabric(FabricIndex fabricIndex) const = 0; + + /** + * @brief This initializes a new keypair for the given fabric and generates a CSR for it, + * so that it can be passed in a CSRResponse. + * + * The keypair is temporary and becomes usable for `SignWithOpKeypair` only after either + * `ActivateOpKeypairForFabric` is called. It is destroyed if + * `RevertPendingKeypair` or `Finish` is called before `CommitOpKeypairForFabric`. + * If a pending keypair already existed for the given `fabricIndex`, it is replaced by this call. + * + * Only one pending operational keypair is supported at a time. + * + * @param fabricIndex - FabricIndex for which a new keypair must be made available + * @param outCertificateSigningRequest - Buffer to contain the CSR. Must be at least `kMAX_CSR_Length` large. + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_BUFFER_TOO_SMALL if `outCertificateSigningRequest` buffer is too small + * @retval CHIP_ERROR_INCORRECT_STATE if the key store is not properly initialized. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is already a pending keypair for another `fabricIndex` value + * or if fabricIndex is an invalid value. + * @retval CHIP_ERROR_NOT_IMPLEMENTED if only `SignWithOpKeypair` is supported + * @retval other CHIP_ERROR value on internal crypto engine errors + */ + virtual CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) = 0; + + /** + * @brief Temporarily activates the operational keypair last generated with `NewOpKeypairForFabric`, so + * that `SignWithOpKeypair` starts using it, but only if it matches the public key associated + * with the last NewOpKeypairForFabric. + * + * This is to be used by AddNOC and UpdateNOC so that a prior key generated by NewOpKeypairForFabric + * can be used for CASE while not committing it yet to permanent storage to remain after fail-safe. + * + * @param fabricIndex - FabricIndex for which to activate the keypair, used for security cross-checking + * @param nocPublicKey - Subject public key associated with an incoming NOC + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the key store is not properly initialized. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no operational keypair for `fabricIndex` from a previous + * matching `NewOpKeypairForFabric`. + * @retval CHIP_ERROR_INVALID_PUBLIC_KEY if `nocPublicKey` does not match the public key associated with the + * key pair from last `NewOpKeypairForFabric`. + * @retval CHIP_ERROR_NOT_IMPLEMENTED if only `SignWithOpKeypair` is supported + * @retval other CHIP_ERROR value on internal storage or crypto engine errors + */ + virtual CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) = 0; + + /** + * @brief Permanently commit the operational keypair last generated with `NewOpKeypairForFabric`, + * replacing a prior one previously committed, if any, so that `SignWithOpKeypair` for the + * given `FabricIndex` permanently uses the key that was pending. + * + * This is to be used when CommissioningComplete is successfully received + * + * @param fabricIndex - FabricIndex for which to commit the keypair, used for security cross-checking + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the key store is not properly initialized, + * or ActivateOpKeypairForFabric not yet called + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no pending operational keypair for `fabricIndex` + * @retval CHIP_ERROR_NOT_IMPLEMENTED if only `SignWithOpKeypair` is supported + * @retval other CHIP_ERROR value on internal storage or crypto engine errors + */ + virtual CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) = 0; + + /** + * @brief Permanently remove the keypair associated with a fabric + * + * This is to be used for fail-safe handling and RemoveFabric. Removes both the + * pending operational keypair for the fabricIndex (if any) and the committed one (if any). + * + * @param fabricIndex - FabricIndex for which to remove the keypair + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the key store is not properly initialized. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if there is no pending operational keypair for `fabricIndex` + * @retval CHIP_ERROR_NOT_IMPLEMENTED if only `SignWithOpKeypair` is supported + * @retval other CHIP_ERROR value on internal storage errors + */ + virtual CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) = 0; + + /** + * @brief Permanently release the operational keypair last generated with `NewOpKeypairForFabric`, + * such that `SignWithOpKeypair` uses the previously committed key (if one existed). + * + * This is to be used when a fail-safe expires prior to CommissioningComplete. + * + * This method cannot error-out and must always succeed, even on a no-op. This should + * be safe to do given that `CommitOpKeypairForFabric` must succeed to make a new operational + * keypair usable. + */ + virtual void RevertPendingKeypair() = 0; + + // ==== Primary operation required: signature + /** + * @brief Sign a message with a fabric's currently-active operational keypair. + * + * If a Keypair was successfully made temporarily active for the given `fabricIndex` with `ActivateOpKeypairForFabric`, + * then that is the keypair whose private key is used. Otherwise, the last committed private key + * is used, if one exists + * + * @param fabricIndex - FabricIndex whose operational keypair will be used to sign the `message` + * @param message - Message to sign with the currently active operational keypair + * @param outSignature - Buffer to contain the signature + * + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if the key store is not properly initialized. + * @retval CHIP_ERROR_INVALID_FABRIC_INDEX if no active key is found for the given `fabricIndex` or if + * `fabricIndex` is invalid. + * @retval other CHIP_ERROR value on internal crypto engine errors + * + */ + virtual CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const = 0; +}; + +} // namespace Crypto +} // namespace chip diff --git a/src/crypto/PersistentStorageOperationalKeystore.cpp b/src/crypto/PersistentStorageOperationalKeystore.cpp new file mode 100644 index 00000000000000..3c099833ffb601 --- /dev/null +++ b/src/crypto/PersistentStorageOperationalKeystore.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "PersistentStorageOperationalKeystore.h" + +namespace chip { + +using namespace chip::Crypto; + +namespace { + +// Tags for our operational keypair storage. +constexpr TLV::Tag kOpKeyVersionTag = TLV::ContextTag(0); +constexpr TLV::Tag kOpKeyDataTag = TLV::ContextTag(1); + +// If this version grows beyond UINT16_MAX, adjust OpKeypairTLVMaxSize +// accordingly. +constexpr uint16_t kOpKeyVersion = 1; + +constexpr size_t OpKeyTLVMaxSize() +{ + // Version and serialized key + return TLV::EstimateStructOverhead(sizeof(uint16_t), Crypto::P256SerializedKeypair::Capacity()); +} + +/** WARNING: This can leave the operational key on the stack somewhere, since many of the platform + * APIs use stack buffers and do not sanitize! This implementation is for example purposes + * only of the API and it is recommended to avoid directly accessing raw private key bits + * in storage. + */ +CHIP_ERROR StoreOperationalKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage, P256Keypair * keypair) +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (storage != nullptr) && (keypair != nullptr), + CHIP_ERROR_INVALID_ARGUMENT); + + // Use a CapacityBoundBuffer to get RAII secret data clearing on scope exit. + Crypto::CapacityBoundBuffer buf; + TLV::TLVWriter writer; + + writer.Init(buf.Bytes(), buf.Capacity()); + + TLV::TLVType outerType; + ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType)); + + ReturnErrorOnFailure(writer.Put(kOpKeyVersionTag, kOpKeyVersion)); + + { + // P256SerializedKeypair has RAII secret clearing + Crypto::P256SerializedKeypair serializedOpKey; + ReturnErrorOnFailure(keypair->Serialize(serializedOpKey)); + + ReturnErrorOnFailure(writer.Put(kOpKeyDataTag, ByteSpan(serializedOpKey.Bytes(), serializedOpKey.Length()))); + } + + ReturnErrorOnFailure(writer.EndContainer(outerType)); + + const auto opKeyLength = writer.GetLengthWritten(); + DefaultStorageKeyAllocator keyAlloc; + VerifyOrReturnError(CanCastTo(opKeyLength), CHIP_ERROR_BUFFER_TOO_SMALL); + ReturnErrorOnFailure(storage->SyncSetKeyValue(keyAlloc.FabricOpKey(fabricIndex), buf, static_cast(opKeyLength))); + + return CHIP_NO_ERROR; +} + +/** WARNING: This can leave the operational key on the stack somewhere, since many of the platform + * APIs use stack buffers and do not sanitize! This implementation is for example purposes + * only of the API and it is recommended to avoid directly accessing raw private key bits + * in storage. + */ +CHIP_ERROR SignWithStoredOpKey(FabricIndex fabricIndex, PersistentStorageDelegate * storage, const ByteSpan & message, + P256ECDSASignature & outSignature) +{ + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (storage != nullptr), CHIP_ERROR_INVALID_ARGUMENT); + + // Use RAII scoping for the transient keypair, to make sure it doesn't get leaked on any error paths. + // Key is put in heap since signature is a costly stack operation and P256Keypair is + // a costly class depending on the backend. + auto transientOperationalKeypair = Platform::MakeUnique(); + if (!transientOperationalKeypair) + { + return CHIP_ERROR_NO_MEMORY; + } + + // Scope 1: Load up the keypair data from storage + { + // Use a CapacityBoundBuffer to get RAII secret data clearing on scope exit. + Crypto::CapacityBoundBuffer buf; + + // Load up the operational key structure from storage + uint16_t size = static_cast(buf.Capacity()); + DefaultStorageKeyAllocator keyAlloc; + CHIP_ERROR err = storage->SyncGetKeyValue(keyAlloc.FabricOpKey(fabricIndex), buf.Bytes(), size); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = CHIP_ERROR_INVALID_FABRIC_INDEX; + } + ReturnErrorOnFailure(err); + buf.SetLength(static_cast(size)); + + // Read-out the operational key TLV entry. + TLV::ContiguousBufferTLVReader reader; + reader.Init(buf.Bytes(), buf.Length()); + + ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())); + TLV::TLVType containerType; + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + + ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag)); + uint16_t opKeyVersion; + ReturnErrorOnFailure(reader.Get(opKeyVersion)); + VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH); + + ReturnErrorOnFailure(reader.Next(kOpKeyDataTag)); + { + ByteSpan keyData; + Crypto::P256SerializedKeypair serializedOpKey; + ReturnErrorOnFailure(reader.GetByteView(keyData)); + + // Unfortunately, we have to copy the data into a P256SerializedKeypair. + VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL); + + // Before doing anything with the key, validate format further. + ReturnErrorOnFailure(reader.ExitContainer(containerType)); + ReturnErrorOnFailure(reader.VerifyEndOfContainer()); + + memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size()); + serializedOpKey.SetLength(keyData.size()); + + // Load-up key material + // WARNING: This makes use of the raw key bits + ReturnErrorOnFailure(transientOperationalKeypair->Deserialize(serializedOpKey)); + } + } + + // Scope 2: Sign message with the keypair + return transientOperationalKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); +} + +} // namespace + +bool PersistentStorageOperationalKeystore::HasOpKeypairForFabric(FabricIndex fabricIndex) const +{ + VerifyOrReturnError(mStorage != nullptr, false); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), false); + + // If there was a pending keypair, then there's really a usable key + if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex) && (mPendingKeypair != nullptr)) + { + return true; + } + + DefaultStorageKeyAllocator keyAlloc; + uint16_t keySize = 0; + CHIP_ERROR err = mStorage->SyncGetKeyValue(keyAlloc.FabricOpKey(fabricIndex), nullptr, keySize); + + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + // Obviously not found + return false; + } + if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) && (keySize > 0)) + { + // On found, we actually expect an "error", since we didn't want to read it out. + return true; + } + + // On any other error, we consider the key not found + return false; +} + +CHIP_ERROR PersistentStorageOperationalKeystore::NewOpKeypairForFabric(FabricIndex fabricIndex, + MutableByteSpan & outCertificateSigningRequest) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + // If a key is pending, we cannot generate for a different fabric index until we commit or revert. + if ((mPendingFabricIndex != kUndefinedFabricIndex) && (fabricIndex != mPendingFabricIndex)) + { + return CHIP_ERROR_INVALID_FABRIC_INDEX; + } + VerifyOrReturnError(outCertificateSigningRequest.size() >= Crypto::kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL); + + // Replace previous pending keypair, if any was previously allocated + ResetPendingKey(); + + mPendingKeypair = Platform::New(); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_NO_MEMORY); + + mPendingKeypair->Initialize(); + size_t csrLength = outCertificateSigningRequest.size(); + CHIP_ERROR err = mPendingKeypair->NewCertificateSigningRequest(outCertificateSigningRequest.data(), csrLength); + if (err != CHIP_NO_ERROR) + { + ResetPendingKey(); + return err; + } + + outCertificateSigningRequest.reduce_size(csrLength); + mPendingFabricIndex = fabricIndex; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOperationalKeystore::ActivateOpKeypairForFabric(FabricIndex fabricIndex, + const Crypto::P256PublicKey & nocPublicKey) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Validate public key being activated matches last generated pending keypair + VerifyOrReturnError(mPendingKeypair->Pubkey().Matches(nocPublicKey), CHIP_ERROR_INVALID_PUBLIC_KEY); + + mIsPendingKeypairActive = true; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOperationalKeystore::CommitOpKeypairForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex) && (fabricIndex == mPendingFabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + VerifyOrReturnError(mIsPendingKeypairActive == true, CHIP_ERROR_INCORRECT_STATE); + + // Try to store persistent key. On failure, leave everything pending as-is + CHIP_ERROR err = StoreOperationalKey(fabricIndex, mStorage, mPendingKeypair); + ReturnErrorOnFailure(err); + + // If we got here, we succeeded and can reset the pending key: next `SignWithOpKeypair` will use the stored key. + ResetPendingKey(); + return CHIP_NO_ERROR; +} + +CHIP_ERROR PersistentStorageOperationalKeystore::RemoveOpKeypairForFabric(FabricIndex fabricIndex) +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Remove pending state if matching + if ((mPendingKeypair != nullptr) && (fabricIndex == mPendingFabricIndex)) + { + RevertPendingKeypair(); + } + + DefaultStorageKeyAllocator keyAlloc; + CHIP_ERROR err = mStorage->SyncDeleteKeyValue(keyAlloc.FabricOpKey(fabricIndex)); + if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND) + { + err = CHIP_ERROR_INVALID_FABRIC_INDEX; + } + + return err; +} + +void PersistentStorageOperationalKeystore::RevertPendingKeypair() +{ + VerifyOrReturn(mStorage != nullptr); + + // Just reset the pending key, we never stored anything + ResetPendingKey(); +} + +CHIP_ERROR PersistentStorageOperationalKeystore::SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const +{ + VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX); + + if (mIsPendingKeypairActive && (fabricIndex == mPendingFabricIndex)) + { + VerifyOrReturnError(mPendingKeypair != nullptr, CHIP_ERROR_INTERNAL); + // We have an override key: sign with it! + return mPendingKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); + } + + return SignWithStoredOpKey(fabricIndex, mStorage, message, outSignature); +} + +} // namespace chip diff --git a/src/crypto/PersistentStorageOperationalKeystore.h b/src/crypto/PersistentStorageOperationalKeystore.h new file mode 100644 index 00000000000000..a332bd968d38a6 --- /dev/null +++ b/src/crypto/PersistentStorageOperationalKeystore.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 Project CHIP Authors + * 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace chip { + +/** + * @brief OperationalKeystore implementation making use of PersistentStorageDelegate + * to load/store keypairs. This is the legacy behavior of `FabricTable` prior + * to refactors to use `OperationalKeystore` and exists as a baseline example + * of how to use the interface. + * + * WARNING: Ensure that any implementation that uses this one as a starting point + * DOES NOT have the raw key material (in usable form) passed up/down to + * direct storage APIs that may make copies on heap/stack without sanitization. + */ +class PersistentStorageOperationalKeystore : public Crypto::OperationalKeystore +{ +public: + PersistentStorageOperationalKeystore() = default; + virtual ~PersistentStorageOperationalKeystore() { Finish(); } + + /** + * @brief Initialize the Operational Keystore to map to a given storage delegate. + * + * @param storage Pointer to persistent storage delegate to use. Must outlive this instance. + * @retval CHIP_NO_ERROR on success + * @retval CHIP_ERROR_INCORRECT_STATE if already initialized + */ + CHIP_ERROR Init(PersistentStorageDelegate * storage) + { + VerifyOrReturnError(mStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + mPendingFabricIndex = kUndefinedFabricIndex; + mIsExternallyOwnedKeypair = false; + mStorage = storage; + mPendingKeypair = nullptr; + mIsPendingKeypairActive = false; + return CHIP_NO_ERROR; + } + + /** + * @brief Finalize the keystore, so that subsequent operations fail + */ + void Finish() + { + VerifyOrReturn(mStorage != nullptr); + + ResetPendingKey(); + mStorage = nullptr; + } + + bool HasPendingOpKeypair() const override { return (mPendingKeypair != nullptr); } + + bool HasOpKeypairForFabric(FabricIndex fabricIndex) const override; + CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override; + CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override; + CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override; + CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override; + void RevertPendingKeypair() override; + CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const override; + +protected: + void ResetPendingKey() + { + if (!mIsExternallyOwnedKeypair && (mPendingKeypair != nullptr)) + { + Platform::Delete(mPendingKeypair); + } + mPendingKeypair = nullptr; + mIsExternallyOwnedKeypair = false; + mIsPendingKeypairActive = false; + mPendingFabricIndex = kUndefinedFabricIndex; + } + + PersistentStorageDelegate * mStorage = nullptr; + + // This pending fabric index is `kUndefinedFabricIndex` if there isn't a pending keypair override for a given fabric. + FabricIndex mPendingFabricIndex = kUndefinedFabricIndex; + Crypto::P256Keypair * mPendingKeypair = nullptr; + bool mIsPendingKeypairActive = false; + + // If overridding NewOpKeypairForFabric method in a subclass, set this to true in + // `NewOpKeypairForFabric` if the mPendingKeypair should not be deleted when no longer in use. + bool mIsExternallyOwnedKeypair = false; +}; + +} // namespace chip diff --git a/src/crypto/tests/BUILD.gn b/src/crypto/tests/BUILD.gn index 98d2e6adfdefb8..265a49e60a1632 100644 --- a/src/crypto/tests/BUILD.gn +++ b/src/crypto/tests/BUILD.gn @@ -44,6 +44,8 @@ chip_test_suite("tests") { "TestCryptoLayer.h", ] + test_sources = [ "TestPersistentStorageOpKeyStore.cpp" ] + if (chip_device_platform == "esp32" || chip_device_platform == "nrfconnect" || chip_device_platform == "efr32") { defines = [ "CURRENT_TIME_NOT_IMPLEMENTED=1" ] diff --git a/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp b/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp new file mode 100644 index 00000000000000..34d10d3971d507 --- /dev/null +++ b/src/crypto/tests/TestPersistentStorageOpKeyStore.cpp @@ -0,0 +1,237 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * 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. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::Crypto; + +namespace { + +void TestBasicLifeCycle(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storageDelegate; + PersistentStorageOperationalKeystore opKeystore; + + FabricIndex kFabricIndex = 111; + FabricIndex kBadFabricIndex = static_cast(kFabricIndex + 10u); + + // Failure before Init of ActivateOpKeypairForFabric + P256PublicKey placeHolderPublicKey; + CHIP_ERROR err = opKeystore.ActivateOpKeypairForFabric(kFabricIndex, placeHolderPublicKey); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + + // Failure before Init of NewOpKeypairForFabric + uint8_t unusedCsrBuf[kMAX_CSR_Length]; + MutableByteSpan unusedCsrSpan{ unusedCsrBuf }; + err = opKeystore.NewOpKeypairForFabric(kFabricIndex, unusedCsrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + // Failure before Init of CommitOpKeypairForFabric + err = opKeystore.CommitOpKeypairForFabric(kFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + // Failure before Init of RemoveOpKeypairForFabric + err = opKeystore.RemoveOpKeypairForFabric(kFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE); + + // Success after Init + err = opKeystore.Init(&storageDelegate); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Can generate a key and get a CSR + uint8_t csrBuf[kMAX_CSR_Length]; + MutableByteSpan csrSpan{ csrBuf }; + err = opKeystore.NewOpKeypairForFabric(kFabricIndex, csrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + P256PublicKey csrPublicKey1; + err = VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), csrPublicKey1); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, csrPublicKey1.Matches(csrPublicKey1)); + + // Can regenerate a second CSR and it has different PK + csrSpan = MutableByteSpan{ csrBuf }; + err = opKeystore.NewOpKeypairForFabric(kFabricIndex, csrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + + // Cannot NewOpKeypair for a different fabric if one already pending + uint8_t badCsrBuf[kMAX_CSR_Length]; + MutableByteSpan badCsrSpan = MutableByteSpan{ badCsrBuf }; + err = opKeystore.NewOpKeypairForFabric(kBadFabricIndex, badCsrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + + P256PublicKey csrPublicKey2; + err = VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), csrPublicKey2); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, !csrPublicKey1.Matches(csrPublicKey2)); + + // Fail to generate CSR for invalid fabrics + csrSpan = MutableByteSpan{ csrBuf }; + err = opKeystore.NewOpKeypairForFabric(kUndefinedFabricIndex, csrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + + csrSpan = MutableByteSpan{ csrBuf }; + err = opKeystore.NewOpKeypairForFabric(kMaxValidFabricIndex + 1, csrSpan); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + + // No storage done by NewOpKeypairForFabric + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + // Even after error, the previous valid pending keypair stays valid. + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + + // Activating with mismatching fabricIndex and matching public key fails + err = opKeystore.ActivateOpKeypairForFabric(kBadFabricIndex, csrPublicKey2); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + // Activating with matching fabricIndex and mismatching public key fails + err = opKeystore.ActivateOpKeypairForFabric(kFabricIndex, csrPublicKey1); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_PUBLIC_KEY); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + + uint8_t message[] = { 1, 2, 3, 4 }; + P256ECDSASignature sig1; + // Before successful activation, cannot sign + err = opKeystore.SignWithOpKeypair(kFabricIndex, ByteSpan{ message }, sig1); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Activating with matching fabricIndex and matching public key succeeds + err = opKeystore.ActivateOpKeypairForFabric(kFabricIndex, csrPublicKey2); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Activating does not store, and keeps pending + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kBadFabricIndex) == false); + + // Can't sign for wrong fabric after activation + P256ECDSASignature sig2; + err = opKeystore.SignWithOpKeypair(kBadFabricIndex, ByteSpan{ message }, sig2); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + + // Can sign after activation + err = opKeystore.SignWithOpKeypair(kFabricIndex, ByteSpan{ message }, sig2); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Signature matches pending key + err = csrPublicKey2.ECDSA_validate_msg_signature(message, sizeof(message), sig2); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Signature does not match a previous pending key + err = csrPublicKey1.ECDSA_validate_msg_signature(message, sizeof(message), sig2); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_SIGNATURE); + + // Committing with mismatching fabric fails, leaves pending + err = opKeystore.CommitOpKeypairForFabric(kBadFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INVALID_FABRIC_INDEX); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == true); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == true); + + // Committing key resets pending state and adds storage + DefaultStorageKeyAllocator keyAllocator; + std::string opKeyStorageKey = keyAllocator.FabricOpKey(kFabricIndex); + err = opKeystore.CommitOpKeypairForFabric(kFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == false); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 1); + NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == true); + + // After committing, signing works with the key that was pending + P256ECDSASignature sig3; + uint8_t message2[] = { 10, 11, 12, 13 }; + err = opKeystore.SignWithOpKeypair(kFabricIndex, ByteSpan{ message2 }, sig3); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = csrPublicKey2.ECDSA_validate_msg_signature(message2, sizeof(message2), sig3); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Let's remove the opkey for a fabric, it disappears + err = opKeystore.RemoveOpKeypairForFabric(kFabricIndex); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opKeystore.HasPendingOpKeypair() == false); + NL_TEST_ASSERT(inSuite, opKeystore.HasOpKeypairForFabric(kFabricIndex) == false); + NL_TEST_ASSERT(inSuite, storageDelegate.GetNumKeys() == 0); + NL_TEST_ASSERT(inSuite, storageDelegate.HasKey(opKeyStorageKey) == false); + + opKeystore.Finish(); +} + +/** + * Test Suite. It lists all the test functions. + */ +static const nlTest sTests[] = { NL_TEST_DEF("Test Basic Lifecycle of PersistentStorageOperationalKeystore", TestBasicLifeCycle), + + NL_TEST_SENTINEL() }; + +/** + * Set up the test suite. + */ +int Test_Setup(void * inContext) +{ + CHIP_ERROR error = chip::Platform::MemoryInit(); + VerifyOrReturnError(error == CHIP_NO_ERROR, FAILURE); + return SUCCESS; +} + +/** + * Tear down the test suite. + */ +int Test_Teardown(void * inContext) +{ + chip::Platform::MemoryShutdown(); + return SUCCESS; +} + +} // namespace + +/** + * Main + */ +int TestPersistentStorageOperationalKeystore() +{ + nlTestSuite theSuite = { "PersistentStorageOperationalKeystore tests", &sTests[0], Test_Setup, Test_Teardown }; + + // Run test suite againt one context. + nlTestRunner(&theSuite, nullptr); + return nlTestRunnerStats(&theSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestPersistentStorageOperationalKeystore) diff --git a/src/darwin/Framework/CHIP/CHIPDeviceController.mm b/src/darwin/Framework/CHIP/CHIPDeviceController.mm index 6d9bad3d3849f3..d4a3821889160c 100644 --- a/src/darwin/Framework/CHIP/CHIPDeviceController.mm +++ b/src/darwin/Framework/CHIP/CHIPDeviceController.mm @@ -62,6 +62,11 @@ static NSString * const kErrorNotRunning = @"Controller is not running. Call startup first."; static NSString * const kInfoStackShutdown = @"Shutting down the CHIP Stack"; static NSString * const kErrorSetupCodeGen = @"Generating Manual Pairing Code failed"; +static NSString * const kErrorGenerateNOC = @"Generating operational certificate failed"; +static NSString * const kErrorKeyAllocation = @"Generating new operational key failed"; +static NSString * const kErrorCSRValidation = @"Extracting public key from CSR failed"; +static NSString * const kErrorActivateKey = @"Activating new operational key failed"; +static NSString * const kErrorCommitPendingFabricData = @"Committing fabric data failed"; @interface CHIPDeviceController () @@ -173,7 +178,8 @@ - (BOOL)startup:(CHIPDeviceControllerStartupParamsInternal *)startupParams } if (startupParams.operationalCertificate != nil && startupParams.operationalKeypair == nil - && startupParams.serializedOperationalKeypair == nullptr) { + && (!startupParams.fabricIndex.HasValue() + || !startupParams.keystore->HasOpKeypairForFabric(startupParams.fabricIndex.Value()))) { CHIP_LOG_ERROR("Have no operational keypair for our operational certificate"); return; } @@ -200,10 +206,6 @@ - (BOOL)startup:(CHIPDeviceControllerStartupParamsInternal *)startupParams return; } - // internallyCreatedOperationalKeypair might not be used, but - // if it is it needs to live long enough (until after we are - // done using commissionerParams). - chip::Crypto::P256Keypair internallyCreatedOperationalKeypair; // nocBuffer might not be used, but if it is it needs to live // long enough (until after we are done using // commissionerParams). @@ -226,16 +228,6 @@ - (BOOL)startup:(CHIPDeviceControllerStartupParamsInternal *)startupParams _operationalKeypairNativeBridge.Emplace(_operationalKeypairBridge); commissionerParams.operationalKeypair = &_operationalKeypairNativeBridge.Value(); commissionerParams.hasExternallyOwnedOperationalKeypair = true; - } else { - if (startupParams.serializedOperationalKeypair != nullptr) { - errorCode = internallyCreatedOperationalKeypair.Deserialize(*startupParams.serializedOperationalKeypair); - } else { - errorCode = internallyCreatedOperationalKeypair.Initialize(); - } - if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorCommissionerInit]) { - return; - } - commissionerParams.operationalKeypair = &internallyCreatedOperationalKeypair; } if (startupParams.operationalCertificate) { @@ -243,10 +235,44 @@ - (BOOL)startup:(CHIPDeviceControllerStartupParamsInternal *)startupParams } else { chip::MutableByteSpan noc(nocBuffer); - errorCode = _operationalCredentialsDelegate->GenerateNOC([startupParams.nodeId unsignedLongLongValue], - startupParams.fabricId, chip::kUndefinedCATs, commissionerParams.operationalKeypair->Pubkey(), noc); - if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorCommissionerInit]) { - return; + if (commissionerParams.operationalKeypair != nullptr) { + errorCode = _operationalCredentialsDelegate->GenerateNOC([startupParams.nodeId unsignedLongLongValue], + startupParams.fabricId, chip::kUndefinedCATs, commissionerParams.operationalKeypair->Pubkey(), noc); + + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorGenerateNOC]) { + return; + } + } else { + // Generate a new random keypair. + uint8_t csrBuffer[chip::Crypto::kMAX_CSR_Length]; + chip::MutableByteSpan csr(csrBuffer); + errorCode = startupParams.fabricTable->AllocatePendingOperationalKey(startupParams.fabricIndex, csr); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorKeyAllocation]) { + return; + } + + chip::Crypto::P256PublicKey pubKey; + errorCode = VerifyCertificateSigningRequest(csr.data(), csr.size(), pubKey); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorCSRValidation]) { + return; + } + + errorCode = _operationalCredentialsDelegate->GenerateNOC( + [startupParams.nodeId unsignedLongLongValue], startupParams.fabricId, chip::kUndefinedCATs, pubKey, noc); + + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorGenerateNOC]) { + return; + } + + errorCode = startupParams.fabricTable->ActivatePendingOperationalKey(pubKey); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorActivateKey]) { + return; + } + + errorCode = startupParams.fabricTable->CommitPendingFabricData(); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorCommitPendingFabricData]) { + return; + } } commissionerParams.controllerNOC = noc; } diff --git a/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams.mm b/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams.mm index 976a69df1a1089..acc0b0c1625e1d 100644 --- a/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams.mm +++ b/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams.mm @@ -156,7 +156,9 @@ - (instancetype)initWithParams:(CHIPDeviceControllerStartupParams *)params return self; } -- (instancetype)initForNewFabric:(CHIPDeviceControllerStartupParams *)params +- (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable + keystore:(chip::Crypto::OperationalKeystore *)keystore + params:(CHIPDeviceControllerStartupParams *)params { if (!(self = [self initWithParams:params])) { return nil; @@ -187,15 +189,23 @@ - (instancetype)initForNewFabric:(CHIPDeviceControllerStartupParams *)params } } + _fabricTable = fabricTable; + _keystore = keystore; + return self; } -- (instancetype)initForExistingFabric:(FabricInfo *)fabric params:(CHIPDeviceControllerStartupParams *)params +- (instancetype)initForExistingFabric:(FabricTable *)fabricTable + fabricIndex:(FabricIndex)fabricIndex + keystore:(chip::Crypto::OperationalKeystore *)keystore + params:(CHIPDeviceControllerStartupParams *)params { if (!(self = [self initWithParams:params])) { return nil; } + FabricInfo * fabric = fabricTable->FindFabricWithIndex(fabricIndex); + if (self.vendorId == nil) { self.vendorId = @(fabric->GetVendorId()); } @@ -216,21 +226,10 @@ - (instancetype)initForExistingFabric:(FabricInfo *)fabric params:(CHIPDeviceCon CHIP_LOG_ERROR("Failed to convert TLV NOC to DER X.509: %s", ErrorStr(err)); return nil; } - if (fabric->GetOperationalKey() == nullptr) { + if (!keystore->HasOpKeypairForFabric(fabric->GetFabricIndex())) { CHIP_LOG_ERROR("No existing operational key for fabric"); return nil; } - _serializedOperationalKeypair = new Crypto::P256SerializedKeypair(); - if (_serializedOperationalKeypair == nullptr) { - CHIP_LOG_ERROR("Failed to allocate serialized keypair"); - return nil; - } - - err = fabric->GetOperationalKey()->Serialize(*_serializedOperationalKeypair); - if (err != CHIP_NO_ERROR) { - CHIP_LOG_ERROR("Failed to serialize operational keypair: %s", ErrorStr(err)); - return nil; - } } usingExistingNOC = YES; @@ -298,6 +297,10 @@ - (instancetype)initForExistingFabric:(FabricInfo *)fabric params:(CHIPDeviceCon return nil; } + _fabricTable = fabricTable; + _fabricIndex.Emplace(fabricIndex); + _keystore = keystore; + return self; } @@ -329,11 +332,4 @@ - (BOOL)keypairsMatchCertificates return YES; } -- (void)dealloc -{ - if (_serializedOperationalKeypair != nullptr) { - delete _serializedOperationalKeypair; - _serializedOperationalKeypair = nullptr; - } -} @end diff --git a/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams_Internal.h b/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams_Internal.h index d21928a4220144..5326d1cc8faac3 100644 --- a/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams_Internal.h +++ b/src/darwin/Framework/CHIP/CHIPDeviceControllerStartupParams_Internal.h @@ -20,10 +20,16 @@ #import #include +#include +#include namespace chip { -class FabricInfo; -} +class FabricTable; + +namespace Crypto { + class OperationalKeystore; +} // namespace Crypto +} // namespace chip NS_ASSUME_NONNULL_BEGIN @@ -38,27 +44,15 @@ NS_ASSUME_NONNULL_BEGIN @interface CHIPDeviceControllerStartupParamsInternal : CHIPDeviceControllerStartupParams -/** - * We may have an operational keypair either provided externally, via - * operationalKeypair, or internally (from the fabric table) via - * serializedOperationalKeypair. - * - * If operationalKeypair is nil and serializedOperationalKeypair is nullptr, a - * new random operational keypair will be generated. - * - * If operationalCertificate is not nil, either operationalKeypair must be not - * nil or serializedOperationalKeypair must be not nullptr. - * - * If operationalCertificate is nil, operationalKeypair may be not nil or - * serializedOperationalKeypair may be not nullptr; that corresponds to needing - * to create a new NOC (e.g. if our signing certificate changed) without - * changing our operational identity. - * - * Meant to be set by MatterControllerFactory. - * - * Assumed to be allocated via C++ new and will be deleted via C++ delete. - */ -@property (nonatomic, nullable, readonly) chip::Crypto::P256SerializedKeypair * serializedOperationalKeypair; +// Fabric table we can use to do things like allocate operational keys. +@property (nonatomic, readonly) chip::FabricTable * fabricTable; + +// Fabric index we're starting on. Only has a value when starting on an +// existing fabric. +@property (nonatomic, readonly) chip::Optional fabricIndex; + +// Key store we're using with our fabric table, for sanity checks. +@property (nonatomic, readonly) chip::Crypto::OperationalKeystore * keystore; /** * Helper method that checks that our keypairs match our certificates. @@ -77,12 +71,17 @@ NS_ASSUME_NONNULL_BEGIN /** * Initialize for controller bringup on a new fabric. */ -- (instancetype)initForNewFabric:(CHIPDeviceControllerStartupParams *)params; +- (instancetype)initForNewFabric:(chip::FabricTable *)fabricTable + keystore:(chip::Crypto::OperationalKeystore *)keystore + params:(CHIPDeviceControllerStartupParams *)params; /** * Initialize for controller bringup on an existing fabric. */ -- (instancetype)initForExistingFabric:(chip::FabricInfo *)fabric params:(CHIPDeviceControllerStartupParams *)params; +- (instancetype)initForExistingFabric:(chip::FabricTable *)fabricTable + fabricIndex:(chip::FabricIndex)fabricIndex + keystore:(chip::Crypto::OperationalKeystore *)keystore + params:(CHIPDeviceControllerStartupParams *)params; - (instancetype)initWithSigningKeypair:(id)nocSigner fabricId:(uint64_t)fabricId ipk:(NSData *)ipk NS_UNAVAILABLE; - (instancetype)initWithOperationalKeypair:(id)operationalKeypair diff --git a/src/darwin/Framework/CHIP/MatterControllerFactory.mm b/src/darwin/Framework/CHIP/MatterControllerFactory.mm index 49805b790960a9..9da23daeadbcf2 100644 --- a/src/darwin/Framework/CHIP/MatterControllerFactory.mm +++ b/src/darwin/Framework/CHIP/MatterControllerFactory.mm @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -48,6 +49,7 @@ static NSString * const kErrorGroupProviderInit = @"Init failure while initializing group data provider"; static NSString * const kErrorControllersInit = @"Init controllers array failure"; static NSString * const kErrorControllerFactoryInit = @"Init failure while initializing controller factory"; +static NSString * const kErrorKeystoreInit = @"Init failure while initializing persistent storage keystore"; @interface MatterControllerFactory () @@ -58,9 +60,10 @@ @interface MatterControllerFactory () // We use TestPersistentStorageDelegate just to get an in-memory store to back // our group data provider impl. We initialize this store correctly on every // controller startup, so don't need to actually persist it. -@property (readonly) chip::TestPersistentStorageDelegate * groupStorageDelegate; -@property (readonly) chip::Credentials::GroupDataProviderImpl * groupDataProvider; +@property (readonly) TestPersistentStorageDelegate * groupStorageDelegate; +@property (readonly) Credentials::GroupDataProviderImpl * groupDataProvider; @property (readonly) NSMutableArray * controllers; +@property (readonly) PersistentStorageOperationalKeystore * keystore; - (BOOL)findMatchingFabric:(FabricTable &)fabricTable params:(CHIPDeviceControllerStartupParams *)params @@ -158,6 +161,12 @@ - (void)cleanupStartupObjects _attestationTrustStoreBridge = nullptr; } + if (_keystore) { + _keystore->Finish(); + delete _keystore; + _keystore = nullptr; + } + if (_persistentStorageDelegateBridge) { delete _persistentStorageDelegateBridge; _persistentStorageDelegateBridge = nullptr; @@ -186,6 +195,19 @@ - (BOOL)startup:(MatterControllerFactoryParams *)startupParams return; } + // TODO: Allow passing a different keystore implementation via startupParams. + _keystore = new PersistentStorageOperationalKeystore(); + if (_keystore == nullptr) { + CHIP_LOG_ERROR("Error: %@", kErrorKeystoreInit); + return; + } + + CHIP_ERROR errorCode = _keystore->Init(_persistentStorageDelegateBridge); + if (errorCode != CHIP_NO_ERROR) { + CHIP_LOG_ERROR("Error: %@", kErrorKeystoreInit); + return; + } + // Initialize device attestation verifier if (startupParams.paaCerts) { _attestationTrustStoreBridge = new CHIPAttestationTrustStoreBridge(startupParams.paaCerts); @@ -210,7 +232,8 @@ - (BOOL)startup:(MatterControllerFactoryParams *)startupParams params.groupDataProvider = _groupDataProvider; params.fabricIndependentStorage = _persistentStorageDelegateBridge; - CHIP_ERROR errorCode = _controllerFactory->Init(params); + params.operationalKeystore = _keystore; + errorCode = _controllerFactory->Init(params); if (errorCode != CHIP_NO_ERROR) { CHIP_LOG_ERROR("Error: %@", kErrorControllerFactoryInit); return; @@ -266,8 +289,8 @@ - (CHIPDeviceController * _Nullable)startControllerOnExistingFabric:(CHIPDeviceC } __block CHIPDeviceControllerStartupParamsInternal * params = nil; + __block FabricTable fabricTable; dispatch_sync(_chipWorkQueue, ^{ - FabricTable fabricTable; FabricInfo * fabric = nullptr; BOOL ok = [self findMatchingFabric:fabricTable params:startupParams fabric:&fabric]; if (!ok) { @@ -293,7 +316,10 @@ - (CHIPDeviceController * _Nullable)startControllerOnExistingFabric:(CHIPDeviceC } } - params = [[CHIPDeviceControllerStartupParamsInternal alloc] initForExistingFabric:fabric params:startupParams]; + params = [[CHIPDeviceControllerStartupParamsInternal alloc] initForExistingFabric:&fabricTable + fabricIndex:fabric->GetFabricIndex() + keystore:_keystore + params:startupParams]; }); if (params == nil) { @@ -334,8 +360,8 @@ - (CHIPDeviceController * _Nullable)startControllerOnNewFabric:(CHIPDeviceContro } __block CHIPDeviceControllerStartupParamsInternal * params = nil; + __block FabricTable fabricTable; dispatch_sync(_chipWorkQueue, ^{ - FabricTable fabricTable; FabricInfo * fabric = nullptr; BOOL ok = [self findMatchingFabric:fabricTable params:startupParams fabric:&fabric]; if (!ok) { @@ -348,7 +374,9 @@ - (CHIPDeviceController * _Nullable)startControllerOnNewFabric:(CHIPDeviceContro return; } - params = [[CHIPDeviceControllerStartupParamsInternal alloc] initForNewFabric:startupParams]; + params = [[CHIPDeviceControllerStartupParamsInternal alloc] initForNewFabric:&fabricTable + keystore:_keystore + params:startupParams]; }); if (params == nil) { @@ -397,7 +425,7 @@ - (BOOL)findMatchingFabric:(FabricTable &)fabricTable params:(CHIPDeviceControllerStartupParams *)params fabric:(FabricInfo * _Nullable * _Nonnull)fabric { - CHIP_ERROR err = fabricTable.Init(_persistentStorageDelegateBridge); + CHIP_ERROR err = fabricTable.Init(_persistentStorageDelegateBridge, _keystore); if (err != CHIP_NO_ERROR) { CHIP_LOG_ERROR("Can't initialize fabric table: %s", ErrorStr(err)); return NO; diff --git a/src/include/platform/DeviceControlServer.h b/src/include/platform/DeviceControlServer.h index b43ba515c7f935..865211e016ad86 100644 --- a/src/include/platform/DeviceControlServer.h +++ b/src/include/platform/DeviceControlServer.h @@ -34,9 +34,9 @@ class DeviceControlServer final public: // ===== Members for internal use by other Device Layer components. - CHIP_ERROR CommissioningComplete(NodeId peerNodeId, FabricIndex accessingFabricIndex); + CHIP_ERROR PostCommissioningCompleteEvent(NodeId peerNodeId, FabricIndex accessingFabricIndex); CHIP_ERROR SetRegulatoryConfig(uint8_t location, const CharSpan & countryCode); - CHIP_ERROR ConnectNetworkForOperational(ByteSpan networkID); + CHIP_ERROR PostConnectedToOperationalNetworkEvent(ByteSpan networkID); FailSafeContext & GetFailSafeContext() { return mFailSafeContext; } diff --git a/src/include/platform/FailSafeContext.h b/src/include/platform/FailSafeContext.h index 1d74cd032acfd8..ce7a3300efd97e 100644 --- a/src/include/platform/FailSafeContext.h +++ b/src/include/platform/FailSafeContext.h @@ -41,10 +41,14 @@ class FailSafeContext */ CHIP_ERROR ArmFailSafe(FabricIndex accessingFabricIndex, System::Clock::Timeout expiryLength); - CHIP_ERROR DisarmFailSafe(); + /** + * @brief Cleanly disarm failsafe timer, such as on CommissioningComplete + */ + void DisarmFailSafe(); CHIP_ERROR SetAddNocCommandInvoked(FabricIndex nocFabricIndex); CHIP_ERROR SetUpdateNocCommandInvoked(); void SetAddTrustedRootCertInvoked() { mAddTrustedRootCertHasBeenInvoked = true; } + void SetCsrRequestForUpdateNoc(bool isForUpdateNoc) { mIsCsrRequestForUpdateNoc = isForUpdateNoc; } /** * @brief @@ -74,6 +78,7 @@ class FailSafeContext bool AddNocCommandHasBeenInvoked() const { return mAddNocCommandHasBeenInvoked; } bool UpdateNocCommandHasBeenInvoked() const { return mUpdateNocCommandHasBeenInvoked; } bool AddTrustedRootCertHasBeenInvoked() const { return mAddTrustedRootCertHasBeenInvoked; } + bool IsCsrRequestForUpdateNoc() const { return mIsCsrRequestForUpdateNoc; } FabricIndex GetFabricIndex() const { @@ -94,7 +99,9 @@ class FailSafeContext bool mAddNocCommandHasBeenInvoked = false; bool mUpdateNocCommandHasBeenInvoked = false; bool mAddTrustedRootCertHasBeenInvoked = false; - FabricIndex mFabricIndex = kUndefinedFabricIndex; + // The fact of whether a CSR occurred at all is stored elsewhere. + bool mIsCsrRequestForUpdateNoc = false; + FabricIndex mFabricIndex = kUndefinedFabricIndex; // TODO:: Track the state of what was mutated during fail-safe. @@ -125,6 +132,8 @@ class FailSafeContext mAddNocCommandHasBeenInvoked = false; mUpdateNocCommandHasBeenInvoked = false; mAddTrustedRootCertHasBeenInvoked = false; + mFailSafeBusy = false; + mIsCsrRequestForUpdateNoc = false; } void FailSafeTimerExpired(); diff --git a/src/include/platform/internal/GenericConfigurationManagerImpl.ipp b/src/include/platform/internal/GenericConfigurationManagerImpl.ipp index 89003e2787086b..5c3044478626e0 100644 --- a/src/include/platform/internal/GenericConfigurationManagerImpl.ipp +++ b/src/include/platform/internal/GenericConfigurationManagerImpl.ipp @@ -417,34 +417,6 @@ CHIP_ERROR GenericConfigurationManagerImpl::Init() ReturnErrorOnFailure(StoreUniqueId(uniqueId, strlen(uniqueId))); } - bool failSafeArmed = false; - - // If the fail-safe was armed when the device last shutdown, initiate cleanup based on the pending Fail Safe Context with - // which the fail-safe timer was armed. - if (GetFailSafeArmed(failSafeArmed) == CHIP_NO_ERROR && failSafeArmed) - { - FabricIndex fabricIndex; - bool addNocCommandInvoked; - bool updateNocCommandInvoked; - - ChipLogProgress(DeviceLayer, "Detected fail-safe armed on reboot"); - - err = FailSafeContext::LoadFromStorage(fabricIndex, addNocCommandInvoked, updateNocCommandInvoked); - if (err == CHIP_NO_ERROR) - { - DeviceControlServer::DeviceControlSvr().GetFailSafeContext().ScheduleFailSafeCleanup(fabricIndex, addNocCommandInvoked, - updateNocCommandInvoked); - } - else - { - // This should not happen, but we should not fail system init based on it! - ChipLogError(DeviceLayer, "Failed to load fail-safe context from storage (err= %" CHIP_ERROR_FORMAT "), cleaning-up!", - err.Format()); - (void) SetFailSafeArmed(false); - err = CHIP_NO_ERROR; - } - } - return err; } diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h index 56e34ba2ac9a8c..bdc31179c6a8a6 100644 --- a/src/lib/support/TestPersistentStorageDelegate.h +++ b/src/lib/support/TestPersistentStorageDelegate.h @@ -59,7 +59,7 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED; } - bool contains = mStorage.find(key) != mStorage.end(); + bool contains = HasKey(key); VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); std::vector & value = mStorage[key]; @@ -109,7 +109,7 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED; } - bool contains = mStorage.find(key) != mStorage.end(); + bool contains = HasKey(key); VerifyOrReturnError(contains, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); mStorage.erase(key); return CHIP_NO_ERROR; @@ -135,6 +135,34 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate */ virtual void ClearStorage() { mStorage.clear(); } + /** + * @return the number of keys currently written in storage + */ + virtual size_t GetNumKeys() { return mStorage.size(); } + + /** + * @return a set of all the keys stored + */ + virtual std::set GetKeys() + { + std::set keys; + + for (auto it = mStorage.begin(); it != mStorage.end(); ++it) + { + keys.insert(it->first); + } + + return keys; + } + + /** + * @brief Determine if storage has a given key + * + * @param key - key to find (case-sensitive) + * @return true if key is present in storage, false otherwise + */ + virtual bool HasKey(const std::string & key) { return (mStorage.find(key) != mStorage.end()); } + protected: std::map> mStorage; std::set mPoisonKeys; diff --git a/src/lib/support/logging/CHIPLogging.cpp b/src/lib/support/logging/CHIPLogging.cpp index 35be0f3145f594..b92e04d7325d2e 100644 --- a/src/lib/support/logging/CHIPLogging.cpp +++ b/src/lib/support/logging/CHIPLogging.cpp @@ -100,7 +100,7 @@ const char ModuleNames[] = "-\0\0" // None "SD\0" // ServiceDirectory "SP\0" // ServiceProvisioning "SWU" // SoftwareUpdate - "TP\0" // TokenPairing + "FS\0" // FailSafe "TS\0" // TimeService "HB\0" // Heartbeat "CSL" // chipSystemLayer diff --git a/src/lib/support/logging/Constants.h b/src/lib/support/logging/Constants.h index ec2c47aba9cff3..37cc149baba661 100644 --- a/src/lib/support/logging/Constants.h +++ b/src/lib/support/logging/Constants.h @@ -41,7 +41,7 @@ enum LogModule kLogModule_ServiceDirectory, kLogModule_ServiceProvisioning, kLogModule_SoftwareUpdate, - kLogModule_TokenPairing, + kLogModule_FailSafe, kLogModule_TimeService, kLogModule_Heartbeat, kLogModule_chipSystemLayer, diff --git a/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp index 57d872b4cf9e3b..10cda95e078cc4 100644 --- a/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp +++ b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp @@ -20,13 +20,36 @@ #include #include +#include #include +#include +#include + #include using namespace chip; namespace { +template +bool SetMatches(const std::set & set, const std::array expectedContents) +{ + if (set.size() != N) + { + return false; + } + + for (auto item : expectedContents) + { + if (set.find(item) == set.cend()) + { + return false; + } + } + + return true; +} + void TestBasicApi(nlTestSuite * inSuite, void * inContext) { TestPersistentStorageDelegate storage; @@ -42,6 +65,8 @@ void TestBasicApi(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); NL_TEST_ASSERT(inSuite, size == sizeof(buf)); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + err = storage.SyncDeleteKeyValue("roboto"); NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); @@ -75,6 +100,11 @@ void TestBasicApi(nlTestSuite * inSuite, void * inContext) err = storage.SyncSetKeyValue("key3", kStringValue3, static_cast(strlen(kStringValue3))); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 2); + auto keys = storage.GetKeys(); + std::array kExpectedKeys{ "key2", "key3" }; + NL_TEST_ASSERT(inSuite, SetMatches(keys, kExpectedKeys) == true); + // Read them back memset(&buf[0], 0, sizeof(buf)); @@ -194,6 +224,8 @@ void TestClearStorage(nlTestSuite * inSuite, void * inContext) uint16_t size = sizeof(buf); // Key not there + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); + CHIP_ERROR err; memset(&buf[0], 0, sizeof(buf)); size = sizeof(buf); @@ -205,6 +237,7 @@ void TestClearStorage(nlTestSuite * inSuite, void * inContext) const char * kStringValue1 = "abcd"; err = storage.SyncSetKeyValue("roboto", kStringValue1, static_cast(strlen(kStringValue1))); NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 1); memset(&buf[0], 0, sizeof(buf)); size = sizeof(buf); @@ -216,6 +249,7 @@ void TestClearStorage(nlTestSuite * inSuite, void * inContext) // Clear storage, make sure it's gone storage.ClearStorage(); + NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == 0); memset(&buf[0], 0, sizeof(buf)); size = sizeof(buf); err = storage.SyncGetKeyValue("roboto", &buf[0], size); diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index 40b7f04a4b78dc..76a8ecef3bc8c4 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -46,8 +46,8 @@ CHIP_ERROR MessagingContext::Init(TransportMgrBase * transport, IOContext * ioCo FabricInfo aliceFabric; FabricInfo bobFabric; - aliceFabric.TestOnlyBuildFabric(GetRootACertAsset().mCert, GetIAA1CertAsset().mCert, GetNodeA1CertAsset().mCert, - GetNodeA1CertAsset().mKey); + ReturnErrorOnFailure(aliceFabric.TestOnlyBuildFabric(GetRootACertAsset().mCert, GetIAA1CertAsset().mCert, + GetNodeA1CertAsset().mCert, GetNodeA1CertAsset().mKey)); ReturnErrorOnFailure(mFabricTable.AddNewFabricForTest(aliceFabric, &mAliceFabricIndex)); bobFabric.TestOnlyBuildFabric(GetRootACertAsset().mCert, GetIAA1CertAsset().mCert, GetNodeA2CertAsset().mCert, diff --git a/src/platform/DeviceControlServer.cpp b/src/platform/DeviceControlServer.cpp index d5022e9d0c5981..da27293ada6008 100644 --- a/src/platform/DeviceControlServer.cpp +++ b/src/platform/DeviceControlServer.cpp @@ -34,10 +34,8 @@ DeviceControlServer & DeviceControlServer::DeviceControlSvr() return sInstance; } -CHIP_ERROR DeviceControlServer::CommissioningComplete(NodeId peerNodeId, FabricIndex accessingFabricIndex) +CHIP_ERROR DeviceControlServer::PostCommissioningCompleteEvent(NodeId peerNodeId, FabricIndex accessingFabricIndex) { - VerifyOrReturnError(CHIP_NO_ERROR == mFailSafeContext.DisarmFailSafe(), CHIP_ERROR_INTERNAL); - ChipDeviceEvent event; event.Type = DeviceEventType::kCommissioningComplete; @@ -66,14 +64,13 @@ CHIP_ERROR DeviceControlServer::SetRegulatoryConfig(uint8_t location, const Char return err; } -CHIP_ERROR DeviceControlServer::ConnectNetworkForOperational(ByteSpan networkID) +CHIP_ERROR DeviceControlServer::PostConnectedToOperationalNetworkEvent(ByteSpan networkID) { ChipDeviceEvent event; event.Type = DeviceEventType::kOperationalNetworkEnabled; // TODO(cecille): This should be some way to specify thread or wifi. event.OperationalNetwork.network = 0; - PlatformMgr().DispatchEvent(&event); - return CHIP_NO_ERROR; + return PlatformMgr().PostEvent(&event); } } // namespace DeviceLayer diff --git a/src/platform/FailSafeContext.cpp b/src/platform/FailSafeContext.cpp index 70bae225d6c913..6bb4a0c3d19b9f 100644 --- a/src/platform/FailSafeContext.cpp +++ b/src/platform/FailSafeContext.cpp @@ -37,37 +37,36 @@ constexpr TLV::Tag kUpdateNocCommandTag = TLV::ContextTag(2); void FailSafeContext::HandleArmFailSafeTimer(System::Layer * layer, void * aAppState) { - FailSafeContext * context = reinterpret_cast(aAppState); - context->FailSafeTimerExpired(); + FailSafeContext * failSafeContext = reinterpret_cast(aAppState); + failSafeContext->FailSafeTimerExpired(); } void FailSafeContext::HandleDisarmFailSafe(intptr_t arg) { - FailSafeContext * this_ = reinterpret_cast(arg); - - this_->mFailSafeBusy = false; - - if (ConfigurationMgr().SetFailSafeArmed(false) != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Failed to set FailSafeArmed config to false"); - } - - if (DeleteFromStorage() != CHIP_NO_ERROR) - { - ChipLogError(DeviceLayer, "Failed to delete FailSafeContext from configuration"); - } + FailSafeContext * failSafeContext = reinterpret_cast(arg); + failSafeContext->DisarmFailSafe(); } void FailSafeContext::FailSafeTimerExpired() { + if (!IsFailSafeArmed()) + { + // In case this was a pending timer event in event loop, and we had + // done CommissioningComplete or manual disarm. + return; + } + + ChipLogProgress(FailSafe, "Fail-safe timer expired"); ScheduleFailSafeCleanup(mFabricIndex, mAddNocCommandHasBeenInvoked, mUpdateNocCommandHasBeenInvoked); } void FailSafeContext::ScheduleFailSafeCleanup(FabricIndex fabricIndex, bool addNocCommandInvoked, bool updateNocCommandInvoked) { - ResetState(); - - mFailSafeBusy = true; + // Not armed, but busy so cannot rearm (via General Commissioning cluster) until the flushing + // via `HandleDisarmFailSafe` path is complete. + // TODO: This is hacky and we need to remove all this event pushing business, to keep all fail-safe logic-only. + mFailSafeBusy = true; + mFailSafeArmed = false; ChipDeviceEvent event; event.Type = DeviceEventType::kFailSafeTimerExpired; @@ -78,7 +77,7 @@ void FailSafeContext::ScheduleFailSafeCleanup(FabricIndex fabricIndex, bool addN if (status != CHIP_NO_ERROR) { - ChipLogError(DeviceLayer, "Failed to post fail-safe timer expired: %" CHIP_ERROR_FORMAT, status.Format()); + ChipLogError(FailSafe, "Failed to post fail-safe timer expired: %" CHIP_ERROR_FORMAT, status.Format()); } PlatformMgr().ScheduleWork(HandleDisarmFailSafe, reinterpret_cast(this)); @@ -96,16 +95,23 @@ CHIP_ERROR FailSafeContext::ArmFailSafe(FabricIndex accessingFabricIndex, System return CHIP_NO_ERROR; } -CHIP_ERROR FailSafeContext::DisarmFailSafe() +void FailSafeContext::DisarmFailSafe() { + DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this); + ResetState(); - DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this); + if (ConfigurationMgr().SetFailSafeArmed(false) != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "Failed to set FailSafeArmed config to false"); + } - ReturnErrorOnFailure(ConfigurationMgr().SetFailSafeArmed(false)); - ReturnErrorOnFailure(DeleteFromStorage()); + if (DeleteFromStorage() != CHIP_NO_ERROR) + { + ChipLogError(FailSafe, "Failed to delete FailSafeContext from configuration"); + } - return CHIP_NO_ERROR; + ChipLogProgress(FailSafe, "Fail-safe cleanly disarmed"); } CHIP_ERROR FailSafeContext::SetAddNocCommandInvoked(FabricIndex nocFabricIndex) @@ -191,7 +197,10 @@ void FailSafeContext::ForceFailSafeTimerExpiry() { return; } + + // Cancel the timer since we force its action DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this); + FailSafeTimerExpired(); } diff --git a/src/platform/tests/TestFailSafeContext.cpp b/src/platform/tests/TestFailSafeContext.cpp index ec1dc5559a2996..1c998caee0a2aa 100644 --- a/src/platform/tests/TestFailSafeContext.cpp +++ b/src/platform/tests/TestFailSafeContext.cpp @@ -67,8 +67,7 @@ static void TestFailSafeContext_ArmFailSafe(nlTestSuite * inSuite, void * inCont NL_TEST_ASSERT(inSuite, failSafeContext.IsFailSafeArmed(kTestAccessingFabricIndex1) == true); NL_TEST_ASSERT(inSuite, failSafeContext.IsFailSafeArmed(kTestAccessingFabricIndex2) == false); - err = failSafeContext.DisarmFailSafe(); - NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + failSafeContext.DisarmFailSafe(); NL_TEST_ASSERT(inSuite, failSafeContext.IsFailSafeArmed() == false); } @@ -93,8 +92,7 @@ static void TestFailSafeContext_NocCommandInvoked(nlTestSuite * inSuite, void * NL_TEST_ASSERT(inSuite, failSafeContext.NocCommandHasBeenInvoked() == true); NL_TEST_ASSERT(inSuite, failSafeContext.UpdateNocCommandHasBeenInvoked() == true); - err = failSafeContext.DisarmFailSafe(); - NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + failSafeContext.DisarmFailSafe(); } static void TestFailSafeContext_CommitToStorage(nlTestSuite * inSuite, void * inContext) @@ -125,8 +123,7 @@ static void TestFailSafeContext_CommitToStorage(nlTestSuite * inSuite, void * in NL_TEST_ASSERT(inSuite, addNocCommandInvoked == true); NL_TEST_ASSERT(inSuite, updateNocCommandInvoked == true); - err = failSafeContext.DisarmFailSafe(); - NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + failSafeContext.DisarmFailSafe(); } /** diff --git a/src/protocols/secure_channel/CASESession.cpp b/src/protocols/secure_channel/CASESession.cpp index a47940b3805b46..79c16adf81f762 100644 --- a/src/protocols/secure_channel/CASESession.cpp +++ b/src/protocols/secure_channel/CASESession.cpp @@ -181,8 +181,8 @@ CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, Fab VerifyOrReturnError(fabrics != nullptr, CHIP_ERROR_INVALID_ARGUMENT); ReturnErrorOnFailure(Init(sessionManager, policy, delegate, previouslyEstablishedPeer)); - mRole = CryptoContext::SessionRole::kResponder; mFabricsTable = fabrics; + mRole = CryptoContext::SessionRole::kResponder; mSessionResumptionStorage = sessionResumptionStorage; mLocalMRPConfig = mrpConfig; @@ -192,21 +192,23 @@ CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, Fab return CHIP_NO_ERROR; } -CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, FabricIndex fabricIndex, - NodeId peerNodeId, ExchangeContext * exchangeCtxt, - SessionResumptionStorage * sessionResumptionStorage, +CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, ScopedNodeId peerScopedNodeId, + ExchangeContext * exchangeCtxt, SessionResumptionStorage * sessionResumptionStorage, Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, Optional mrpConfig) { MATTER_TRACE_EVENT_SCOPE("EstablishSession", "CASESession"); - CHIP_ERROR err = CHIP_NO_ERROR; - FabricInfo * fabricInfo = nullptr; + CHIP_ERROR err = CHIP_NO_ERROR; // Return early on error here, as we have not initialized any state yet ReturnErrorCodeIf(exchangeCtxt == 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); + + // Use FabricTable directly to avoid situation of dangling index from stale FabricInfo + // until we factor-out any FabricInfo direct usage. + ReturnErrorCodeIf(peerScopedNodeId.GetFabricIndex() == kUndefinedFabricIndex, CHIP_ERROR_INVALID_ARGUMENT); + auto * fabricInfo = fabricTable->FindFabricWithIndex(peerScopedNodeId.GetFabricIndex()); + ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INVALID_ARGUMENT); err = Init(sessionManager, policy, delegate, ScopedNodeId(peerNodeId, fabricIndex)); @@ -221,12 +223,12 @@ CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, Fabric SuccessOrExit(err); mFabricsTable = fabricTable; - mFabricIndex = fabricIndex; + mFabricIndex = fabricInfo->GetFabricIndex(); mSessionResumptionStorage = sessionResumptionStorage; mLocalMRPConfig = mrpConfig; mExchangeCtxt->SetResponseTimeout(kSigma_Response_Timeout + mExchangeCtxt->GetSessionHandle()->GetAckTimeout()); - mPeerNodeId = peerNodeId; + mPeerNodeId = peerScopedNodeId.GetNodeId(); mLocalNodeId = fabricInfo->GetNodeId(); ChipLogProgress(SecureChannel, "Initiating session on local FabricIndex %u from 0x" ChipLogFormatX64 " -> 0x" ChipLogFormatX64, @@ -326,7 +328,7 @@ CHIP_ERROR CASESession::RecoverInitiatorIpk() // since it leaks private security material. #if 0 ChipLogProgress(SecureChannel, "RecoverInitiatorIpk: GroupDataProvider %p, Got IPK for FabricIndex %u", mGroupDataProvider, - static_cast(mFabricInfo->GetFabricIndex())); + static_cast(mFabricIndex)); ChipLogByteSpan(SecureChannel, ByteSpan(mIPK)); #endif @@ -350,7 +352,7 @@ CHIP_ERROR CASESession::SendSigma1() uint8_t destinationIdentifier[kSHA256_Hash_Length] = { 0 }; // Lookup fabric info. - auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); // Validate that we have a session ID allocated. @@ -371,6 +373,7 @@ CHIP_ERROR CASESession::SendSigma1() ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(1), ByteSpan(mInitiatorRandom))); // Retrieve Session Identifier ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), GetLocalSessionId().Value())); + // Generate a Destination Identifier based on the node we are attempting to reach { // Obtain originator IPK matching the fabric where we are trying to open a session. mIPK @@ -511,7 +514,7 @@ CHIP_ERROR CASESession::TryResumeSession(SessionResumptionStorage::ConstResumpti ReturnErrorOnFailure( ValidateSigmaResumeMIC(resume1MIC, initiatorRandom, resumptionId, ByteSpan(kKDFS1RKeyInfo), ByteSpan(kResume1MIC_Nonce))); - auto fabricInfo = mFabricsTable->FindFabricWithIndex(node.GetFabricIndex()); + auto * fabricInfo = mFabricsTable->FindFabricWithIndex(node.GetFabricIndex()); VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); mFabricIndex = node.GetFabricIndex(); @@ -664,7 +667,7 @@ CHIP_ERROR CASESession::SendSigma2() VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE); - auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE); ByteSpan icaCert; @@ -707,12 +710,9 @@ CHIP_ERROR CASESession::SendSigma2() ByteSpan(mRemotePubKey, mRemotePubKey.Length()), msg_R2_Signed.Get(), msg_r2_signed_len)); // Generate a Signature - VerifyOrReturnError(fabricInfo->GetOperationalKey() != nullptr, CHIP_ERROR_INCORRECT_STATE); - P256ECDSASignature tbsData2Signature; ReturnErrorOnFailure( - fabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R2_Signed.Get(), msg_r2_signed_len, tbsData2Signature)); - + mFabricsTable->SignWithOpKeypair(mFabricIndex, ByteSpan{ msg_R2_Signed.Get(), msg_r2_signed_len }, tbsData2Signature)); msg_R2_Signed.Free(); // Construct Sigma2 TBE Data @@ -1065,8 +1065,7 @@ CHIP_ERROR CASESession::SendSigma3() ByteSpan(mRemotePubKey, mRemotePubKey.Length()), msg_R3_Signed.Get(), msg_r3_signed_len)); // Generate a signature - VerifyOrExit(fabricInfo->GetOperationalKey() != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - err = fabricInfo->GetOperationalKey()->ECDSA_sign_msg(msg_R3_Signed.Get(), msg_r3_signed_len, tbsData3Signature); + err = mFabricsTable->SignWithOpKeypair(mFabricIndex, ByteSpan{ msg_R3_Signed.Get(), msg_r3_signed_len }, tbsData3Signature); SuccessOrExit(err); // Prepare Sigma3 TBE Data Blob @@ -1413,7 +1412,7 @@ CHIP_ERROR CASESession::ValidatePeerIdentity(const ByteSpan & peerNOC, const Byt Crypto::P256PublicKey & peerPublicKey) { ReturnErrorCodeIf(mFabricsTable == nullptr, CHIP_ERROR_INCORRECT_STATE); - auto fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); + auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex); ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorOnFailure(SetEffectiveTime()); diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h index 8e55b6fd8e821e..2e8bf3f61f8ee7 100644 --- a/src/protocols/secure_channel/CASESession.h +++ b/src/protocols/secure_channel/CASESession.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -93,9 +94,8 @@ 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 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 fabricTable The fabric table that contains a fabric in common with the peer + * @param peerScopedNodeId Node to which we want to establish a session * @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 @@ -103,7 +103,7 @@ class DLL_EXPORT CASESession : public Messaging::UnsolicitedMessageHandler, * @return CHIP_ERROR The result of initialization */ CHIP_ERROR - EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, FabricIndex fabricIndex, NodeId peerNodeId, + EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, ScopedNodeId peerScopedNodeId, Messaging::ExchangeContext * exchangeCtxt, SessionResumptionStorage * sessionResumptionStorage, Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate, Optional mrpConfig = Optional::Missing()); diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index 47ec1cac67354a..86626ae08a2fb7 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -53,27 +54,6 @@ using namespace chip::Protocols; using TestContext = Test::LoopbackMessagingContext; namespace { -#if CHIP_CONFIG_SLOW_CRYPTO -constexpr uint32_t sTestCaseMessageCount = 8; -constexpr uint32_t sTestCaseResumptionMessageCount = 6; -#else // CHIP_CONFIG_SLOW_CRYPTO -constexpr uint32_t sTestCaseMessageCount = 5; -constexpr uint32_t sTestCaseResumptionMessageCount = 4; -#endif // CHIP_CONFIG_SLOW_CRYPTO - -FabricTable gCommissionerFabrics; -FabricIndex gCommissionerFabricIndex; -GroupDataProviderImpl gCommissionerGroupDataProvider; -TestPersistentStorageDelegate gCommissionerStorageDelegate; - -FabricTable gDeviceFabrics; -FabricIndex gDeviceFabricIndex; -GroupDataProviderImpl gDeviceGroupDataProvider; -TestPersistentStorageDelegate gDeviceStorageDelegate; - -NodeId Node01_01 = 0xDEDEDEDE00010001; -NodeId Node01_02 = 0xDEDEDEDE00010002; - class TestCASESecurePairingDelegate : public SessionEstablishmentDelegate { public: @@ -103,6 +83,68 @@ class CASEServerForTest : public CASEServer CASESession mCaseSession; }; +class TestOperationalKeystore : public chip::Crypto::OperationalKeystore +{ +public: + void Init(FabricIndex fabricIndex, Platform::UniquePtr keypair) + { + mSingleFabricIndex = fabricIndex; + mKeypair = std::move(keypair); + } + + bool HasPendingOpKeypair() const override { return false; } + bool HasOpKeypairForFabric(FabricIndex fabricIndex) const override { return mSingleFabricIndex != kUndefinedFabricIndex; } + + CHIP_ERROR NewOpKeypairForFabric(FabricIndex fabricIndex, MutableByteSpan & outCertificateSigningRequest) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + CHIP_ERROR ActivateOpKeypairForFabric(FabricIndex fabricIndex, const Crypto::P256PublicKey & nocPublicKey) override + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + + CHIP_ERROR CommitOpKeypairForFabric(FabricIndex fabricIndex) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + CHIP_ERROR RemoveOpKeypairForFabric(FabricIndex fabricIndex) override { return CHIP_ERROR_NOT_IMPLEMENTED; } + + void RevertPendingKeypair() override {} + + CHIP_ERROR SignWithOpKeypair(FabricIndex fabricIndex, const ByteSpan & message, + Crypto::P256ECDSASignature & outSignature) const override + { + VerifyOrReturnError(mKeypair != nullptr, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(fabricIndex == mSingleFabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX); + return mKeypair->ECDSA_sign_msg(message.data(), message.size(), outSignature); + } + +protected: + Platform::UniquePtr mKeypair; + FabricIndex mSingleFabricIndex = kUndefinedFabricIndex; +}; + +#if CHIP_CONFIG_SLOW_CRYPTO +constexpr uint32_t sTestCaseMessageCount = 8; +constexpr uint32_t sTestCaseResumptionMessageCount = 6; +#else // CHIP_CONFIG_SLOW_CRYPTO +constexpr uint32_t sTestCaseMessageCount = 5; +constexpr uint32_t sTestCaseResumptionMessageCount = 4; +#endif // CHIP_CONFIG_SLOW_CRYPTO + +FabricTable gCommissionerFabrics; +FabricIndex gCommissionerFabricIndex; +GroupDataProviderImpl gCommissionerGroupDataProvider; +TestPersistentStorageDelegate gCommissionerStorageDelegate; + +FabricTable gDeviceFabrics; +FabricIndex gDeviceFabricIndex; +GroupDataProviderImpl gDeviceGroupDataProvider; +TestPersistentStorageDelegate gDeviceStorageDelegate; +TestOperationalKeystore gDeviceOperationalKeystore; + +NodeId Node01_01 = 0xDEDEDEDE00010001; +NodeId Node01_02 = 0xDEDEDEDE00010002; + CHIP_ERROR InitTestIpk(GroupDataProvider & groupDataProvider, const FabricInfo & fabricInfo, size_t numIpks) { VerifyOrReturnError((numIpks > 0) && (numIpks <= 3), CHIP_ERROR_INVALID_ARGUMENT); @@ -160,14 +202,17 @@ CHIP_ERROR InitCredentialSets() ReturnErrorOnFailure(gDeviceGroupDataProvider.Init()); FabricInfo deviceFabric; + auto deviceOpKey = Platform::MakeUnique(); memcpy((uint8_t *) (opKeysSerialized), sTestCert_Node01_01_PublicKey, sTestCert_Node01_01_PublicKey_Len); memcpy((uint8_t *) (opKeysSerialized) + sTestCert_Node01_01_PublicKey_Len, sTestCert_Node01_01_PrivateKey, sTestCert_Node01_01_PrivateKey_Len); ReturnErrorOnFailure(opKeysSerialized.SetLength(sTestCert_Node01_01_PublicKey_Len + sTestCert_Node01_01_PrivateKey_Len)); - ReturnErrorOnFailure(opKey.Deserialize(opKeysSerialized)); - ReturnErrorOnFailure(deviceFabric.SetOperationalKeypair(&opKey)); + ReturnErrorOnFailure(deviceOpKey->Deserialize(opKeysSerialized)); + + gDeviceOperationalKeystore.Init(1, std::move(deviceOpKey)); + ReturnErrorOnFailure(gDeviceFabrics.Init(&gDeviceStorageDelegate, &gDeviceOperationalKeystore)); ReturnErrorOnFailure(deviceFabric.SetRootCert(ByteSpan(sTestCert_Root01_Chip, sTestCert_Root01_Chip_Len))); ReturnErrorOnFailure(deviceFabric.SetICACert(ByteSpan(sTestCert_ICA01_Chip, sTestCert_ICA01_Chip_Len))); @@ -221,18 +266,20 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) ExchangeContext * context = ctx.NewUnauthenticatedExchangeToBob(&pairing); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, nullptr, gCommissionerFabricIndex, Node01_01, nullptr, nullptr, nullptr, - nullptr) != CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, nullptr, ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, nullptr, + nullptr, nullptr, nullptr) != CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, nullptr, - nullptr, nullptr, nullptr) != CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, &gCommissionerFabrics, + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, nullptr, nullptr, nullptr, + nullptr) != CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, - pairing.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, context, - nullptr, nullptr, &delegate) == CHIP_NO_ERROR); + pairing.EstablishSession(sessionManager, &gCommissionerFabrics, + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, context, nullptr, nullptr, + &delegate) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); auto & loopback = ctx.GetLoopback(); @@ -252,8 +299,9 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) ExchangeContext * context1 = ctx.NewUnauthenticatedExchangeToBob(&pairing1); NL_TEST_ASSERT(inSuite, - pairing1.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, context1, - nullptr, nullptr, &delegate) == CHIP_ERROR_BAD_REQUEST); + pairing1.EstablishSession(sessionManager, &gCommissionerFabrics, + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, context1, nullptr, nullptr, + &delegate) == CHIP_ERROR_BAD_REQUEST); ctx.DrainAndServiceIO(); loopback.mMessageSendError = CHIP_NO_ERROR; @@ -287,8 +335,9 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte &delegateAccessory, ScopedNodeId(), MakeOptional(verySleepyAccessoryRmpConfig)) == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, - pairingCommissioner.EstablishSession(sessionManager, &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, - contextCommissioner, nullptr, nullptr, &delegateCommissioner, + pairingCommissioner.EstablishSession(sessionManager, &gCommissionerFabrics, + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, contextCommissioner, + nullptr, nullptr, &delegateCommissioner, MakeOptional(nonSleepyCommissionerRmpConfig)) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); @@ -336,13 +385,10 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner); - FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); - NL_TEST_ASSERT(inSuite, fabric != nullptr); - NL_TEST_ASSERT(inSuite, pairingCommissioner->EstablishSession(ctx.GetSecureSessionManager(), &gCommissionerFabrics, - gCommissionerFabricIndex, Node01_01, contextCommissioner, nullptr, nullptr, - &delegateCommissioner) == CHIP_NO_ERROR); + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, contextCommissioner, + nullptr, nullptr, &delegateCommissioner) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); NL_TEST_ASSERT(inSuite, loopback.mSentMessageCount == sTestCaseMessageCount); @@ -351,7 +397,8 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte // Validate that secure session is created SessionHolder & holder = delegateCommissioner.GetSessionHolder(); NL_TEST_ASSERT(inSuite, bool(holder)); - NL_TEST_ASSERT(inSuite, holder->GetPeer() == fabric->GetScopedNodeIdForNode(Node01_01)); + + NL_TEST_ASSERT(inSuite, (holder->GetPeer() == chip::ScopedNodeId{ Node01_01, gCommissionerFabricIndex })); auto * pairingCommissioner1 = chip::Platform::New(); pairingCommissioner1->SetGroupDataProvider(&gCommissionerGroupDataProvider); @@ -359,8 +406,8 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte NL_TEST_ASSERT(inSuite, pairingCommissioner1->EstablishSession(ctx.GetSecureSessionManager(), &gCommissionerFabrics, - gCommissionerFabricIndex, Node01_01, contextCommissioner1, nullptr, - nullptr, &delegateCommissioner) == CHIP_NO_ERROR); + ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, contextCommissioner1, + nullptr, nullptr, &delegateCommissioner) == CHIP_NO_ERROR); ctx.DrainAndServiceIO(); chip::Platform::Delete(pairingCommissioner); @@ -699,10 +746,10 @@ static void CASE_SessionResumptionStorage(nlTestSuite * inSuite, void * inContex chip::Crypto::P256ECDHDerivedSecret sharedSecretB; // Create our fabric-scoped node IDs. - FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); - NL_TEST_ASSERT(inSuite, fabric != nullptr); - ScopedNodeId initiator = fabric->GetScopedNodeIdForNode(Node01_02); - ScopedNodeId responder = fabric->GetScopedNodeIdForNode(Node01_01); + const FabricInfo * fabricInfo = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); + NL_TEST_ASSERT(inSuite, fabricInfo != nullptr); + ScopedNodeId initiator = fabricInfo->GetScopedNodeIdForNode(Node01_02); + ScopedNodeId responder = fabricInfo->GetScopedNodeIdForNode(Node01_01); // Generate a resumption IDs. NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == chip::Crypto::DRBG_get_bytes(resumptionIdA.data(), resumptionIdA.size())); @@ -764,15 +811,15 @@ static void CASE_SessionResumptionStorage(nlTestSuite * inSuite, void * inContex &gDeviceGroupDataProvider) == CHIP_NO_ERROR); ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner); auto establishmentReturnVal = pairingCommissioner->EstablishSession( - ctx.GetSecureSessionManager(), &gCommissionerFabrics, gCommissionerFabricIndex, Node01_01, contextCommissioner, - &testVectors[i].initiatorStorage, nullptr, &delegateCommissioner); + ctx.GetSecureSessionManager(), &gCommissionerFabrics, ScopedNodeId{ Node01_01, gCommissionerFabricIndex }, + 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); NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == i + 1); SessionHolder & holder = delegateCommissioner.GetSessionHolder(); NL_TEST_ASSERT(inSuite, bool(holder)); - NL_TEST_ASSERT(inSuite, holder->GetPeer() == fabric->GetScopedNodeIdForNode(Node01_01)); + NL_TEST_ASSERT(inSuite, holder->GetPeer() == fabricInfo->GetScopedNodeIdForNode(Node01_01)); chip::Platform::Delete(pairingCommissioner); } } @@ -822,7 +869,6 @@ CHIP_ERROR CASETestSecurePairingSetup(void * inContext) ReturnErrorOnFailure(ctx.Init()); gCommissionerFabrics.Init(&gCommissionerStorageDelegate); - gDeviceFabrics.Init(&gDeviceStorageDelegate); return InitCredentialSets(); } @@ -833,6 +879,8 @@ CHIP_ERROR CASETestSecurePairingSetup(void * inContext) */ int CASE_TestSecurePairing_Setup(void * inContext) { + chip::Platform::MemoryInit(); + CHIP_ERROR err = CASETestSecurePairingSetup(inContext); if (err != CHIP_NO_ERROR) { diff --git a/src/system/SystemLayer.h b/src/system/SystemLayer.h index ee0b72ddaac005..3a91b994475371 100644 --- a/src/system/SystemLayer.h +++ b/src/system/SystemLayer.h @@ -119,8 +119,11 @@ class DLL_EXPORT Layer * * @note * The cancellation could fail silently in two different ways. If the timer specified by the combination of the callback - * function and application state object couldn't be found, cancellation could fail. If the timer has fired, but not yet - * removed from memory, cancellation could also fail. + * function and application state object couldn't be found, cancellation could fail. If the timer has fired, then + * an event is queued and will be processed later. + * + * WARNING: Timer handlers MUST assume that they may be hit even after CancelTimer due to cancelling an + * already fired timer that is queued in the event loop already. * * @param[in] aOnComplete A pointer to the callback function used in calling @p StartTimer(). * @param[in] aAppState A pointer to the application state object used in calling @p StartTimer().