From ad8363432cd739af6d8f4c54ec9d3881edef4289 Mon Sep 17 00:00:00 2001 From: Tennessee Carmel-Veilleux Date: Wed, 30 Mar 2022 17:44:08 -0400 Subject: [PATCH] Implement CASE processing of IPK (#16737) * Implement CASE processing of IPK Problem: - IPK processing used placeholder IPK at innermost levels of CASE and never used the values set in GroupDataProvider as set by Commissioner with AddNOC or KeySetWrite cluster commands. - IPK Processing requires significant state keeping by controllers and commissioners to work, and none of the plumbing existed Changes: - Fixed NOC Cluster setting of IPK in AddNOC that was using the wrong compressed fabric ID. - Properly use GroupDataProvider to get IPK for controllers sending Sigma1 - Properly iterate through all IPK on receiving Sigma1, from GroupDataProvider - Add plumbing to properly initialize GroupDataProvider at CHIPDeviceControllerFactory and Server - Added a central point for a default IPK, that is at the very outermost level, rather than innermost level. A follow-up PR will allow reconfiguration of it for CHIP-tool. Code paths will properly use the OperationalCredentialsDelegate's IPK value passed in Callback, if you it's provided. Controllers and commissioners can setup their GroupDataProvider context to properly use the right group keys even without the follow-up to allow non-default IPK in chip-tool. - Cleaned-up the loose ends around all the injection points Testing done: - Updated all necessary Unit tests - Added IPK and Destination ID unit test cases from spec - All cert tests still pass - All unit tests still pass Fixes #15583 * Restyled by clang-format * Fix a few remaining TODOs * First pass of fixing leftover CI * Restyled by clang-format * More CI fixes * Restyled by clang-format * Apply suggestions from code review Co-authored-by: Michael Sandstedt * Restyled by clang-format * Revert pHYRate change * Hook up IPKs on Darwin. * Fix more CI on Python, Android, TV app * Restyled by clang-format * Apply review comment * Fix Python repl * Restyled by clang-format * Fix shutdown of group data provider * Fix Darwin and Android CI * More fixing of Android CI Co-authored-by: Restyled.io Co-authored-by: Michael Sandstedt Co-authored-by: Boris Zbarsky --- .../chip-tool/commands/common/CHIPCommand.cpp | 32 +- .../chip-tool/commands/common/CHIPCommand.h | 5 + examples/platform/linux/AppMain.cpp | 23 ++ src/app/CASEClient.cpp | 1 + src/app/CASEClient.h | 10 +- src/app/CASESessionManager.cpp | 4 +- src/app/CASESessionManager.h | 12 +- src/app/OperationalDeviceProxy.cpp | 5 +- src/app/OperationalDeviceProxy.h | 22 +- .../operational-credentials-server.cpp | 12 +- src/app/server/Server.cpp | 43 +-- src/app/server/Server.h | 21 +- src/app/tests/TestOperationalDeviceProxy.cpp | 17 +- src/app/tests/TestWriteInteraction.cpp | 13 +- src/controller/CHIPDeviceController.cpp | 8 +- .../CHIPDeviceControllerFactory.cpp | 33 +- src/controller/CHIPDeviceControllerFactory.h | 22 +- .../CHIPDeviceControllerSystemState.h | 9 +- .../ExampleOperationalCredentialsIssuer.cpp | 23 +- .../java/AndroidDeviceControllerWrapper.cpp | 41 +++ .../java/AndroidDeviceControllerWrapper.h | 3 + .../AndroidOperationalCredentialsIssuer.cpp | 22 +- .../ChipDeviceController-ScriptBinding.cpp | 7 + src/controller/python/OpCredsBinding.cpp | 23 ++ .../python/chip/internal/CommissionerImpl.cpp | 27 ++ src/credentials/FabricTable.cpp | 81 ----- src/credentials/FabricTable.h | 12 +- src/credentials/GroupDataProvider.h | 63 +++- src/credentials/GroupDataProviderImpl.cpp | 41 ++- src/credentials/GroupDataProviderImpl.h | 1 + .../tests/TestGroupDataProvider.cpp | 129 ++++++-- src/crypto/CHIPCryptoPAL.cpp | 19 +- src/crypto/CHIPCryptoPAL.h | 7 +- src/crypto/tests/CHIPCryptoPALTest.cpp | 18 +- .../Framework/CHIP/CHIPDeviceController.mm | 73 ++++- .../CHIP/CHIPOperationalCredentialsDelegate.h | 25 +- .../CHIPOperationalCredentialsDelegate.mm | 141 ++++++++- src/lib/core/CHIPConfig.h | 8 +- src/lib/support/TestGroupData.h | 69 ++--- .../support/TestPersistentStorageDelegate.h | 10 +- .../TestTestPersistentStorageDelegate.cpp | 43 ++- src/protocols/secure_channel/BUILD.gn | 2 + .../secure_channel/CASEDestinationId.cpp | 58 ++++ .../secure_channel/CASEDestinationId.h | 37 +++ src/protocols/secure_channel/CASEServer.cpp | 14 +- src/protocols/secure_channel/CASEServer.h | 7 +- src/protocols/secure_channel/CASESession.cpp | 157 +++++++--- src/protocols/secure_channel/CASESession.h | 44 +-- .../secure_channel/tests/TestCASESession.cpp | 285 +++++++++--------- 49 files changed, 1269 insertions(+), 513 deletions(-) create mode 100644 src/protocols/secure_channel/CASEDestinationId.cpp create mode 100644 src/protocols/secure_channel/CASEDestinationId.h diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 7a4741fceb2b7d..a2547e9fe98cc9 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -72,8 +72,20 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() ReturnLogErrorOnFailure(mDefaultStorage.Init()); chip::Controller::FactoryInitParams factoryInitParams; + factoryInitParams.fabricIndependentStorage = &mDefaultStorage; - uint16_t port = mDefaultStorage.GetListenPort(); + + // 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 + // and the DeviceControllerFactory all "share" in the same underlying data. + // Different commissioner implementations may want to use alternate implementations + // of GroupDataProvider for injection through factoryInitParams. + mGroupDataProvider.SetStorageDelegate(&mDefaultStorage); + ReturnLogErrorOnFailure(mGroupDataProvider.Init()); + chip::Credentials::SetGroupDataProvider(&mGroupDataProvider); + factoryInitParams.groupDataProvider = &mGroupDataProvider; + + uint16_t port = mDefaultStorage.GetListenPort(); if (port != 0) { // Make sure different commissioners run on different ports. @@ -101,8 +113,7 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityBeta, kIdentityBetaFabricId, trustStore)); ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityGamma, kIdentityGammaFabricId, trustStore)); - // Initialize Group Data - ReturnLogErrorOnFailure(chip::GroupTesting::InitProvider(mDefaultStorage)); + // Initialize Group Data, including IPK for (auto it = mCommissioners.begin(); it != mCommissioners.end(); it++) { chip::FabricInfo * fabric = it->second->GetFabricInfo(); @@ -111,7 +122,17 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() uint8_t compressed_fabric_id[sizeof(uint64_t)]; chip::MutableByteSpan compressed_fabric_id_span(compressed_fabric_id); ReturnLogErrorOnFailure(fabric->GetCompressedId(compressed_fabric_id_span)); - ReturnLogErrorOnFailure(chip::GroupTesting::InitData(fabric->GetFabricIndex(), compressed_fabric_id_span)); + + ReturnLogErrorOnFailure( + chip::GroupTesting::InitData(&mGroupDataProvider, fabric->GetFabricIndex(), compressed_fabric_id_span)); + + // Configure the default IPK for all fabrics used by CHIP-tool. The epoch + // key is the same, but the derived keys will be different for each fabric. + // This has to be done here after we know the Compressed Fabric ID of all + // chip-tool-managed fabrics + chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + ReturnLogErrorOnFailure(chip::Credentials::SetSingleIpkEpochKey(&mGroupDataProvider, fabric->GetFabricIndex(), + defaultIpk, compressed_fabric_id_span)); } } @@ -287,7 +308,8 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(std::string key, chip::FabricId f commissionerParams.controllerNOC = nocSpan; } - commissionerParams.storageDelegate = &mCommissionerStorage; + commissionerParams.storageDelegate = &mCommissionerStorage; + // TODO: Initialize IPK epoch key in ExampleOperationalCredentials issuer rather than relying on DefaultIpkValue commissionerParams.operationalCredentialsDelegate = mCredIssuerCmds->GetCredentialIssuer(); commissionerParams.controllerVendorId = chip::VendorId::TestVendor1; diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index 670d7c6c7851d0..49e82a1d1407a3 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -22,6 +22,7 @@ #include "Command.h" #include #include +#include #pragma once @@ -52,6 +53,9 @@ class CHIPCommand : public Command using PeerId = ::chip::PeerId; using PeerAddress = ::chip::Transport::PeerAddress; + static constexpr uint16_t kMaxGroupsPerFabric = 5; + static constexpr uint16_t kMaxGroupKeysPerFabric = 8; + CHIPCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds) : Command(commandName), mCredIssuerCmds(credIssuerCmds) { @@ -91,6 +95,7 @@ class CHIPCommand : public Command PersistentStorage mDefaultStorage; PersistentStorage mCommissionerStorage; + chip::Credentials::GroupDataProviderImpl mGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric }; CredentialIssuerCommands * mCredIssuerCmds; std::string GetIdentity(); diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp index 22d9052686fac2..596629efaea7d5 100644 --- a/examples/platform/linux/AppMain.cpp +++ b/examples/platform/linux/AppMain.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -378,6 +379,7 @@ MyCommissionerCallback gCommissionerCallback; MyServerStorageDelegate gServerStorage; ExampleOperationalCredentialsIssuer gOpCredsIssuer; NodeId gLocalId = kMaxOperationalNodeId; +Credentials::GroupDataProviderImpl gGroupDataProvider; CHIP_ERROR InitCommissioner() { @@ -388,6 +390,10 @@ CHIP_ERROR InitCommissioner() factoryParams.listenPort = LinuxDeviceOptions::GetInstance().securedCommissionerPort + 10; factoryParams.fabricIndependentStorage = &gServerStorage; + gGroupDataProvider.SetStorageDelegate(&gServerStorage); + ReturnErrorOnFailure(gGroupDataProvider.Init()); + factoryParams.groupDataProvider = &gGroupDataProvider; + params.storageDelegate = &gServerStorage; params.operationalCredentialsDelegate = &gOpCredsIssuer; @@ -427,6 +433,23 @@ CHIP_ERROR InitCommissioner() auto & factory = Controller::DeviceControllerFactory::GetInstance(); ReturnErrorOnFailure(factory.Init(factoryParams)); ReturnErrorOnFailure(factory.SetupCommissioner(params, gCommissioner)); + + chip::FabricInfo * fabricInfo = gCommissioner.GetFabricInfo(); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INTERNAL); + + uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 }; + MutableByteSpan compressedFabricIdSpan(compressedFabricId); + ReturnErrorOnFailure(fabricInfo->GetCompressedId(compressedFabricIdSpan)); + ChipLogProgress(Support, "Setting up group data for Fabric Index %u with Compressed Fabric ID:", + static_cast(fabricInfo->GetFabricIndex())); + ChipLogByteSpan(Support, compressedFabricIdSpan); + + // TODO: Once ExampleOperationalCredentialsIssuer has support, set default IPK on it as well so + // that commissioned devices get the IPK set from real values rather than "test-only" internal hookups. + ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + ReturnLogErrorOnFailure(chip::Credentials::SetSingleIpkEpochKey(&gGroupDataProvider, fabricInfo->GetFabricIndex(), defaultIpk, + compressedFabricIdSpan)); + gCommissionerDiscoveryController.SetUserDirectedCommissioningServer(gCommissioner.GetUserDirectedCommissioningServer()); gCommissionerDiscoveryController.SetCommissionerCallback(&gCommissionerCallback); diff --git a/src/app/CASEClient.cpp b/src/app/CASEClient.cpp index ab805e3b0d4c93..32b842a2ba2dee 100644 --- a/src/app/CASEClient.cpp +++ b/src/app/CASEClient.cpp @@ -47,6 +47,7 @@ CHIP_ERROR CASEClient::EstablishSession(PeerId peer, const Transport::PeerAddres Messaging::ExchangeContext * exchange = mInitParams.exchangeMgr->NewContext(session.Value(), &mCASESession); VerifyOrReturnError(exchange != nullptr, CHIP_ERROR_INTERNAL); + mCASESession.SetGroupDataProvider(mInitParams.groupDataProvider); ReturnErrorOnFailure(mCASESession.EstablishSession(peerAddress, mInitParams.fabricInfo, peer.GetNodeId(), keyID, exchange, this, mInitParams.mrpLocalConfig)); mConnectionSuccessCallback = onConnection; diff --git a/src/app/CASEClient.h b/src/app/CASEClient.h index f317210360d1ba..6a1c708fc3a7de 100644 --- a/src/app/CASEClient.h +++ b/src/app/CASEClient.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include #include @@ -31,10 +32,11 @@ typedef void (*OnCASEConnectionFailure)(void * context, CASEClient * client, CHI struct CASEClientInitParams { - SessionManager * sessionManager = nullptr; - Messaging::ExchangeManager * exchangeMgr = nullptr; - SessionIDAllocator * idAllocator = nullptr; - FabricInfo * fabricInfo = nullptr; + SessionManager * sessionManager = nullptr; + Messaging::ExchangeManager * exchangeMgr = nullptr; + SessionIDAllocator * idAllocator = nullptr; + FabricInfo * fabricInfo = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; Optional mrpLocalConfig = Optional::Missing(); }; diff --git a/src/app/CASESessionManager.cpp b/src/app/CASESessionManager.cpp index 92c67a6b8f6f49..3e0f9e140501f9 100644 --- a/src/app/CASESessionManager.cpp +++ b/src/app/CASESessionManager.cpp @@ -21,8 +21,10 @@ namespace chip { -CHIP_ERROR CASESessionManager::Init(chip::System::Layer * systemLayer) +CHIP_ERROR CASESessionManager::Init(chip::System::Layer * systemLayer, const CASESessionManagerConfig & params) { + ReturnErrorOnFailure(params.sessionInitParams.Validate()); + mConfig = params; return AddressResolve::Resolver::Instance().Init(systemLayer); } diff --git a/src/app/CASESessionManager.h b/src/app/CASESessionManager.h index 4f7c118c92a92f..8842e3ffaf0c77 100644 --- a/src/app/CASESessionManager.h +++ b/src/app/CASESessionManager.h @@ -52,18 +52,10 @@ struct CASESessionManagerConfig class CASESessionManager { public: - CASESessionManager() = delete; - - CASESessionManager(const CASESessionManagerConfig & params) - { - VerifyOrDie(params.sessionInitParams.Validate() == CHIP_NO_ERROR); - - mConfig = params; - } - + CASESessionManager() = default; virtual ~CASESessionManager() {} - CHIP_ERROR Init(chip::System::Layer * systemLayer); + CHIP_ERROR Init(chip::System::Layer * systemLayer, const CASESessionManagerConfig & params); void Shutdown() {} /** diff --git a/src/app/OperationalDeviceProxy.cpp b/src/app/OperationalDeviceProxy.cpp index 4aad213124c966..efc18d11c3a9b5 100644 --- a/src/app/OperationalDeviceProxy.cpp +++ b/src/app/OperationalDeviceProxy.cpp @@ -167,8 +167,9 @@ bool OperationalDeviceProxy::GetAddress(Inet::IPAddress & addr, uint16_t & port) CHIP_ERROR OperationalDeviceProxy::EstablishConnection() { - mCASEClient = mInitParams.clientPool->Allocate(CASEClientInitParams{ - mInitParams.sessionManager, mInitParams.exchangeMgr, mInitParams.idAllocator, mFabricInfo, mInitParams.mrpLocalConfig }); + mCASEClient = mInitParams.clientPool->Allocate( + CASEClientInitParams{ mInitParams.sessionManager, mInitParams.exchangeMgr, mInitParams.idAllocator, mFabricInfo, + mInitParams.groupDataProvider, mInitParams.mrpLocalConfig }); ReturnErrorCodeIf(mCASEClient == nullptr, CHIP_ERROR_NO_MEMORY); CHIP_ERROR err = mCASEClient->EstablishSession(mPeerId, mDeviceAddress, mMRPConfig, HandleCASEConnected, HandleCASEConnectionFailure, this); diff --git a/src/app/OperationalDeviceProxy.h b/src/app/OperationalDeviceProxy.h index 74806c4a57a4cc..ce5275d6d6e088 100644 --- a/src/app/OperationalDeviceProxy.h +++ b/src/app/OperationalDeviceProxy.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -48,11 +49,12 @@ namespace chip { struct DeviceProxyInitParams { - SessionManager * sessionManager = nullptr; - Messaging::ExchangeManager * exchangeMgr = nullptr; - SessionIDAllocator * idAllocator = nullptr; - FabricTable * fabricTable = nullptr; - CASEClientPoolDelegate * clientPool = nullptr; + SessionManager * sessionManager = nullptr; + Messaging::ExchangeManager * exchangeMgr = nullptr; + SessionIDAllocator * idAllocator = nullptr; + FabricTable * fabricTable = nullptr; + CASEClientPoolDelegate * clientPool = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; Optional mrpLocalConfig = Optional::Missing(); @@ -62,6 +64,7 @@ struct DeviceProxyInitParams ReturnErrorCodeIf(exchangeMgr == nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorCodeIf(idAllocator == nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorCodeIf(fabricTable == nullptr, CHIP_ERROR_INCORRECT_STATE); + ReturnErrorCodeIf(groupDataProvider == nullptr, CHIP_ERROR_INCORRECT_STATE); ReturnErrorCodeIf(clientPool == nullptr, CHIP_ERROR_INCORRECT_STATE); return CHIP_NO_ERROR; @@ -91,10 +94,15 @@ class DLL_EXPORT OperationalDeviceProxy : public DeviceProxy, ~OperationalDeviceProxy() override; OperationalDeviceProxy(DeviceProxyInitParams & params, PeerId peerId) : mSecureSession(*this) { - VerifyOrReturn(params.Validate() == CHIP_NO_ERROR); + mInitParams = params; + // Do not do worse + if (params.Validate() != CHIP_NO_ERROR) + { + mState = State::Uninitialized; + return; + } mSystemLayer = params.exchangeMgr->GetSessionManager()->SystemLayer(); - mInitParams = params; mPeerId = peerId; mFabricInfo = params.fabricTable->FindFabricWithCompressedId(peerId.GetCompressedFabricId()); mState = State::NeedsAddress; 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 66bf36933c8d54..045bcf99dae183 100644 --- a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp +++ b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -589,6 +590,7 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co CHIP_ERROR err = CHIP_NO_ERROR; FabricIndex fabricIndex = 0; Credentials::GroupDataProvider::KeySet keyset; + FabricInfo * newFabricInfo = nullptr; uint8_t compressed_fabric_id_buffer[sizeof(uint64_t)]; MutableByteSpan compressed_fabric_id(compressed_fabric_id_buffer); @@ -650,11 +652,17 @@ bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * co // Set the Identity Protection Key (IPK) VerifyOrExit(ipkValue.size() == Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INVALID_ARGUMENT)); - keyset.keyset_id = 0; // The IPK SHALL be the operational group key under GroupKeySetID of 0 + // The IPK SHALL be the operational group key under GroupKeySetID of 0 + keyset.keyset_id = Credentials::GroupDataProvider::kIdentityProtectionKeySetId; keyset.policy = GroupKeyManagement::GroupKeySecurityPolicy::kTrustFirst; keyset.num_keys_used = 1; memcpy(keyset.epoch_keys[0].key, ipkValue.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES); - err = gFabricBeingCommissioned.GetCompressedId(compressed_fabric_id); + + newFabricInfo = Server::GetInstance().GetFabricTable().FindFabricWithIndex(fabricIndex); + VerifyOrExit(newFabricInfo != nullptr, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INTERNAL)); + err = newFabricInfo->GetCompressedId(compressed_fabric_id); + VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); + err = groups->SetKeySet(fabricIndex, compressed_fabric_id, keyset); VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err)); diff --git a/src/app/server/Server.cpp b/src/app/server/Server.cpp index 5ad4143cc6a31c..498169e2bde733 100644 --- a/src/app/server/Server.cpp +++ b/src/app/server/Server.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -46,7 +47,6 @@ #include #include #include - using namespace chip::DeviceLayer; using chip::kMinValidFabricIndex; @@ -94,26 +94,11 @@ static ::chip::PersistedCounter sGlobalEventIdCounter; static ::chip::app::CircularEventBuffer sLoggingBuffer[CHIP_NUM_EVENT_LOGGING_BUFFERS]; #endif // CHIP_CONFIG_ENABLE_SERVER_IM_EVENT -Server::Server() : - mCASESessionManager(CASESessionManagerConfig { - .sessionInitParams = { - .sessionManager = &mSessions, - .exchangeMgr = &mExchangeMgr, - .idAllocator = &mSessionIDAllocator, - .fabricTable = &mFabrics, - .clientPool = &mCASEClientPool, - }, -#if CHIP_CONFIG_MDNS_CACHE_SIZE > 0 - .dnsCache = nullptr, -#endif - .devicePool = &mDevicePool, - }) -{} - CHIP_ERROR Server::Init(AppDelegate * delegate, uint16_t secureServicePort, uint16_t unsecureServicePort, Inet::InterfaceId interfaceId) { Access::AccessControl::Delegate * accessDelegate = nullptr; + CASESessionManagerConfig caseSessionManagerConfig; mSecuredServicePort = secureServicePort; mUnsecuredServicePort = unsecureServicePort; @@ -187,7 +172,7 @@ CHIP_ERROR Server::Init(AppDelegate * delegate, uint16_t secureServicePort, uint err = mSessions.Init(&DeviceLayer::SystemLayer(), &mTransports, &mMessageCounterManager, &mDeviceStorage, &GetFabricTable()); SuccessOrExit(err); - err = mFabricDelegate.Init(&mSessions); + err = mFabricDelegate.Init(this); SuccessOrExit(err); mFabrics.AddFabricDelegate(&mFabricDelegate); @@ -252,15 +237,31 @@ CHIP_ERROR Server::Init(AppDelegate * delegate, uint16_t secureServicePort, uint app::DnssdServer::Instance().StartServer(); #endif + caseSessionManagerConfig = { + .sessionInitParams = { + .sessionManager = &mSessions, + .exchangeMgr = &mExchangeMgr, + .idAllocator = &mSessionIDAllocator, + .fabricTable = &mFabrics, + .clientPool = &mCASEClientPool, + .groupDataProvider = &mGroupsProvider, + }, +#if CHIP_CONFIG_MDNS_CACHE_SIZE > 0 + .dnsCache = nullptr, +#endif + .devicePool = &mDevicePool, + }; + + err = mCASESessionManager.Init(&DeviceLayer::SystemLayer(), caseSessionManagerConfig); + SuccessOrExit(err); + err = mCASEServer.ListenForSessionEstablishment(&mExchangeMgr, &mTransports, #if CONFIG_NETWORK_LAYER_BLE chip::DeviceLayer::ConnectivityMgr().GetBleLayer(), #endif - &mSessions, &mFabrics); + &mSessions, &mFabrics, &mGroupsProvider); SuccessOrExit(err); - err = mCASESessionManager.Init(&DeviceLayer::SystemLayer()); - // This code is necessary to restart listening to existing groups after a reboot // Each manufacturer needs to validate that they can rejoin groups by placing this code at the appropriate location for them // diff --git a/src/app/server/Server.h b/src/app/server/Server.h index ca5bdcfa99a394..78adc35bfb292e 100644 --- a/src/app/server/Server.h +++ b/src/app/server/Server.h @@ -86,6 +86,8 @@ class Server TransportMgrBase & GetTransportManager() { return mTransports; } + Credentials::GroupDataProvider * GetGroupDataProvider() { return &mGroupsProvider; } + #if CONFIG_NETWORK_LAYER_BLE Ble::BleLayer * GetBleLayerObject() { return mBleLayer; } #endif @@ -107,7 +109,7 @@ class Server static Server & GetInstance() { return sServer; } private: - Server(); + Server() = default; static Server sServer; @@ -203,22 +205,21 @@ class Server public: ServerFabricDelegate() {} - CHIP_ERROR Init(SessionManager * sessionManager) + CHIP_ERROR Init(Server * server) { - VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(server != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - mSessionManager = sessionManager; + mServer = server; return CHIP_NO_ERROR; }; void OnFabricDeletedFromStorage(CompressedFabricId compressedId, FabricIndex fabricIndex) override { (void) compressedId; - if (mSessionManager != nullptr) - { - mSessionManager->FabricRemoved(fabricIndex); - } - Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider(); + auto & sessionManager = mServer->GetSecureSessionManager(); + sessionManager.FabricRemoved(fabricIndex); + + Credentials::GroupDataProvider * groupDataProvider = mServer->GetGroupDataProvider(); if (groupDataProvider != nullptr) { groupDataProvider->RemoveFabric(fabricIndex); @@ -230,7 +231,7 @@ class Server void OnFabricPersistedToStorage(FabricInfo * fabricInfo) override { (void) fabricInfo; } private: - SessionManager * mSessionManager = nullptr; + Server * mServer = nullptr; }; #if CONFIG_NETWORK_LAYER_BLE diff --git a/src/app/tests/TestOperationalDeviceProxy.cpp b/src/app/tests/TestOperationalDeviceProxy.cpp index bc963f3837f470..cf31ff169520ee 100644 --- a/src/app/tests/TestOperationalDeviceProxy.cpp +++ b/src/app/tests/TestOperationalDeviceProxy.cpp @@ -15,6 +15,7 @@ * limitations under the License. */ +#include #include #include #include @@ -32,6 +33,7 @@ using namespace chip; using namespace chip::Transport; using namespace chip::Messaging; +using namespace chip::Credentials; #if INET_CONFIG_ENABLE_IPV4 namespace { @@ -40,6 +42,7 @@ using TestTransportMgr = TransportMgr; void TestOperationalDeviceProxy_EstablishSessionDirectly(nlTestSuite * inSuite, void * inContext) { + // TODO: This test appears not to be workable since it does not init the fabric table!!! Platform::MemoryInit(); TestTransportMgr transportMgr; SessionManager sessionManager; @@ -50,9 +53,11 @@ void TestOperationalDeviceProxy_EstablishSessionDirectly(nlTestSuite * inSuite, // stack. FabricTable * fabrics = Platform::New(); FabricInfo * fabric = fabrics->FindFabricWithIndex(1); + VerifyOrDie(fabric != nullptr); secure_channel::MessageCounterManager messageCounterManager; chip::TestPersistentStorageDelegate deviceStorage; SessionIDAllocator idAllocator; + GroupDataProviderImpl groupDataProvider; systemLayer.Init(); udpEndPointManager.Init(systemLayer); @@ -60,12 +65,16 @@ void TestOperationalDeviceProxy_EstablishSessionDirectly(nlTestSuite * inSuite, sessionManager.Init(&systemLayer, &transportMgr, &messageCounterManager, &deviceStorage); exchangeMgr.Init(&sessionManager); messageCounterManager.Init(&exchangeMgr); + groupDataProvider.SetPersistentStorage(&deviceStorage); + VerifyOrDie(groupDataProvider.Init() == CHIP_NO_ERROR); + // TODO: Set IPK in groupDataProvider DeviceProxyInitParams params = { - .sessionManager = &sessionManager, - .exchangeMgr = &exchangeMgr, - .idAllocator = &idAllocator, - .fabricInfo = fabric, + .sessionManager = &sessionManager, + .exchangeMgr = &exchangeMgr, + .idAllocator = &idAllocator, + .fabricInfo = fabric, + .groupDataProvider = &groupDataProvider, }; NodeId mockNodeId = 1; OperationalDeviceProxy device(params, PeerId().SetNodeId(mockNodeId)); diff --git a/src/app/tests/TestWriteInteraction.cpp b/src/app/tests/TestWriteInteraction.cpp index 4e9c5eac95f81b..85fb6552f70eeb 100644 --- a/src/app/tests/TestWriteInteraction.cpp +++ b/src/app/tests/TestWriteInteraction.cpp @@ -40,6 +40,12 @@ namespace { uint8_t attributeDataTLV[CHIP_CONFIG_DEFAULT_UDP_MTU_SIZE]; size_t attributeDataTLVLen = 0; +constexpr uint16_t kMaxGroupsPerFabric = 5; +constexpr uint16_t kMaxGroupKeysPerFabric = 8; + +chip::TestPersistentStorageDelegate gTestStorage; +chip::Credentials::GroupDataProviderImpl gGroupsProvider(kMaxGroupsPerFabric, kMaxGroupKeysPerFabric); + } // namespace namespace chip { namespace app { @@ -480,12 +486,15 @@ int Test_Setup(void * inContext) VerifyOrReturnError(TestContext::InitializeAsync(inContext) == SUCCESS, FAILURE); TestContext & ctx = *static_cast(inContext); - VerifyOrReturnError(CHIP_NO_ERROR == chip::GroupTesting::InitProvider(), FAILURE); + gTestStorage.ClearStorage(); + gGroupsProvider.SetStorageDelegate(&gTestStorage); + VerifyOrReturnError(CHIP_NO_ERROR == gGroupsProvider.Init(), FAILURE); + chip::Credentials::SetGroupDataProvider(&gGroupsProvider); uint8_t buf[sizeof(chip::CompressedFabricId)]; chip::MutableByteSpan span(buf); VerifyOrReturnError(CHIP_NO_ERROR == ctx.GetBobFabric()->GetCompressedId(span), FAILURE); - VerifyOrReturnError(CHIP_NO_ERROR == chip::GroupTesting::InitData(ctx.GetBobFabricIndex(), span), FAILURE); + VerifyOrReturnError(CHIP_NO_ERROR == chip::GroupTesting::InitData(&gGroupsProvider, ctx.GetBobFabricIndex(), span), FAILURE); return SUCCESS; } diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 8b8a168a134288..d208a99d00bbad 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -1019,10 +1019,14 @@ void DeviceCommissioner::OnDeviceNOCChainGeneration(void * context, CHIP_ERROR s MATTER_TRACE_EVENT_SCOPE("OnDeviceNOCChainGeneration", "DeviceCommissioner"); DeviceCommissioner * commissioner = static_cast(context); - // TODO(#13825): If not passed by the signer, the commissioner should - // provide its current IPK to the commissionee in the AddNOC command. + // The placeholder IPK is not satisfactory, but is there to fill the NocChain struct on error. It will still fail. const uint8_t placeHolderIpk[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + if (!ipk.HasValue()) + { + ChipLogError(Controller, "Did not have an IPK from the OperationalCredentialsIssuer! Cannot commission."); + status = CHIP_ERROR_INVALID_ARGUMENT; + } ChipLogProgress(Controller, "Received callback from the CA for NOC Chain generation. Status %s", ErrorStr(status)); if (commissioner->mState != State::Initialized) diff --git a/src/controller/CHIPDeviceControllerFactory.cpp b/src/controller/CHIPDeviceControllerFactory.cpp index 92668b255a0d5a..3f0c03cf1f7717 100644 --- a/src/controller/CHIPDeviceControllerFactory.cpp +++ b/src/controller/CHIPDeviceControllerFactory.cpp @@ -80,6 +80,7 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState() params.listenPort = mListenPort; params.fabricIndependentStorage = mFabricIndependentStorage; params.enableServerInteractions = mEnableServerInteractions; + params.groupDataProvider = mSystemState->GetGroupDataProvider(); } return InitSystemState(params); @@ -149,10 +150,12 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.sessionMgr = chip::Platform::New(); stateParams.exchangeMgr = chip::Platform::New(); stateParams.messageCounterManager = chip::Platform::New(); + stateParams.groupDataProvider = params.groupDataProvider; ReturnErrorOnFailure(stateParams.fabricTable->Init(params.fabricIndependentStorage)); - auto delegate = chip::Platform::MakeUnique(stateParams.sessionMgr); + auto delegate = chip::Platform::MakeUnique(); + ReturnErrorOnFailure(delegate->Init(stateParams.sessionMgr, stateParams.groupDataProvider)); ReturnErrorOnFailure(stateParams.fabricTable->AddFabricDelegate(delegate.get())); delegate.release(); @@ -178,12 +181,12 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) // We pass in a nullptr for the BLELayer since we're not permitting usage of BLE in this server modality for the controller, // especially since it will interrupt other potential usages of BLE by the controller acting in a commissioning capacity. // - ReturnErrorOnFailure( - stateParams.caseServer->ListenForSessionEstablishment(stateParams.exchangeMgr, stateParams.transportMgr, + ReturnErrorOnFailure(stateParams.caseServer->ListenForSessionEstablishment( + stateParams.exchangeMgr, stateParams.transportMgr, #if CONFIG_NETWORK_LAYER_BLE - nullptr, + nullptr, #endif - stateParams.sessionMgr, stateParams.fabricTable)); + stateParams.sessionMgr, stateParams.fabricTable, stateParams.groupDataProvider)); // // We need to advertise the port that we're listening to for unsolicited messages over UDP. However, we have both a IPv4 @@ -216,12 +219,13 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) stateParams.caseClientPool = Platform::New(); DeviceProxyInitParams deviceInitParams = { - .sessionManager = stateParams.sessionMgr, - .exchangeMgr = stateParams.exchangeMgr, - .idAllocator = stateParams.sessionIDAllocator, - .fabricTable = stateParams.fabricTable, - .clientPool = stateParams.caseClientPool, - .mrpLocalConfig = Optional::Value(GetLocalMRPConfig()), + .sessionManager = stateParams.sessionMgr, + .exchangeMgr = stateParams.exchangeMgr, + .idAllocator = stateParams.sessionIDAllocator, + .fabricTable = stateParams.fabricTable, + .clientPool = stateParams.caseClientPool, + .groupDataProvider = stateParams.groupDataProvider, + .mrpLocalConfig = Optional::Value(GetLocalMRPConfig()), }; CASESessionManagerConfig sessionManagerConfig = { @@ -233,8 +237,8 @@ CHIP_ERROR DeviceControllerFactory::InitSystemState(FactoryInitParams params) }; // TODO: Need to be able to create a CASESessionManagerConfig here! - stateParams.caseSessionManager = Platform::New(sessionManagerConfig); - ReturnErrorOnFailure(stateParams.caseSessionManager->Init(stateParams.systemLayer)); + stateParams.caseSessionManager = Platform::New(); + ReturnErrorOnFailure(stateParams.caseSessionManager->Init(stateParams.systemLayer, sessionManagerConfig)); // store the system state mSystemState = chip::Platform::New(stateParams); @@ -275,7 +279,10 @@ CHIP_ERROR DeviceControllerFactory::SetupCommissioner(SetupParams params, Device ReturnErrorOnFailure(InitSystemState()); CommissionerInitParams commissionerParams; + // PopulateInitParams works against ControllerInitParams base class of CommissionerInitParams only PopulateInitParams(commissionerParams, params); + + // Set commissioner-specific fields not in ControllerInitParams commissionerParams.pairingDelegate = params.pairingDelegate; commissionerParams.defaultCommissioner = params.defaultCommissioner; diff --git a/src/controller/CHIPDeviceControllerFactory.h b/src/controller/CHIPDeviceControllerFactory.h index 4b2eec0416ded2..a4880b5340c491 100644 --- a/src/controller/CHIPDeviceControllerFactory.h +++ b/src/controller/CHIPDeviceControllerFactory.h @@ -31,7 +31,7 @@ #include #include -#include +#include #include namespace chip { @@ -71,12 +71,13 @@ struct SetupParams CommissioningDelegate * defaultCommissioner = nullptr; }; -// TODO everything other than the fabric storage here should be removed. +// 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 struct FactoryInitParams { System::Layer * systemLayer = nullptr; PersistentStorageDelegate * fabricIndependentStorage = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; Inet::EndPointManager * tcpEndPointManager = nullptr; Inet::EndPointManager * udpEndPointManager = nullptr; #if CONFIG_NETWORK_LAYER_BLE @@ -154,14 +155,15 @@ class DeviceControllerFactory class ControllerFabricDelegate final : public FabricTableDelegate { public: - ControllerFabricDelegate() {} - ControllerFabricDelegate(SessionManager * sessionManager) : FabricTableDelegate(true), mSessionManager(sessionManager) {} + ControllerFabricDelegate() : FabricTableDelegate(true) {} - CHIP_ERROR Init(SessionManager * sessionManager) + CHIP_ERROR Init(SessionManager * sessionManager, Credentials::GroupDataProvider * groupDataProvider) { VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(groupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - mSessionManager = sessionManager; + mSessionManager = sessionManager; + mGroupDataProvider = groupDataProvider; return CHIP_NO_ERROR; }; @@ -171,10 +173,9 @@ class DeviceControllerFactory { mSessionManager->FabricRemoved(fabricIndex); } - Credentials::GroupDataProvider * groupDataProvider = Credentials::GetGroupDataProvider(); - if (groupDataProvider != nullptr) + if (mGroupDataProvider != nullptr) { - groupDataProvider->RemoveFabric(fabricIndex); + mGroupDataProvider->RemoveFabric(fabricIndex); } }; @@ -183,7 +184,8 @@ class DeviceControllerFactory void OnFabricPersistedToStorage(FabricInfo * fabricInfo) override { (void) fabricInfo; } private: - SessionManager * mSessionManager = nullptr; + SessionManager * mSessionManager = nullptr; + Credentials::GroupDataProvider * mGroupDataProvider = nullptr; }; private: diff --git a/src/controller/CHIPDeviceControllerSystemState.h b/src/controller/CHIPDeviceControllerSystemState.h index 869d77c1ca68a1..a3818527e99463 100644 --- a/src/controller/CHIPDeviceControllerSystemState.h +++ b/src/controller/CHIPDeviceControllerSystemState.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -90,6 +91,7 @@ struct DeviceControllerSystemStateParams SessionIDAllocator * sessionIDAllocator = nullptr; OperationalDevicePool * operationalDevicePool = nullptr; CASEClientPool * caseClientPool = nullptr; + Credentials::GroupDataProvider * groupDataProvider = nullptr; }; // A representation of the internal state maintained by the DeviceControllerFactory @@ -108,7 +110,7 @@ class DeviceControllerSystemState mExchangeMgr(params.exchangeMgr), mMessageCounterManager(params.messageCounterManager), mFabrics(params.fabricTable), mCASEServer(params.caseServer), mCASESessionManager(params.caseSessionManager), mSessionIDAllocator(params.sessionIDAllocator), mOperationalDevicePool(params.operationalDevicePool), - mCASEClientPool(params.caseClientPool) + mCASEClientPool(params.caseClientPool), mGroupDataProvider(params.groupDataProvider) { #if CONFIG_NETWORK_LAYER_BLE mBleLayer = params.bleLayer; @@ -141,7 +143,8 @@ class DeviceControllerSystemState { return mSystemLayer != nullptr && mUDPEndPointManager != nullptr && mTransportMgr != nullptr && mSessionMgr != nullptr && mExchangeMgr != nullptr && mMessageCounterManager != nullptr && mFabrics != nullptr && mCASESessionManager != nullptr && - mSessionIDAllocator != nullptr && mOperationalDevicePool != nullptr && mCASEClientPool != nullptr; + mSessionIDAllocator != nullptr && mOperationalDevicePool != nullptr && mCASEClientPool != nullptr && + mGroupDataProvider != nullptr; }; System::Layer * SystemLayer() { return mSystemLayer; }; @@ -157,6 +160,7 @@ class DeviceControllerSystemState #endif CASESessionManager * CASESessionMgr() const { return mCASESessionManager; } SessionIDAllocator * SessionIDAlloc() const { return mSessionIDAllocator; } + Credentials::GroupDataProvider * GetGroupDataProvider() const { return mGroupDataProvider; } private: DeviceControllerSystemState(){}; @@ -177,6 +181,7 @@ class DeviceControllerSystemState SessionIDAllocator * mSessionIDAllocator = nullptr; OperationalDevicePool * mOperationalDevicePool = nullptr; CASEClientPool * mCASEClientPool = nullptr; + Credentials::GroupDataProvider * mGroupDataProvider = nullptr; std::atomic mRefCount{ 1 }; diff --git a/src/controller/ExampleOperationalCredentialsIssuer.cpp b/src/controller/ExampleOperationalCredentialsIssuer.cpp index e0884d12cc0eac..d273ca169194d8 100644 --- a/src/controller/ExampleOperationalCredentialsIssuer.cpp +++ b/src/controller/ExampleOperationalCredentialsIssuer.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace chip { namespace Controller { @@ -229,9 +230,27 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan ReturnErrorOnFailure( GenerateNOCChainAfterValidation(assignedId, mNextFabricId, chip::kUndefinedCATs, pubkey, rcacSpan, icacSpan, nocSpan)); + // TODO(#13825): Should always generate some IPK. Using a temporary fixed value until APIs are plumbed in to set it end-to-end + // TODO: Force callers to set IPK if used before GenerateNOCChain will succeed. + ByteSpan defaultIpkSpan = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + + // The below static assert validates a key assumption in types used (needed for public API conformance) + static_assert(CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == kAES_CCM128_Key_Length, "IPK span sizing must match"); + + // Prepare IPK to be sent back. A more fully-fledged operational credentials delegate + // would obtain a suitable key per fabric. + uint8_t ipkValue[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES]; + Crypto::AesCcm128KeySpan ipkSpan(ipkValue); + + ReturnErrorCodeIf(defaultIpkSpan.size() != sizeof(ipkValue), CHIP_ERROR_INTERNAL); + + memcpy(&ipkValue[0], defaultIpkSpan.data(), defaultIpkSpan.size()); + Optional ipkSpanValue; + ipkSpanValue.SetValue(ipkSpan); + + // Callback onto commissioner. ChipLogProgress(Controller, "Providing certificate chain to the commissioner"); - onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, nocSpan, icacSpan, rcacSpan, Optional(), - Optional()); + onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, nocSpan, icacSpan, rcacSpan, ipkSpanValue, Optional()); return CHIP_NO_ERROR; } diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 2c30deae8981a2..39212b181dd11f 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -129,6 +130,17 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle setupParams.operationalCredentialsDelegate = opCredsIssuer; initParams.fabricIndependentStorage = setupParams.storageDelegate; + wrapper->mGroupDataProvider.SetStorageDelegate(setupParams.storageDelegate); + + CHIP_ERROR err = wrapper->mGroupDataProvider.Init(); + if (err != CHIP_NO_ERROR) + { + *errInfoOnFailure = err; + return nullptr; + } + initParams.groupDataProvider = &wrapper->mGroupDataProvider; + + // TODO: Init IPK Epoch Key in opcreds issuer, so that commissionees get the right IPK opCredsIssuer->Initialize(*wrapper.get(), wrapper.get()->mJavaObjectRef); Platform::ScopedMemoryBuffer noc; @@ -186,6 +198,35 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle return nullptr; } + // Setup IPK + chip::FabricInfo * fabricInfo = wrapper->Controller()->GetFabricInfo(); + if (fabricInfo == nullptr) + { + *errInfoOnFailure = CHIP_ERROR_INTERNAL; + return nullptr; + } + + uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 }; + chip::MutableByteSpan compressedFabricIdSpan(compressedFabricId); + + *errInfoOnFailure = fabricInfo->GetCompressedId(compressedFabricIdSpan); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + ChipLogProgress(Support, "Setting up group data for Fabric Index %u with Compressed Fabric ID:", + static_cast(fabricInfo->GetFabricIndex())); + ChipLogByteSpan(Support, compressedFabricIdSpan); + + chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + + *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey(&wrapper->mGroupDataProvider, fabricInfo->GetFabricIndex(), + defaultIpk, compressedFabricIdSpan); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + return wrapper.release(); } diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index 24c87433a0ff27..7600dd68fd1c38 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -83,6 +84,8 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel ChipDeviceControllerPtr mController; AndroidOperationalCredentialsIssuerPtr mOpCredsIssuer; + // TODO: This may need to be injected as a GroupDataProvider* + chip::Credentials::GroupDataProviderImpl mGroupDataProvider; JavaVM * mJavaVM = nullptr; jobject mJavaObjectRef = nullptr; diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp index 6e14f5a72729b8..bedc27a4bec7d5 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -181,8 +182,25 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan ReturnErrorOnFailure( GenerateNOCChainAfterValidation(assignedId, mNextFabricId, chip::kUndefinedCATs, pubkey, rcacSpan, icacSpan, nocSpan)); - onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, nocSpan, ByteSpan(), rcacSpan, Optional(), - Optional()); + // TODO: Force callers to set IPK if used before GenerateNOCChain will succeed. + ByteSpan defaultIpkSpan = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + + // The below static assert validates a key assumption in types used (needed for public API conformance) + static_assert(CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == kAES_CCM128_Key_Length, "IPK span sizing must match"); + + // Prepare IPK to be sent back. A more fully-fledged operational credentials delegate + // would obtain a suitable key per fabric. + uint8_t ipkValue[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES]; + Crypto::AesCcm128KeySpan ipkSpan(ipkValue); + + ReturnErrorCodeIf(defaultIpkSpan.size() != sizeof(ipkValue), CHIP_ERROR_INTERNAL); + + memcpy(&ipkValue[0], defaultIpkSpan.data(), defaultIpkSpan.size()); + Optional ipkSpanValue; + ipkSpanValue.SetValue(ipkSpan); + + // Call-back into commissioner with the generated data. + onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, nocSpan, ByteSpan(), rcacSpan, ipkSpanValue, Optional()); jbyteArray javaCsr; JniReferences::GetInstance().GetEnvForCurrentThread()->ExceptionClear(); diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 919809b4479b21..e795cf5a8e055c 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,7 @@ chip::Controller::CommissioningParameters sCommissioningParameters; chip::Controller::ScriptDevicePairingDelegate sPairingDelegate; chip::Controller::Python::StorageAdapter * sStorageAdapter = nullptr; +chip::Credentials::GroupDataProviderImpl sGroupDataProvider; // NOTE: Remote device ID is in sync with the echo server device id // At some point, we may want to add an option to connect to a device without @@ -216,6 +218,11 @@ ChipError::StorageType pychip_DeviceController_StackInit() FactoryInitParams factoryParams; factoryParams.fabricIndependentStorage = sStorageAdapter; + + sGroupDataProvider.SetStorageDelegate(sStorageAdapter); + ReturnErrorOnFailure(sGroupDataProvider.Init().AsInteger()); + + factoryParams.groupDataProvider = &sGroupDataProvider; factoryParams.enableServerInteractions = true; ReturnErrorOnFailure(DeviceControllerFactory::GetInstance().Init(factoryParams).AsInteger()); diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index a98bb834ea894b..4a08a2a0402759 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -33,8 +33,10 @@ #include #include #include +#include #include +#include #include #include #include @@ -96,7 +98,9 @@ class OperationalCredentialsAdapter : public OperationalCredentialsDelegate extern chip::Controller::Python::StorageAdapter * pychip_Storage_GetStorageAdapter(); extern chip::Controller::Python::StorageAdapter * sStorageAdapter; +extern chip::Credentials::GroupDataProviderImpl sGroupDataProvider; extern chip::Controller::ScriptDevicePairingDelegate sPairingDelegate; + bool sTestCommissionerUsed = false; class TestCommissioner : public chip::Controller::AutoCommissioner { @@ -192,6 +196,25 @@ ChipError::StorageType pychip_OpCreds_AllocateController(OpCredsContext * contex err = Controller::DeviceControllerFactory::GetInstance().SetupCommissioner(initParams, *devCtrl); VerifyOrReturnError(err == CHIP_NO_ERROR, err.AsInteger()); + // Setup IPK in Group Data Provider for controller after Commissioner init which sets-up the fabric table entry + FabricInfo * fabricInfo = devCtrl->GetFabricInfo(); + VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INTERNAL.AsInteger()); + + uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 }; + chip::MutableByteSpan compressedFabricIdSpan(compressedFabricId); + + err = fabricInfo->GetCompressedId(compressedFabricIdSpan); + VerifyOrReturnError(err == CHIP_NO_ERROR, err.AsInteger()); + + ChipLogProgress(Support, "Setting up group data for Fabric Index %u with Compressed Fabric ID:", + static_cast(fabricInfo->GetFabricIndex())); + ChipLogByteSpan(Support, compressedFabricIdSpan); + + chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + err = chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, fabricInfo->GetFabricIndex(), defaultIpk, + compressedFabricIdSpan); + VerifyOrReturnError(err == CHIP_NO_ERROR, err.AsInteger()); + *outDevCtrl = devCtrl.release(); return CHIP_NO_ERROR.AsInteger(); diff --git a/src/controller/python/chip/internal/CommissionerImpl.cpp b/src/controller/python/chip/internal/CommissionerImpl.cpp index 318543d707b4af..1c66349012cc21 100644 --- a/src/controller/python/chip/internal/CommissionerImpl.cpp +++ b/src/controller/python/chip/internal/CommissionerImpl.cpp @@ -19,11 +19,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -91,6 +93,7 @@ class ScriptDevicePairingDelegate final : public chip::Controller::DevicePairing ServerStorageDelegate gServerStorage; ScriptDevicePairingDelegate gPairingDelegate; +chip::Credentials::GroupDataProviderImpl gGroupDataProvider; chip::Controller::ExampleOperationalCredentialsIssuer gOperationalCredentialsIssuer; } // namespace @@ -127,6 +130,12 @@ extern "C" chip::Controller::DeviceCommissioner * pychip_internal_Commissioner_N factoryParams.fabricIndependentStorage = &gServerStorage; + // Initialize group data provider for local group key state and IPKs + gGroupDataProvider.SetStorageDelegate(&gServerStorage); + err = gGroupDataProvider.Init(); + SuccessOrExit(err); + factoryParams.groupDataProvider = &gGroupDataProvider; + commissionerParams.pairingDelegate = &gPairingDelegate; commissionerParams.storageDelegate = &gServerStorage; @@ -145,9 +154,15 @@ extern "C" chip::Controller::DeviceCommissioner * pychip_internal_Commissioner_N VerifyOrExit(rcac.Alloc(chip::Controller::kMaxCHIPDERCertLength), err = CHIP_ERROR_NO_MEMORY); { + chip::FabricInfo * fabricInfo = nullptr; + uint8_t compressedFabricId[sizeof(uint64_t)] = { 0 }; + chip::MutableByteSpan compressedFabricIdSpan(compressedFabricId); + chip::ByteSpan defaultIpk; + chip::MutableByteSpan nocSpan(noc.Get(), chip::Controller::kMaxCHIPDERCertLength); chip::MutableByteSpan icacSpan(icac.Get(), chip::Controller::kMaxCHIPDERCertLength); chip::MutableByteSpan rcacSpan(rcac.Get(), chip::Controller::kMaxCHIPDERCertLength); + err = gOperationalCredentialsIssuer.GenerateNOCChainAfterValidation( localDeviceId, /* fabricId = */ 1, { { localCommissionerCAT, chip::kUndefinedCAT, chip::kUndefinedCAT } }, ephemeralKey.Pubkey(), rcacSpan, icacSpan, nocSpan); @@ -161,6 +176,18 @@ extern "C" chip::Controller::DeviceCommissioner * pychip_internal_Commissioner_N SuccessOrExit(DeviceControllerFactory::GetInstance().Init(factoryParams)); err = DeviceControllerFactory::GetInstance().SetupCommissioner(commissionerParams, *result); + + fabricInfo = result->GetFabricInfo(); + VerifyOrExit(fabricInfo != nullptr, err = CHIP_ERROR_INTERNAL); + + SuccessOrExit(fabricInfo->GetCompressedId(compressedFabricIdSpan)); + ChipLogProgress(Support, "Setting up group data for Fabric Index %u with Compressed Fabric ID:", + static_cast(fabricInfo->GetFabricIndex())); + ChipLogByteSpan(Support, compressedFabricIdSpan); + + defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + SuccessOrExit(chip::Credentials::SetSingleIpkEpochKey(&gGroupDataProvider, fabricInfo->GetFabricIndex(), defaultIpk, + compressedFabricIdSpan)); } exit: ChipLogProgress(Controller, "Commissioner initialization status: %s", chip::ErrorStr(err)); diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index 48d6eb6dc0127b..66fbc2f4c4fa77 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -282,12 +282,7 @@ CHIP_ERROR FabricInfo::GeneratePeerId(FabricId fabricId, NodeId nodeId, PeerId * rootPubkey = rootPubkeySpan; } - ChipLogDetail(Inet, "Generating compressed fabric ID using uncompressed fabric ID 0x" ChipLogFormatX64 " and root pubkey", - ChipLogValueX64(fabricId)); - ChipLogByteSpan(Inet, ByteSpan(rootPubkey.ConstBytes(), rootPubkey.Length())); ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubkey, fabricId, compressedFabricIdSpan)); - ChipLogDetail(Inet, "Generated compressed fabric ID"); - ChipLogByteSpan(Inet, compressedFabricIdSpan); // Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId() // returns a binary buffer and is agnostic of usage of the output as an integer type. @@ -437,68 +432,6 @@ CHIP_ERROR FabricInfo::VerifyCredentials(const ByteSpan & noc, const ByteSpan & return CHIP_NO_ERROR; } -CHIP_ERROR FabricInfo::GenerateDestinationID(const ByteSpan & ipk, const ByteSpan & random, NodeId destNodeId, - MutableByteSpan & destinationId) const -{ - constexpr uint16_t kSigmaParamRandomNumberSize = 32; - constexpr size_t kDestinationMessageLen = - kSigmaParamRandomNumberSize + kP256_PublicKey_Length + sizeof(FabricId) + sizeof(NodeId); - HMAC_sha hmac; - uint8_t destinationMessage[kDestinationMessageLen]; - P256PublicKeySpan rootPubkeySpan; - - ReturnErrorOnFailure(GetRootPubkey(rootPubkeySpan)); - - Encoding::LittleEndian::BufferWriter bbuf(destinationMessage, sizeof(destinationMessage)); - - ChipLogDetail(Inet, - "Generating DestinationID. Fabric ID 0x" ChipLogFormatX64 ", Dest node ID 0x" ChipLogFormatX64 ", Random data", - ChipLogValueX64(mFabricId), ChipLogValueX64(destNodeId)); - ChipLogByteSpan(Inet, random); - - bbuf.Put(random.data(), random.size()); - // TODO: In the current implementation this check is required because in some cases the - // GenerateDestinationID() is called before mRootCert is initialized and GetRootPubkey() returns - // empty Span. - if (!rootPubkeySpan.empty()) - { - ChipLogDetail(Inet, "Root pubkey"); - ChipLogByteSpan(Inet, rootPubkeySpan); - bbuf.Put(rootPubkeySpan.data(), rootPubkeySpan.size()); - } - bbuf.Put64(mFabricId); - bbuf.Put64(destNodeId); - - size_t written = 0; - VerifyOrReturnError(bbuf.Fit(written), CHIP_ERROR_BUFFER_TOO_SMALL); - - ChipLogDetail(Inet, "IPK"); - ChipLogByteSpan(Inet, ipk); - - CHIP_ERROR err = - hmac.HMAC_SHA256(ipk.data(), ipk.size(), destinationMessage, written, destinationId.data(), destinationId.size()); - ChipLogDetail(Inet, "Generated DestinationID output"); - ChipLogByteSpan(Inet, destinationId); - return err; -} - -CHIP_ERROR FabricInfo::MatchDestinationID(const ByteSpan & targetDestinationId, const ByteSpan & initiatorRandom, - const ByteSpan * ipkList, size_t ipkListEntries) const -{ - uint8_t localDestID[kSHA256_Hash_Length] = { 0 }; - MutableByteSpan localDestIDSpan(localDestID); - VerifyOrReturnError(IsInitialized(), CHIP_ERROR_INCORRECT_STATE); - for (size_t ipkIdx = 0; ipkIdx < ipkListEntries; ++ipkIdx) - { - if (GenerateDestinationID(ipkList[ipkIdx], initiatorRandom, mOperationalId.GetNodeId(), localDestIDSpan) == CHIP_NO_ERROR && - targetDestinationId.data_equal(localDestIDSpan)) - { - return CHIP_NO_ERROR; - } - } - return CHIP_ERROR_CERT_NOT_TRUSTED; -} - FabricTable::~FabricTable() { FabricTableDelegate * delegate = mDelegate; @@ -638,20 +571,6 @@ CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric) return CHIP_NO_ERROR; } -FabricIndex FabricTable::FindDestinationIDCandidate(const ByteSpan & destinationId, const ByteSpan & initiatorRandom, - const ByteSpan * ipkList, size_t ipkListEntries) -{ - for (auto & fabric : *this) - { - if (fabric.MatchDestinationID(destinationId, initiatorRandom, ipkList, ipkListEntries) == CHIP_NO_ERROR) - { - return fabric.GetFabricIndex(); - } - } - - return kUndefinedFabricIndex; -} - CHIP_ERROR FabricTable::AddNewFabric(FabricInfo & newFabric, FabricIndex * outputIndex) { VerifyOrReturnError(outputIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT); diff --git a/src/credentials/FabricTable.h b/src/credentials/FabricTable.h index c87042ce1c5a41..bae1e864bb5bfc 100644 --- a/src/credentials/FabricTable.h +++ b/src/credentials/FabricTable.h @@ -110,7 +110,7 @@ class DLL_EXPORT FabricInfo void SetVendorId(uint16_t vendorId) { mVendorId = vendorId; } - Crypto::P256Keypair * GetOperationalKey() + Crypto::P256Keypair * GetOperationalKey() const { if (mOperationalKey == nullptr) { @@ -140,12 +140,6 @@ class DLL_EXPORT FabricInfo bool IsInitialized() const { return IsOperationalNodeId(mOperationalId.GetNodeId()); } - CHIP_ERROR GenerateDestinationID(const ByteSpan & ipk, const ByteSpan & random, NodeId destNodeId, - MutableByteSpan & destinationId) const; - - CHIP_ERROR MatchDestinationID(const ByteSpan & destinationId, const ByteSpan & initiatorRandom, const ByteSpan * ipkList, - size_t ipkListEntries) const; - // 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. @@ -231,9 +225,9 @@ class DLL_EXPORT FabricInfo char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' }; #ifdef ENABLE_HSM_CASE_OPS_KEY - Crypto::P256KeypairHSM * mOperationalKey = nullptr; + mutable Crypto::P256KeypairHSM * mOperationalKey = nullptr; #else - Crypto::P256Keypair * mOperationalKey = nullptr; + mutable Crypto::P256Keypair * mOperationalKey = nullptr; #endif MutableByteSpan mRootCert; diff --git a/src/credentials/GroupDataProvider.h b/src/credentials/GroupDataProvider.h index f5fa345213f1b2..c9f0b64444edff 100644 --- a/src/credentials/GroupDataProvider.h +++ b/src/credentials/GroupDataProvider.h @@ -31,7 +31,8 @@ namespace Credentials { class GroupDataProvider { public: - using SecurityPolicy = app::Clusters::GroupKeyManagement::GroupKeySecurityPolicy; + using SecurityPolicy = app::Clusters::GroupKeyManagement::GroupKeySecurityPolicy; + static constexpr KeysetId kIdentityProtectionKeySetId = 0; struct GroupInfo { @@ -126,6 +127,12 @@ class GroupDataProvider // Actual key bits. Depending on context, it may be a raw epoch key (as seen within `SetKeySet` calls) // or it may be the derived operational group key (as seen in any other usage). uint8_t key[kLengthBytes]; + + void Clear() + { + start_time = 0; + Crypto::ClearSecretData(&key[0], sizeof(key)); + } }; // A operational group key set, usable by many GroupState mappings @@ -151,6 +158,14 @@ class GroupDataProvider VerifyOrReturnError(this->policy == other.policy && this->num_keys_used == other.num_keys_used, false); return !memcmp(this->epoch_keys, other.epoch_keys, this->num_keys_used * sizeof(EpochKey)); } + + void ClearKeys() + { + for (size_t key_idx = 0; key_idx < kEpochKeysMax; ++key_idx) + { + epoch_keys[key_idx].Clear(); + } + } }; /** @@ -294,10 +309,26 @@ class GroupDataProvider virtual CHIP_ERROR SetKeySet(FabricIndex fabric_index, const ByteSpan & compressed_fabric_id, const KeySet & keys) = 0; virtual CHIP_ERROR GetKeySet(FabricIndex fabric_index, KeysetId keyset_id, KeySet & keys) = 0; virtual CHIP_ERROR RemoveKeySet(FabricIndex fabric_index, KeysetId keyset_id) = 0; + + /** + * @brief Obtain the actual operational Identity Protection Key (IPK) keyset for a given + * fabric. These keys are used by the CASE protocol, and do not participate in + * any direct traffic encryption. Since the identity protection operational keyset + * is used in multiple key derivations and procedures, it cannot be hidden behind a + * SymmetricKeyContext, and must be obtainable by value. + * + * @param fabric_index - Fabric index for which to get the IPK operational keyset + * @param out_keyset - Reference to a KeySet where the IPK keys will be stored on success + * @return CHIP_NO_ERROR on success, CHIP_ERROR_NOT_FOUND if the IPK keyset is somehow unavailable + * or another CHIP_ERROR value if an internal storage error occurs. + */ + virtual CHIP_ERROR GetIpkKeySet(FabricIndex fabric_index, KeySet & out_keyset) = 0; + /** * Creates an iterator that may be used to obtain the list of key sets associated with the given fabric. * In order to release the allocated memory, the Release() method must be called after the iteration is finished. * Modifying the key sets table during the iteration is currently not supported, and may yield unexpected behaviour. + * * @retval An instance of KeySetIterator on success * @retval nullptr if no iterator instances are available. */ @@ -334,6 +365,36 @@ class GroupDataProvider GroupListener * mListener = nullptr; }; +/** + * @brief Utility Set the IPK Epoch key on a GroupDataProvider assuming a single IPK + * + * This utility replaces having to call `GroupDataProvider::SetKeySet` for the simple situation of a + * single IPK for a fabric, if a single epoch key is used. Start time will be set to 0 ("was always valid") + * + * @param provider - pointer to GroupDataProvider on which to set the IPK + * @param fabric_index - fabric index within the GroupDataProvider for which to set the IPK + * @param ipk_epoch_span - Span containing the IPK epoch key + * @param compressed_fabric_id - Compressed fabric ID associated with the fabric, for key derivation + * @return CHIP_NO_ERROR on success, CHIP_ERROR_INVALID_ARGUMENT on any bad argument, other CHIP_ERROR values + * from implementation on other errors + */ +inline CHIP_ERROR SetSingleIpkEpochKey(GroupDataProvider * provider, FabricIndex fabric_index, const ByteSpan & ipk_epoch_span, + const ByteSpan & compressed_fabric_id) +{ + GroupDataProvider::KeySet ipkKeySet(GroupDataProvider::kIdentityProtectionKeySetId, + GroupDataProvider::SecurityPolicy::kTrustFirst, 1); + + VerifyOrReturnError(provider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(ipk_epoch_span.size() == sizeof(ipkKeySet.epoch_keys[0].key), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(compressed_fabric_id.size() == sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT); + + ipkKeySet.epoch_keys[0].start_time = 0; + memcpy(&ipkKeySet.epoch_keys[0].key, ipk_epoch_span.data(), ipk_epoch_span.size()); + + // Set a single IPK, validate key derivation follows spec + return provider->SetKeySet(fabric_index, compressed_fabric_id, ipkKeySet); +} + /** * Instance getter for the global GroupDataProvider. * diff --git a/src/credentials/GroupDataProviderImpl.cpp b/src/credentials/GroupDataProviderImpl.cpp index f9c03fd2e0dc8f..c3e2069e0e674d 100644 --- a/src/credentials/GroupDataProviderImpl.cpp +++ b/src/credentials/GroupDataProviderImpl.cpp @@ -1624,6 +1624,7 @@ CHIP_ERROR GroupDataProviderImpl::SetKeySet(chip::FabricIndex fabric_index, cons ReturnErrorOnFailure(Crypto::DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, key_span)); ReturnErrorOnFailure(Crypto::DeriveGroupSessionId(key_span, keyset.operational_keys[i].hash)); } + if (found) { // Update existing keyset info, keep next @@ -1650,11 +1651,11 @@ CHIP_ERROR GroupDataProviderImpl::GetKeySet(chip::FabricIndex fabric_index, uint VerifyOrReturnError(keyset.Find(mStorage, fabric, target_id), CHIP_ERROR_NOT_FOUND); // Target keyset found + out_keyset.ClearKeys(); out_keyset.keyset_id = keyset.keyset_id; out_keyset.policy = keyset.policy; out_keyset.num_keys_used = keyset.keys_count; // Epoch keys are not read back, only start times - memset(out_keyset.epoch_keys, 0x00, sizeof(out_keyset.epoch_keys)); out_keyset.epoch_keys[0].start_time = keyset.operational_keys[0].start_time; out_keyset.epoch_keys[1].start_time = keyset.operational_keys[1].start_time; out_keyset.epoch_keys[2].start_time = keyset.operational_keys[2].start_time; @@ -1724,12 +1725,12 @@ bool GroupDataProviderImpl::KeySetIteratorImpl::Next(KeySet & output) VerifyOrReturnError(CHIP_NO_ERROR == keyset.Load(mProvider.mStorage), false); mCount++; - mNextId = keyset.next; + mNextId = keyset.next; + output.ClearKeys(); output.keyset_id = keyset.keyset_id; output.policy = keyset.policy; output.num_keys_used = keyset.keys_count; // Epoch keys are not read back, only start times - memset(output.epoch_keys, 0x00, sizeof(output.epoch_keys)); output.epoch_keys[0].start_time = keyset.operational_keys[0].start_time; output.epoch_keys[1].start_time = keyset.operational_keys[1].start_time; output.epoch_keys[2].start_time = keyset.operational_keys[2].start_time; @@ -1804,7 +1805,8 @@ Crypto::SymmetricKeyContext * GroupDataProviderImpl::GetKeyContext(FabricIndex f for (uint16_t i = 0; i < fabric.map_count; ++i, mapping.id = mapping.next) { VerifyOrReturnError(CHIP_NO_ERROR == mapping.Load(mStorage), nullptr); - // GroupKeySetID of 0 is reserved for the Identity Protection Key (IPK) + // GroupKeySetID of 0 is reserved for the Identity Protection Key (IPK), + // it cannot be used for operational group communication. if (mapping.keyset_id > 0 && mapping.group_id == group_id) { // Group found, get the keyset @@ -1821,6 +1823,37 @@ Crypto::SymmetricKeyContext * GroupDataProviderImpl::GetKeyContext(FabricIndex f return nullptr; } +CHIP_ERROR GroupDataProviderImpl::GetIpkKeySet(FabricIndex fabric_index, KeySet & out_keyset) +{ + FabricData fabric(fabric_index); + VerifyOrReturnError(CHIP_NO_ERROR == fabric.Load(mStorage), CHIP_ERROR_NOT_FOUND); + + KeyMapData mapping(fabric.fabric_index, fabric.first_map); + + // Group found, get the keyset + KeySetData keyset; + VerifyOrReturnError(keyset.Find(mStorage, fabric, kIdentityProtectionKeySetId), CHIP_ERROR_NOT_FOUND); + + // If the keyset ID doesn't match, we have a ... problem. + VerifyOrReturnError(keyset.keyset_id == kIdentityProtectionKeySetId, CHIP_ERROR_INTERNAL); + + out_keyset.keyset_id = keyset.keyset_id; + out_keyset.num_keys_used = keyset.keys_count; + out_keyset.policy = keyset.policy; + + for (size_t key_idx = 0; key_idx < KeySet::kEpochKeysMax; ++key_idx) + { + out_keyset.epoch_keys[key_idx].Clear(); + if (key_idx < keyset.keys_count) + { + out_keyset.epoch_keys[key_idx].start_time = keyset.operational_keys[key_idx].start_time; + memcpy(&out_keyset.epoch_keys[key_idx].key[0], keyset.operational_keys[key_idx].value, EpochKey::kLengthBytes); + } + } + + return CHIP_NO_ERROR; +} + void GroupDataProviderImpl::GroupKeyContext::Release() { memset(mKeyValue, 0, sizeof(mKeyValue)); diff --git a/src/credentials/GroupDataProviderImpl.h b/src/credentials/GroupDataProviderImpl.h index e9717149367bdd..a67ce73ff7a940 100644 --- a/src/credentials/GroupDataProviderImpl.h +++ b/src/credentials/GroupDataProviderImpl.h @@ -83,6 +83,7 @@ class GroupDataProviderImpl : public GroupDataProvider CHIP_ERROR SetKeySet(FabricIndex fabric_index, const ByteSpan & compressed_fabric_id, const KeySet & keys) override; CHIP_ERROR GetKeySet(FabricIndex fabric_index, chip::KeysetId keyset_id, KeySet & keys) override; CHIP_ERROR RemoveKeySet(FabricIndex fabric_index, chip::KeysetId keyset_id) override; + CHIP_ERROR GetIpkKeySet(FabricIndex fabric_index, KeySet & out_keyset) override; KeySetIterator * IterateKeySets(FabricIndex fabric_index) override; // Fabrics diff --git a/src/credentials/tests/TestGroupDataProvider.cpp b/src/credentials/tests/TestGroupDataProvider.cpp index b4e5d1f1076096..a3b89aee141bd3 100644 --- a/src/credentials/tests/TestGroupDataProvider.cpp +++ b/src/credentials/tests/TestGroupDataProvider.cpp @@ -50,12 +50,32 @@ static const size_t kSize2 = strlen(kValue2) + 1; constexpr uint16_t kMaxGroupsPerFabric = 5; constexpr uint16_t kMaxGroupKeysPerFabric = 8; -constexpr chip::FabricIndex kFabric1 = 1; -constexpr chip::FabricIndex kFabric2 = 7; -static const uint8_t kFabricIdBuffer1[] = { 0x29, 0x06, 0xC9, 0x08, 0xD1, 0x15, 0xD3, 0x62 }; -static const uint8_t kFabricIdBuffer2[] = { 0x94, 0xb2, 0x68, 0x9a, 0x72, 0xb0, 0xc5, 0x1c }; -constexpr ByteSpan kCompressedFabricId1(kFabricIdBuffer1); -constexpr ByteSpan kCompressedFabricId2(kFabricIdBuffer2); +// If test cases covering more than 2 fabrics are added, update `ResetProvider` function. +constexpr chip::FabricIndex kFabric1 = 1; +constexpr chip::FabricIndex kFabric2 = 7; + +// Currently unused constants that are useful for context +#if 0 +static const uint8_t kExampleOperationalRootPublicKey[65] = { + 0x04, 0x4a, 0x9f, 0x42, 0xb1, 0xca, 0x48, 0x40, 0xd3, 0x72, 0x92, 0xbb, 0xc7, 0xf6, 0xa7, 0xe1, 0x1e, + 0x22, 0x20, 0x0c, 0x97, 0x6f, 0xc9, 0x00, 0xdb, 0xc9, 0x8a, 0x7a, 0x38, 0x3a, 0x64, 0x1c, 0xb8, 0x25, + 0x4a, 0x2e, 0x56, 0xd4, 0xe2, 0x95, 0xa8, 0x47, 0x94, 0x3b, 0x4e, 0x38, 0x97, 0xc4, 0xa7, 0x73, 0xe9, + 0x30, 0x27, 0x7b, 0x4d, 0x9f, 0xbe, 0xde, 0x8a, 0x05, 0x26, 0x86, 0xbf, 0xac, 0xfa, +}; +static const ByteSpan kExampleOperationalRootPublicKeySpan{ kExampleOperationalRootPublicKey }; + +constexpr chip::FabricId kFabricId1 = 0x2906C908D115D362; +constexpr chip::FabricId kFabricId2 = 0x5E1C0F1B2C813C7A; +#endif + +// kFabricId1/kCompressedFabricIdBuffer1 matches the Compressed Fabric Identifier +// example of spec section `4.3.2.2. Compressed Fabric Identifier`. It is based on +// the public key in `kExampleOperationalRootPublicKey`. +static const uint8_t kCompressedFabricIdBuffer1[] = { 0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30 }; +constexpr ByteSpan kCompressedFabricId1(kCompressedFabricIdBuffer1); + +static const uint8_t kCompressedFabricIdBuffer2[] = { 0x3f, 0xaa, 0xe2, 0x90, 0x93, 0xd5, 0xaf, 0x45 }; +constexpr ByteSpan kCompressedFabricId2(kCompressedFabricIdBuffer2); constexpr chip::GroupId kGroup1 = kMinFabricGroupId; constexpr chip::GroupId kGroup2 = 0x2222; @@ -137,6 +157,12 @@ class TestListener : public GroupDataProvider::GroupListener }; static TestListener sListener; +void ResetProvider(GroupDataProvider * provider) +{ + provider->RemoveFabric(kFabric1); + provider->RemoveFabric(kFabric2); +} + bool CompareKeySets(const KeySet & keyset1, const KeySet & keyset2) { VerifyOrReturnError(keyset1.policy == keyset2.policy, false); @@ -187,8 +213,7 @@ void TestGroupInfo(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupInfo group; @@ -307,8 +332,7 @@ void TestGroupInfoIterator(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupInfo group; @@ -366,8 +390,7 @@ void TestEndpoints(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupInfo group; @@ -461,8 +484,7 @@ void TestEndpointIterator(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupInfo group; @@ -536,8 +558,7 @@ void TestGroupKeys(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupKey pair; @@ -641,8 +662,7 @@ void TestGroupKeyIterator(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); GroupKey pair; @@ -708,8 +728,7 @@ void TestKeySets(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); KeySet keyset; @@ -797,17 +816,72 @@ void TestKeySets(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, CHIP_ERROR_NOT_FOUND == provider->GetKeySet(kFabric2, kKeysetId0, keyset)); } +void TestIpk(nlTestSuite * apSuite, void * apContext) +{ + GroupDataProvider * provider = GetGroupDataProvider(); + NL_TEST_ASSERT(apSuite, provider); + + // Reset test + ResetProvider(provider); + + // Make sure IPK set is not found on a fresh provider + KeySet ipkOperationalKeySet; + NL_TEST_ASSERT(apSuite, CHIP_ERROR_NOT_FOUND == provider->GetIpkKeySet(kFabric1, ipkOperationalKeySet)); + + // Add a non-IPK key, make sure the IPK set is not found + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetKeySet(kFabric1, kCompressedFabricId1, kKeySet3)); + NL_TEST_ASSERT(apSuite, CHIP_ERROR_NOT_FOUND == provider->GetIpkKeySet(kFabric1, ipkOperationalKeySet)); + + const uint8_t kIpkEpochKeyFromSpec[] = { 0x23, 0x5b, 0xf7, 0xe6, 0x28, 0x23, 0xd3, 0x58, + 0xdc, 0xa4, 0xba, 0x50, 0xb1, 0x53, 0x5f, 0x4b }; + + KeySet fabric1KeySet0(kKeysetId0, SecurityPolicy::kTrustFirst, 1); + fabric1KeySet0.epoch_keys[0].start_time = 1234; + memcpy(&fabric1KeySet0.epoch_keys[0].key, &kIpkEpochKeyFromSpec[0], sizeof(kIpkEpochKeyFromSpec)); + + // Set a single IPK, validate key derivation follows spec + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetKeySet(kFabric1, kCompressedFabricId1, fabric1KeySet0)); + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->GetIpkKeySet(kFabric1, ipkOperationalKeySet)); + + // Make sure the derived key matches spec test vector + const uint8_t kExpectedIpkFromSpec[] = { 0xa6, 0xf5, 0x30, 0x6b, 0xaf, 0x6d, 0x05, 0x0a, + 0xf2, 0x3b, 0xa4, 0xbd, 0x6b, 0x9d, 0xd9, 0x60 }; + + NL_TEST_ASSERT(apSuite, 0 == ipkOperationalKeySet.keyset_id); + NL_TEST_ASSERT(apSuite, 1 == ipkOperationalKeySet.num_keys_used); + NL_TEST_ASSERT(apSuite, SecurityPolicy::kTrustFirst == ipkOperationalKeySet.policy); + NL_TEST_ASSERT(apSuite, 1234 == ipkOperationalKeySet.epoch_keys[0].start_time); + NL_TEST_ASSERT(apSuite, + 0 == memcmp(ipkOperationalKeySet.epoch_keys[0].key, kExpectedIpkFromSpec, sizeof(kExpectedIpkFromSpec))); + + // Remove IPK, verify removal + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->RemoveKeySet(kFabric1, kKeysetId0)); + NL_TEST_ASSERT(apSuite, CHIP_ERROR_NOT_FOUND == provider->GetIpkKeySet(kFabric1, ipkOperationalKeySet)); + + // Set a single IPK with the SetSingleIpkEpochKey helper, validate key derivation follows spec + NL_TEST_ASSERT( + apSuite, + CHIP_NO_ERROR == + chip::Credentials::SetSingleIpkEpochKey(provider, kFabric1, ByteSpan(kIpkEpochKeyFromSpec), kCompressedFabricId1)); + NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->GetIpkKeySet(kFabric1, ipkOperationalKeySet)); + + NL_TEST_ASSERT(apSuite, 0 == ipkOperationalKeySet.keyset_id); + NL_TEST_ASSERT(apSuite, 1 == ipkOperationalKeySet.num_keys_used); + NL_TEST_ASSERT(apSuite, SecurityPolicy::kTrustFirst == ipkOperationalKeySet.policy); + NL_TEST_ASSERT(apSuite, 0 == ipkOperationalKeySet.epoch_keys[0].start_time); // default time is zero for SetSingleIpkEpochKey + NL_TEST_ASSERT(apSuite, + 0 == memcmp(ipkOperationalKeySet.epoch_keys[0].key, kExpectedIpkFromSpec, sizeof(kExpectedIpkFromSpec))); +} + void TestKeySetIterator(nlTestSuite * apSuite, void * apContext) { GroupDataProvider * provider = GetGroupDataProvider(); NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); // Add data to iterate - NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetKeySet(kFabric1, kCompressedFabricId1, kKeySet1)); NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetKeySet(kFabric1, kCompressedFabricId1, kKeySet0)); NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetKeySet(kFabric1, kCompressedFabricId1, kKeySet2)); @@ -869,8 +943,7 @@ void TestPerFabricData(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); // Group Info GroupInfo group; @@ -998,8 +1071,7 @@ void TestGroupDecryption(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, provider); // Reset test - provider->RemoveFabric(kFabric1); - provider->RemoveFabric(kFabric2); + ResetProvider(provider); NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetGroupInfoAt(kFabric1, 0, kGroupInfo1_3)); NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == provider->SetGroupInfoAt(kFabric1, 1, kGroupInfo1_2)); @@ -1094,7 +1166,7 @@ void TestGroupDecryption(nlTestSuite * apSuite, void * apContext) NL_TEST_ASSERT(apSuite, expected.count(found) > 0); NL_TEST_ASSERT(apSuite, session.key != nullptr); - // Decrypt de ciphertext + // Decrypt the ciphertext NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == session.key->DecryptMessage(ciphertext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)), @@ -1183,6 +1255,7 @@ const nlTest sTests[] = { NL_TEST_DEF("TestStorageDelegate", chip::app::TestGrou NL_TEST_DEF("TestGroupKeyIterator", chip::app::TestGroups::TestGroupKeyIterator), NL_TEST_DEF("TestKeySets", chip::app::TestGroups::TestKeySets), NL_TEST_DEF("TestKeySetIterator", chip::app::TestGroups::TestKeySetIterator), + NL_TEST_DEF("TestIpk", chip::app::TestGroups::TestIpk), NL_TEST_DEF("TestPerFabricData", chip::app::TestGroups::TestPerFabricData), NL_TEST_DEF("TestGroupDecryption", chip::app::TestGroups::TestGroupDecryption), NL_TEST_SENTINEL() }; diff --git a/src/crypto/CHIPCryptoPAL.cpp b/src/crypto/CHIPCryptoPAL.cpp index 738614f52f3d77..8ff43ed6a902ed 100644 --- a/src/crypto/CHIPCryptoPAL.cpp +++ b/src/crypto/CHIPCryptoPAL.cpp @@ -752,19 +752,22 @@ CHIP_ERROR GenerateCompressedFabricId(const Crypto::P256PublicKey & rootPublicKe return CHIP_NO_ERROR; } -/* Operational Group Key Group, Security Salt: "GroupKey v1.0" */ -static const uint8_t kGroupSecuritySalt[] = { 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x20, 0x76, 0x31, 0x2e, 0x30 }; +/* Operational Group Key Group, Security Info: "GroupKey v1.0" */ +static const uint8_t kGroupSecurityInfo[] = { 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x20, 0x76, 0x31, 0x2e, 0x30 }; /* Group Key Derivation Function, Info: "GroupKeyHash" ” */ static const uint8_t kGroupKeyHashInfo[] = { 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4b, 0x65, 0x79, 0x48, 0x61, 0x73, 0x68 }; static const uint8_t kGroupKeyHashSalt[0] = {}; /* - OperationalGroupKey = Crypto_KDF ( - InputKey = Epoch Key, - Salt = [], - Info = Group Security Salt, - Length = CRYPTO_SYMMETRIC_KEY_LENGTH_BITS) + OperationalGroupKey = + Crypto_KDF + ( + InputKey = Epoch Key, + Salt = CompressedFabricIdentifier, + Info = "GroupKey v1.0", + Length = CRYPTO_SYMMETRIC_KEY_LENGTH_BITS + ) */ CHIP_ERROR DeriveGroupOperationalKey(const ByteSpan & epoch_key, const ByteSpan & compressed_fabric_id, MutableByteSpan & out_key) { @@ -773,7 +776,7 @@ CHIP_ERROR DeriveGroupOperationalKey(const ByteSpan & epoch_key, const ByteSpan Crypto::HKDF_sha crypto; return crypto.HKDF_SHA256(epoch_key.data(), epoch_key.size(), compressed_fabric_id.data(), compressed_fabric_id.size(), - kGroupSecuritySalt, sizeof(kGroupSecuritySalt), out_key.data(), + kGroupSecurityInfo, sizeof(kGroupSecurityInfo), out_key.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES); } diff --git a/src/crypto/CHIPCryptoPAL.h b/src/crypto/CHIPCryptoPAL.h index 056743b682fdaa..d4c15e6f640b6b 100644 --- a/src/crypto/CHIPCryptoPAL.h +++ b/src/crypto/CHIPCryptoPAL.h @@ -1409,7 +1409,7 @@ enum class MatterOid CHIP_ERROR ExtractDNAttributeFromX509Cert(MatterOid matterOid, const ByteSpan & certificate, uint16_t & id); /** - * @brief Opaque context used to protect the symmetric key. The key operations must + * @brief Opaque context used to protect a symmetric key. The key operations must * be performed without exposing the protected key value. */ class SymmetricKeyContext @@ -1417,6 +1417,9 @@ class SymmetricKeyContext public: /** * @brief Returns the symmetric key hash + * + * TODO: Replace GetKeyHash() with DeriveGroupSessionId(SymmetricKeyContext &, uint16_t & session_id) + * * @return Group Key Hash */ virtual uint16_t GetKeyHash() = 0; @@ -1470,7 +1473,7 @@ class SymmetricKeyContext const ByteSpan & mic) const = 0; /** - * @brief Release the dynamic memory used to allocate this instance of the SymmetricKeyContext + * @brief Release resources such as dynamic memory used to allocate this instance of the SymmetricKeyContext */ virtual void Release() = 0; }; diff --git a/src/crypto/tests/CHIPCryptoPALTest.cpp b/src/crypto/tests/CHIPCryptoPALTest.cpp index c7c557cd6061d4..011e00cea5fa19 100644 --- a/src/crypto/tests/CHIPCryptoPALTest.cpp +++ b/src/crypto/tests/CHIPCryptoPALTest.cpp @@ -2170,8 +2170,16 @@ const uint8_t kGroupOperationalKey1[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYT const uint8_t kGroupOperationalKey2[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0xaa, 0x97, 0x9a, 0x48, 0xbd, 0x8c, 0xdf, 0x29, 0x3a, 0x07, 0x09, 0xb9, 0xc1, 0xeb, 0x19, 0x30 }; -const uint16_t kGroupSessionId1 = 0x6c80; -const uint16_t kGroupSessionId2 = 0x0c48; + +static const uint8_t kCompressedFabricId2[] = { 0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30 }; +const uint8_t kEpochKeyBuffer3[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0x23, 0x5b, 0xf7, 0xe6, 0x28, 0x23, 0xd3, 0x58, + 0xdc, 0xa4, 0xba, 0x50, 0xb1, 0x53, 0x5f, 0x4b }; +const uint8_t kGroupOperationalKey3[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { 0xa6, 0xf5, 0x30, 0x6b, 0xaf, 0x6d, + 0x05, 0x0a, 0xf2, 0x3b, 0xa4, 0xbd, + 0x6b, 0x9d, 0xd9, 0x60 }; + +const uint16_t kGroupSessionId1 = 0x6c80; +const uint16_t kGroupSessionId2 = 0x0c48; static void TestGroup_OperationalKeyDerivation(nlTestSuite * inSuite, void * inContext) { @@ -2192,6 +2200,12 @@ static void TestGroup_OperationalKeyDerivation(nlTestSuite * inSuite, void * inC epoch_key = ByteSpan(kEpochKeyBuffer2, sizeof(kEpochKeyBuffer2)); NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, operational_key)); NL_TEST_ASSERT(inSuite, 0 == memcmp(operational_key.data(), kGroupOperationalKey2, sizeof(kGroupOperationalKey2))); + + // Epoch Key 3 (example from spec) + epoch_key = ByteSpan(kEpochKeyBuffer3, sizeof(kEpochKeyBuffer3)); + compressed_fabric_id = ByteSpan(kCompressedFabricId2); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == DeriveGroupOperationalKey(epoch_key, compressed_fabric_id, operational_key)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(operational_key.data(), kGroupOperationalKey3, sizeof(kGroupOperationalKey3))); } static void TestGroup_SessionIdDerivation(nlTestSuite * inSuite, void * inContext) diff --git a/src/darwin/Framework/CHIP/CHIPDeviceController.mm b/src/darwin/Framework/CHIP/CHIPDeviceController.mm index 84c541ecc52319..dda9a7bfb83cd0 100644 --- a/src/darwin/Framework/CHIP/CHIPDeviceController.mm +++ b/src/darwin/Framework/CHIP/CHIPDeviceController.mm @@ -38,10 +38,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -51,6 +53,8 @@ static NSString * const kErrorMemoryInit = @"Init Memory failure"; static NSString * const kErrorKVSInit = @"Init Key Value Store failure"; static NSString * const kErrorCommissionerInit = @"Init failure while initializing a commissioner"; +static NSString * const kErrorGroupProviderInit = @"Init failure while initializing group data provider"; +static NSString * const kErrorIPKInit = @"Init failure while initializing IPK"; static NSString * const kErrorOperationalCredentialsInit = @"Init failure while creating operational credentials delegate"; static NSString * const kErrorPairingInit = @"Init failure while creating a pairing delegate"; static NSString * const kErrorPersistentStorageInit = @"Init failure while creating a persistent storage delegate"; @@ -68,6 +72,11 @@ @interface CHIPDeviceController () @property (atomic, readonly) dispatch_queue_t chipWorkQueue; @property (readonly) chip::Controller::DeviceCommissioner * cppCommissioner; +// 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) CHIPDevicePairingDelegateBridge * pairingDelegateBridge; @property (readonly) CHIPPersistentStorageDelegateBridge * persistentStorageDelegateBridge; @property (readonly) CHIPOperationalCredentialsDelegate * operationalCredentialsDelegate; @@ -118,6 +127,23 @@ - (instancetype)init if ([self checkForInitError:(_operationalCredentialsDelegate != nullptr) logMsg:kErrorOperationalCredentialsInit]) { return nil; } + + _groupStorageDelegate = new chip::TestPersistentStorageDelegate(); + if ([self checkForInitError:(_groupStorageDelegate != nullptr) logMsg:kErrorGroupProviderInit]) { + return nil; + } + + // For now default args are fine, since we are just using this for the IPK. + _groupDataProvider = new chip::Credentials::GroupDataProviderImpl(); + if ([self checkForInitError:(_groupDataProvider != nullptr) logMsg:kErrorGroupProviderInit]) { + return nil; + } + + _groupDataProvider->SetStorageDelegate(_groupStorageDelegate); + errorCode = _groupDataProvider->Init(); + if ([self checkForInitError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorGroupProviderInit]) { + return nil; + } } return self; } @@ -132,6 +158,15 @@ - (BOOL)shutdown dispatch_async(_chipWorkQueue, ^{ if (self->_cppCommissioner) { CHIP_LOG_DEBUG("%@", kInfoStackShutdown); + chip::FabricIndex fabricIdx; + CHIP_ERROR err = self->_cppCommissioner->GetFabricIndex(&fabricIdx); + if (err == CHIP_NO_ERROR && self->_groupDataProvider) { + // Clear out out group keys for this fabric index, just in case + // we get a different fabric index assigned when we are started + // again, since _groupDataProvider lives across shutdown/startup + // cycles. + self->_groupDataProvider->RemoveGroupKeys(fabricIdx); + } self->_cppCommissioner->Shutdown(); delete self->_cppCommissioner; self->_cppCommissioner = nullptr; @@ -179,7 +214,7 @@ - (BOOL)startup:(_Nullable id)storageDelegate _keypairBridge.Init(nocSigner); nativeBridge.reset(new chip::Crypto::CHIPP256KeypairNativeBridge(_keypairBridge)); } - errorCode = _operationalCredentialsDelegate->init(_persistentStorageDelegateBridge, std::move(nativeBridge)); + errorCode = _operationalCredentialsDelegate->init(_persistentStorageDelegateBridge, std::move(nativeBridge), nil); if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorOperationalCredentialsInit]) { return; } @@ -188,7 +223,7 @@ - (BOOL)startup:(_Nullable id)storageDelegate [self _getControllerNodeId]; _cppCommissioner = new chip::Controller::DeviceCommissioner(); - if ([self checkForInitError:(_cppCommissioner != nullptr) logMsg:kErrorMemoryInit]) { + if ([self checkForStartError:(_cppCommissioner != nullptr) logMsg:kErrorCommissionerInit]) { return; } @@ -205,6 +240,7 @@ - (BOOL)startup:(_Nullable id)storageDelegate const chip::Credentials::AttestationTrustStore * testingRootStore = chip::Credentials::GetTestAttestationTrustStore(); chip::Credentials::SetDeviceAttestationVerifier(chip::Credentials::GetDefaultDACVerifier(testingRootStore)); + params.groupDataProvider = _groupDataProvider; params.fabricIndependentStorage = _persistentStorageDelegateBridge; commissionerParams.storageDelegate = _persistentStorageDelegateBridge; commissionerParams.pairingDelegate = _pairingDelegateBridge; @@ -239,7 +275,7 @@ - (BOOL)startup:(_Nullable id)storageDelegate if (_kvsPath != nullptr) { errorCode = chip::DeviceLayer::PersistedStorage::KeyValueStoreMgrImpl().Init(_kvsPath); - if ([self checkForInitError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorKVSInit]) { + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorKVSInit]) { return; } } @@ -256,6 +292,25 @@ - (BOOL)startup:(_Nullable id)storageDelegate return; } + chip::FabricIndex fabricIdx = 0; + errorCode = _cppCommissioner->GetFabricIndex(&fabricIdx); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorIPKInit]) { + return; + } + + uint8_t compressedIdBuffer[sizeof(uint64_t)]; + chip::MutableByteSpan compressedId(compressedIdBuffer); + errorCode = _cppCommissioner->GetFabricInfo()->GetCompressedId(compressedId); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorIPKInit]) { + return; + } + + errorCode = chip::Credentials::SetSingleIpkEpochKey( + _groupDataProvider, fabricIdx, _operationalCredentialsDelegate->GetIPK(), compressedId); + if ([self checkForStartError:(CHIP_NO_ERROR == errorCode) logMsg:kErrorIPKInit]) { + return; + } + commissionerInitialized = YES; }); @@ -613,6 +668,17 @@ - (BOOL)checkForInitError:(BOOL)condition logMsg:(NSString *)logMsg _persistentStorageDelegateBridge = NULL; } + if (_groupDataProvider) { + _groupDataProvider->Finish(); + delete _groupDataProvider; + _groupDataProvider = NULL; + } + + if (_groupStorageDelegate) { + delete _groupStorageDelegate; + _groupStorageDelegate = NULL; + } + return YES; } @@ -625,6 +691,7 @@ - (BOOL)checkForStartError:(BOOL)condition logMsg:(NSString *)logMsg CHIP_LOG_ERROR("Error: %@", logMsg); if (_cppCommissioner) { + _cppCommissioner->Shutdown(); delete _cppCommissioner; _cppCommissioner = NULL; } diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h index 37b8b0e580a07a..5846d0c5a2f24f 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h @@ -25,6 +25,7 @@ #import "CHIPPersistentStorageDelegateBridge.h" #include +#include #include #include @@ -36,7 +37,14 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC ~CHIPOperationalCredentialsDelegate() {} - CHIP_ERROR init(CHIPPersistentStorageDelegateBridge * storage, ChipP256KeypairPtr nocSigner); + /** + * If nocSigner is not provided (is null), a keypair will be loaded from the + * keychain, or generated if nothing is present in the keychain. + * + * If ipk is not provided (is nil), an IPK will be loaded from the keychain, + * or generated if nothing is present in the keychain. + */ + CHIP_ERROR init(CHIPPersistentStorageDelegateBridge * storage, ChipP256KeypairPtr nocSigner, NSData * _Nullable ipk); CHIP_ERROR GenerateNOCChain(const chip::ByteSpan & csrElements, const chip::ByteSpan & attestationSignature, const chip::ByteSpan & DAC, const chip::ByteSpan & PAI, const chip::ByteSpan & PAA, @@ -57,10 +65,16 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC const chip::Crypto::P256PublicKey & pubkey, chip::MutableByteSpan & rcac, chip::MutableByteSpan & icac, chip::MutableByteSpan & noc); + const chip::Crypto::AesCcm128KeySpan GetIPK() { return chip::Crypto::AesCcm128KeySpan(mIPK); } + private: - CHIP_ERROR GenerateKeys(); - CHIP_ERROR LoadKeysFromKeyChain(); - CHIP_ERROR DeleteKeys(); + CHIP_ERROR GenerateRootCertKeys(); + CHIP_ERROR LoadRootCertKeysFromKeyChain(); + CHIP_ERROR DeleteRootCertKeysFromKeychain(); + + CHIP_ERROR GenerateIPK(); + CHIP_ERROR LoadIPKFromKeyChain(); + CHIP_ERROR DeleteIPKFromKeyChain(); CHIP_ERROR SetIssuerID(CHIPPersistentStorageDelegateBridge * storage); @@ -69,8 +83,11 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC ChipP256KeypairPtr mIssuerKey; uint64_t mIssuerId = 1234; + uint8_t mIPK[chip::Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES]; + const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60; const NSString * kCHIPCAKeyChainLabel = @"matter.nodeopcerts.CA:0"; + const NSString * kCHIPIPKKeyChainLabel = @"matter.nodeopcerts.IPK:0"; CHIPPersistentStorageDelegateBridge * mStorage; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm index 58edf9a095b511..04a98ef2ea9b85 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm @@ -46,38 +46,70 @@ static BOOL isRunningTests(void) return (environment[@"XCTestConfigurationFilePath"] != nil); } -CHIP_ERROR CHIPOperationalCredentialsDelegate::init(CHIPPersistentStorageDelegateBridge * storage, ChipP256KeypairPtr nocSigner) +CHIP_ERROR CHIPOperationalCredentialsDelegate::init( + CHIPPersistentStorageDelegateBridge * storage, ChipP256KeypairPtr nocSigner, NSData * _Nullable ipk) { if (storage == nil) { return CHIP_ERROR_INVALID_ARGUMENT; } - CHIP_ERROR err = CHIP_NO_ERROR; mStorage = storage; if (!nocSigner) { CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate: No NOC Signer provided, using self managed keys"); mIssuerKey.reset(new chip::Crypto::P256Keypair()); - err = LoadKeysFromKeyChain(); + CHIP_ERROR err = LoadRootCertKeysFromKeyChain(); if (err != CHIP_NO_ERROR) { - // Generate keys if keys could not be loaded - err = GenerateKeys(); + // Generate keys if keys could not be loaded. Delete keys first, in + // case we have already-stored data that could not be loaded as + // keys; otherwise key generation will fail due to not being able to + // store in the keychain. + DeleteRootCertKeysFromKeychain(); + + err = GenerateRootCertKeys(); + } + + if (err != CHIP_NO_ERROR) { + CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate::init failed to set up CA keypair: %s", err.AsString()); + return err; } } else { mIssuerKey = std::move(nocSigner); } - if (err == CHIP_NO_ERROR) { - // If keys were loaded, or generated, let's get the certificate issuer ID + if (ipk) { + if ([ipk length] != sizeof(mIPK)) { + CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate::init provided IPK is wrong size"); + return CHIP_ERROR_INVALID_ARGUMENT; + } + memcpy(mIPK, [ipk bytes], [ipk length]); + } else { + CHIP_ERROR err = LoadIPKFromKeyChain(); + + if (err != CHIP_NO_ERROR) { + // Generate an IPK if an IPK could not be loaded. Delete the existing + // IPK first, in case we have already-stored data that could not be + // loaded as an IPK; otherwise IPK generation will fail due to not being + // able to store in the keychain. + DeleteIPKFromKeyChain(); + + err = GenerateIPK(); + } - // TODO - enable generating a random issuer ID and saving it in persistent storage - // err = SetIssuerID(storage); + if (err != CHIP_NO_ERROR) { + CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate::init failed to set up IPK: %s", err.AsString()); + return err; + } } - CHIP_LOG_ERROR("CHIPOperationalCredentialsDelegate::init returning %s", chip::ErrorStr(err)); - return err; + // If keys were loaded, or generated, let's get the certificate issuer ID + + // TODO - enable generating a random issuer ID and saving it in persistent storage + // err = SetIssuerID(storage); + + return CHIP_NO_ERROR; } CHIP_ERROR CHIPOperationalCredentialsDelegate::SetIssuerID(CHIPPersistentStorageDelegateBridge * storage) @@ -100,7 +132,7 @@ static BOOL isRunningTests(void) return CHIP_NO_ERROR; } -CHIP_ERROR CHIPOperationalCredentialsDelegate::LoadKeysFromKeyChain() +CHIP_ERROR CHIPOperationalCredentialsDelegate::LoadRootCertKeysFromKeyChain() { const NSDictionary * query = @{ (id) kSecClass : (id) kSecClassGenericPassword, @@ -134,7 +166,36 @@ static BOOL isRunningTests(void) return mIssuerKey->Deserialize(serialized); } -CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateKeys() +CHIP_ERROR CHIPOperationalCredentialsDelegate::LoadIPKFromKeyChain() +{ + const NSDictionary * query = @{ + (id) kSecClass : (id) kSecClassGenericPassword, + (id) kSecAttrService : kCHIPIPKKeyChainLabel, + (id) kSecAttrSynchronizable : @YES, + (id) kSecReturnData : @YES, + }; + + CFDataRef keyDataRef; + OSStatus status = SecItemCopyMatching((CFDictionaryRef) query, (CFTypeRef *) &keyDataRef); + if (status == errSecItemNotFound || keyDataRef == nil) { + CHIP_LOG_ERROR("Did not find IPK in the keychain"); + return CHIP_ERROR_KEY_NOT_FOUND; + } + + CHIP_LOG_ERROR("Found an existing IPK in the keychain"); + NSData * keyData = CFBridgingRelease(keyDataRef); + + NSData * ipkData = [[NSData alloc] initWithBase64EncodedData:keyData options:0]; + if ([ipkData length] != sizeof(mIPK)) { + NSLog(@"IPK length %zu does not match expected length %zu", [ipkData length], sizeof(mIPK)); + return CHIP_ERROR_INTERNAL; + } + + memcpy(mIPK, [ipkData bytes], [ipkData length]); + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateRootCertKeys() { CHIP_LOG_ERROR("Generating self managed keys for the CA"); CHIP_ERROR errorCode = mIssuerKey->Initialize(); @@ -144,6 +205,9 @@ static BOOL isRunningTests(void) chip::Crypto::P256SerializedKeypair serializedKey; errorCode = mIssuerKey->Serialize(serializedKey); + if (errorCode != CHIP_NO_ERROR) { + return errorCode; + } NSData * keypairData = [NSData dataWithBytes:serializedKey.Bytes() length:serializedKey.Length()]; @@ -166,7 +230,34 @@ static BOOL isRunningTests(void) return CHIP_NO_ERROR; } -CHIP_ERROR CHIPOperationalCredentialsDelegate::DeleteKeys() +CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateIPK() +{ + CHIP_ERROR errorCode = DRBG_get_bytes(mIPK, sizeof(mIPK)); + if (errorCode != CHIP_NO_ERROR) { + return errorCode; + } + + NSData * ipkAdata = [NSData dataWithBytes:mIPK length:sizeof(mIPK)]; + + const NSDictionary * addParams = @{ + (id) kSecClass : (id) kSecClassGenericPassword, + (id) kSecAttrService : kCHIPIPKKeyChainLabel, + (id) kSecAttrSynchronizable : @YES, + (id) kSecValueData : [ipkAdata base64EncodedDataWithOptions:0], + }; + + OSStatus status = SecItemAdd((__bridge CFDictionaryRef) addParams, NULL); + // TODO: Enable SecItemAdd for Darwin unit tests + if (status != errSecSuccess && !isRunningTests()) { + NSLog(@"Failed in storing IPK : %d", status); + return CHIP_ERROR_INTERNAL; + } + + NSLog(@"Stored IPK"); + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::DeleteRootCertKeysFromKeychain() { CHIP_LOG_ERROR("Deleting self managed CA keys"); OSStatus status = noErr; @@ -184,6 +275,26 @@ static BOOL isRunningTests(void) } NSLog(@"Deleted the key"); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPOperationalCredentialsDelegate::DeleteIPKFromKeyChain() +{ + const NSDictionary * deleteParams = @{ + (id) kSecClass : (id) kSecClassGenericPassword, + (id) kSecAttrService : kCHIPIPKKeyChainLabel, + (id) kSecAttrSynchronizable : @YES, + }; + + OSStatus status = SecItemDelete((__bridge CFDictionaryRef) deleteParams); + if (status != errSecSuccess) { + NSLog(@"Failed in deleting IPK : %d", status); + return CHIP_ERROR_INTERNAL; + } + + NSLog(@"Deleted the IPK"); + return CHIP_NO_ERROR; } @@ -285,7 +396,7 @@ static BOOL isRunningTests(void) ReturnErrorOnFailure(GenerateNOCChainAfterValidation(assignedId, mNextFabricId, chip::kUndefinedCATs, pubkey, rcac, icac, noc)); - onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, noc, icac, rcac, Optional(), Optional()); + onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, noc, icac, rcac, MakeOptional(GetIPK()), Optional()); return CHIP_NO_ERROR; } diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 1e9c53c4289e7a..5b480bc72f5a05 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1511,14 +1511,18 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; /** * @def CHIP_CONFIG_MAX_GROUPS_PER_FABRIC * - * @brief Defines the number of groups supported per fabric, see Group Key Management Cluster in specification. + * @brief Defines the number of groups key sets supported per fabric, see Group Key Management Cluster in specification. * - * Binds to number of GroupState entries to support per fabric + * Binds to number of KeySet entries to support per fabric (Need at least 1 for Identity Protection Key) */ #ifndef CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC #define CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC 2 #endif +#if CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC < 1 +#error "Please ensure CHIP_CONFIG_MAX_GROUP_KEYS_PER_FABRIC > 0 to support at least the IPK." +#endif + /** * @def CHIP_CONFIG_MAX_GROUP_ENDPOINTS_PER_FABRIC * diff --git a/src/lib/support/TestGroupData.h b/src/lib/support/TestGroupData.h index dc611b4697609b..13c49ee9c62678 100644 --- a/src/lib/support/TestGroupData.h +++ b/src/lib/support/TestGroupData.h @@ -18,55 +18,44 @@ #pragma once -#include -#include - -namespace { - -constexpr uint16_t kMaxGroupsPerFabric = 5; -constexpr uint16_t kMaxGroupKeysPerFabric = 8; - -static chip::TestPersistentStorageDelegate sDeviceStorage; -static chip::Credentials::GroupDataProviderImpl sGroupsProvider(kMaxGroupsPerFabric, kMaxGroupKeysPerFabric); - -static const chip::GroupId kGroup1 = 0x0101; -static const chip::GroupId kGroup2 = 0x0102; -static const chip::KeysetId kKeySet1 = 0x01a1; -static const chip::KeysetId kKeySet2 = 0x01a2; - -} // namespace +#include +#include namespace chip { namespace GroupTesting { -CHIP_ERROR InitProvider() +class DefaultIpkValue { - sGroupsProvider.SetStorageDelegate(&sDeviceStorage); - ReturnErrorOnFailure(sGroupsProvider.Init()); - chip::Credentials::SetGroupDataProvider(&sGroupsProvider); - return CHIP_NO_ERROR; -} - -CHIP_ERROR InitProvider(chip::PersistentStorageDelegate & storageDelegate) +public: + DefaultIpkValue() {} + + static ByteSpan GetDefaultIpk() + { + static const uint8_t mDefaultIpk[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { + 't', 'e', 'm', 'p', 'o', 'r', 'a', 'r', 'y', ' ', 'i', 'p', 'k', ' ', '0', '1' + }; + return ByteSpan(mDefaultIpk); + } +}; + +inline CHIP_ERROR InitData(chip::Credentials::GroupDataProvider * provider, chip::FabricIndex fabric_index, + const ByteSpan & compressed_fabric_id) { - sGroupsProvider.SetStorageDelegate(&storageDelegate); - ReturnErrorOnFailure(sGroupsProvider.Init()); - chip::Credentials::SetGroupDataProvider(&sGroupsProvider); - return CHIP_NO_ERROR; -} + static const chip::GroupId kGroup1 = 0x0101; + static const chip::GroupId kGroup2 = 0x0102; + static const chip::KeysetId kKeySet1 = 0x01a1; + static const chip::KeysetId kKeySet2 = 0x01a2; -CHIP_ERROR InitData(chip::FabricIndex fabric_index, const ByteSpan & compressed_fabric_id) -{ // Groups const chip::Credentials::GroupDataProvider::GroupInfo group1(kGroup1, "Group #1"); - ReturnErrorOnFailure(sGroupsProvider.SetGroupInfo(fabric_index, group1)); - ReturnErrorOnFailure(sGroupsProvider.AddEndpoint(fabric_index, group1.group_id, 1)); + ReturnErrorOnFailure(provider->SetGroupInfo(fabric_index, group1)); + ReturnErrorOnFailure(provider->AddEndpoint(fabric_index, group1.group_id, 1)); const chip::Credentials::GroupDataProvider::GroupInfo group2(kGroup2, "Group #2"); - ReturnErrorOnFailure(sGroupsProvider.SetGroupInfo(fabric_index, group2)); - ReturnErrorOnFailure(sGroupsProvider.AddEndpoint(fabric_index, group2.group_id, 0)); + ReturnErrorOnFailure(provider->SetGroupInfo(fabric_index, group2)); + ReturnErrorOnFailure(provider->AddEndpoint(fabric_index, group2.group_id, 0)); // Key Sets @@ -78,7 +67,7 @@ CHIP_ERROR InitData(chip::FabricIndex fabric_index, const ByteSpan & compressed_ { 1110002, { 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf } }, }; memcpy(keyset1.epoch_keys, epoch_keys1, sizeof(epoch_keys1)); - CHIP_ERROR err = sGroupsProvider.SetKeySet(fabric_index, compressed_fabric_id, keyset1); + CHIP_ERROR err = provider->SetKeySet(fabric_index, compressed_fabric_id, keyset1); ReturnErrorOnFailure(err); chip::Credentials::GroupDataProvider::KeySet keyset2(kKeySet2, @@ -89,11 +78,11 @@ CHIP_ERROR InitData(chip::FabricIndex fabric_index, const ByteSpan & compressed_ { 2220002, { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff } }, }; memcpy(keyset2.epoch_keys, epoch_keys2, sizeof(epoch_keys2)); - err = sGroupsProvider.SetKeySet(fabric_index, compressed_fabric_id, keyset2); + err = provider->SetKeySet(fabric_index, compressed_fabric_id, keyset2); ReturnErrorOnFailure(err); - sGroupsProvider.SetGroupKeyAt(fabric_index, 0, chip::Credentials::GroupDataProvider::GroupKey(kGroup1, kKeySet1)); - sGroupsProvider.SetGroupKeyAt(fabric_index, 1, chip::Credentials::GroupDataProvider::GroupKey(kGroup2, kKeySet2)); + provider->SetGroupKeyAt(fabric_index, 0, chip::Credentials::GroupDataProvider::GroupKey(kGroup1, kKeySet1)); + provider->SetGroupKeyAt(fabric_index, 1, chip::Credentials::GroupDataProvider::GroupKey(kGroup2, kKeySet2)); return CHIP_NO_ERROR; } diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h index 9ae5cae339950e..5466daa1acb4e5 100644 --- a/src/lib/support/TestPersistentStorageDelegate.h +++ b/src/lib/support/TestPersistentStorageDelegate.h @@ -127,13 +127,19 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate * * @param key - Poison key to add to the set. */ - void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); } + virtual void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); } /** * @brief Clear all "poison keys" * */ - void ClearPoisonKeys() { mPoisonKeys.clear(); } + virtual void ClearPoisonKeys() { mPoisonKeys.clear(); } + + /** + * @brief Reset entire contents back to empty. This does NOT clear the "poison keys" + * + */ + virtual void ClearStorage() { mStorage.clear(); } protected: std::map> mStorage; diff --git a/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp index 3f42dca88d5b12..57d872b4cf9e3b 100644 --- a/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp +++ b/src/lib/support/tests/TestTestPersistentStorageDelegate.cpp @@ -184,7 +184,48 @@ void TestBasicApi(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, size == sizeof(buf)); } -const nlTest sTests[] = { NL_TEST_DEF("Test basic API", TestBasicApi), NL_TEST_SENTINEL() }; +// ClearStorage is not a PersistentStorageDelegate base class method, it only +// appears in the TestPersistentStorageDelegate. +void TestClearStorage(nlTestSuite * inSuite, void * inContext) +{ + TestPersistentStorageDelegate storage; + + uint8_t buf[16]; + uint16_t size = sizeof(buf); + + // Key not there + CHIP_ERROR err; + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, size == sizeof(buf)); + + // Add basic key, read it back + const char * kStringValue1 = "abcd"; + err = storage.SyncSetKeyValue("roboto", kStringValue1, static_cast(strlen(kStringValue1))); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, size == strlen(kStringValue1)); + NL_TEST_ASSERT(inSuite, 0 == memcmp(&buf[0], kStringValue1, strlen(kStringValue1))); + + // Clear storage, make sure it's gone + storage.ClearStorage(); + + memset(&buf[0], 0, sizeof(buf)); + size = sizeof(buf); + err = storage.SyncGetKeyValue("roboto", &buf[0], size); + NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + NL_TEST_ASSERT(inSuite, size == sizeof(buf)); +} + +const nlTest sTests[] = { NL_TEST_DEF("Test basic API", TestBasicApi), + NL_TEST_DEF("Test ClearStorage method of TestPersistentStorageDelegate", TestClearStorage), + NL_TEST_SENTINEL() }; } // namespace diff --git a/src/protocols/secure_channel/BUILD.gn b/src/protocols/secure_channel/BUILD.gn index ed9bf68b9dd28e..5037378f195e9f 100644 --- a/src/protocols/secure_channel/BUILD.gn +++ b/src/protocols/secure_channel/BUILD.gn @@ -4,6 +4,8 @@ static_library("secure_channel") { output_name = "libSecureChannel" sources = [ + "CASEDestinationId.cpp", + "CASEDestinationId.h", "CASEServer.cpp", "CASEServer.h", "CASESession.cpp", diff --git a/src/protocols/secure_channel/CASEDestinationId.cpp b/src/protocols/secure_channel/CASEDestinationId.cpp new file mode 100644 index 00000000000000..396177da9901be --- /dev/null +++ b/src/protocols/secure_channel/CASEDestinationId.cpp @@ -0,0 +1,58 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include + +#include "CASEDestinationId.h" + +namespace chip { + +using namespace chip::Crypto; + +CHIP_ERROR GenerateCaseDestinationId(const ByteSpan & ipk, const ByteSpan & initiatorRandom, const ByteSpan & rootPubKey, + FabricId fabricId, NodeId nodeId, MutableByteSpan & outDestinationId) +{ + VerifyOrReturnError(ipk.size() == kIPKSize, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(initiatorRandom.size() == kSigmaParamRandomNumberSize, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(rootPubKey.size() == kP256_PublicKey_Length, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(outDestinationId.size() >= kSHA256_Hash_Length, CHIP_ERROR_INVALID_ARGUMENT); + + constexpr size_t kDestinationMessageLen = + kSigmaParamRandomNumberSize + kP256_PublicKey_Length + sizeof(FabricId) + sizeof(NodeId); + uint8_t destinationMessage[kDestinationMessageLen]; + + Encoding::LittleEndian::BufferWriter bbuf(destinationMessage, sizeof(destinationMessage)); + bbuf.Put(initiatorRandom.data(), initiatorRandom.size()); + bbuf.Put(rootPubKey.data(), rootPubKey.size()); + bbuf.Put64(fabricId); + bbuf.Put64(nodeId); + + size_t written = 0; + VerifyOrReturnError(bbuf.Fit(written), CHIP_ERROR_BUFFER_TOO_SMALL); + + HMAC_sha hmac; + CHIP_ERROR err = + hmac.HMAC_SHA256(ipk.data(), ipk.size(), bbuf.Buffer(), written, outDestinationId.data(), outDestinationId.size()); + return err; +} + +} // namespace chip diff --git a/src/protocols/secure_channel/CASEDestinationId.h b/src/protocols/secure_channel/CASEDestinationId.h new file mode 100644 index 00000000000000..1be4a7cf88fe22 --- /dev/null +++ b/src/protocols/secure_channel/CASEDestinationId.h @@ -0,0 +1,37 @@ +/* + * + * Copyright (c) 2022 Project CHIP Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +namespace chip { + +constexpr uint16_t kSigmaParamRandomNumberSize = 32; +constexpr uint16_t kIPKSize = Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES; + +CHIP_ERROR GenerateCaseDestinationId(const ByteSpan & ipk, const ByteSpan & initiatorRandom, const ByteSpan & rootPubKey, + FabricId fabricId, NodeId nodeId, MutableByteSpan & outDestinationId); + +} // namespace chip diff --git a/src/protocols/secure_channel/CASEServer.cpp b/src/protocols/secure_channel/CASEServer.cpp index 139e0079fcee86..59dcffe9943f2c 100644 --- a/src/protocols/secure_channel/CASEServer.cpp +++ b/src/protocols/secure_channel/CASEServer.cpp @@ -33,19 +33,22 @@ CHIP_ERROR CASEServer::ListenForSessionEstablishment(Messaging::ExchangeManager #if CONFIG_NETWORK_LAYER_BLE Ble::BleLayer * bleLayer, #endif - SessionManager * sessionManager, FabricTable * fabrics) + SessionManager * sessionManager, FabricTable * fabrics, + Credentials::GroupDataProvider * responderGroupDataProvider) { VerifyOrReturnError(transportMgr != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(exchangeManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError(fabrics != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(sessionManager != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(responderGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); #if CONFIG_NETWORK_LAYER_BLE mBleLayer = bleLayer; #endif - mSessionManager = sessionManager; - mFabrics = fabrics; - mExchangeManager = exchangeManager; + mSessionManager = sessionManager; + mFabrics = fabrics; + mExchangeManager = exchangeManager; + mGroupDataProvider = responderGroupDataProvider; Cleanup(); return CHIP_NO_ERROR; @@ -74,6 +77,7 @@ CHIP_ERROR CASEServer::InitCASEHandshake(Messaging::ExchangeContext * ec) ReturnErrorOnFailure(mSessionIDAllocator.Allocate(mSessionKeyId)); // Setup CASE state machine using the credentials for the current fabric. + GetSession().SetGroupDataProvider(mGroupDataProvider); ReturnErrorOnFailure(GetSession().ListenForSessionEstablishment( mSessionKeyId, mFabrics, this, Optional::Value(GetLocalMRPConfig()))); diff --git a/src/protocols/secure_channel/CASEServer.h b/src/protocols/secure_channel/CASEServer.h index 0bb6e85f000cd1..f57ef94baaf87e 100644 --- a/src/protocols/secure_channel/CASEServer.h +++ b/src/protocols/secure_channel/CASEServer.h @@ -20,6 +20,7 @@ #if CONFIG_NETWORK_LAYER_BLE #include #endif +#include #include #include #include @@ -43,7 +44,8 @@ class CASEServer : public SessionEstablishmentDelegate, public Messaging::Exchan #if CONFIG_NETWORK_LAYER_BLE Ble::BleLayer * bleLayer, #endif - SessionManager * sessionManager, FabricTable * fabrics); + SessionManager * sessionManager, FabricTable * fabrics, + Credentials::GroupDataProvider * responderGroupDataProvider); //////////// SessionEstablishmentDelegate Implementation /////////////// void OnSessionEstablishmentError(CHIP_ERROR error) override; @@ -67,7 +69,8 @@ class CASEServer : public SessionEstablishmentDelegate, public Messaging::Exchan Ble::BleLayer * mBleLayer = nullptr; #endif - FabricTable * mFabrics = nullptr; + FabricTable * mFabrics = nullptr; + Credentials::GroupDataProvider * mGroupDataProvider = nullptr; SessionIDAllocator mSessionIDAllocator; CHIP_ERROR InitCASEHandshake(Messaging::ExchangeContext * ec); diff --git a/src/protocols/secure_channel/CASESession.cpp b/src/protocols/secure_channel/CASESession.cpp index abda82f306563f..a68e524ac2ded2 100644 --- a/src/protocols/secure_channel/CASESession.cpp +++ b/src/protocols/secure_channel/CASESession.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -89,10 +90,7 @@ using HKDF_sha_crypto = HKDF_sha; // The session establishment fails if the response is not received within timeout window. static constexpr ExchangeContext::Timeout kSigma_Response_Timeout = System::Clock::Seconds16(30); -CASESession::CASESession() : PairingSession(Transport::SecureSession::Type::kCASE) -{ - mTrustedRootId = CertificateKeyId(); -} +CASESession::CASESession() : PairingSession(Transport::SecureSession::Type::kCASE) {} CASESession::~CASESession() { @@ -109,6 +107,7 @@ void CASESession::Clear() PairingSession::Clear(); mState = kInitialized; + Crypto::ClearSecretData(&mIPK[0], sizeof(mIPK)); AbortExchange(); } @@ -151,12 +150,12 @@ CHIP_ERROR CASESession::ToCachable(CASESessionCachable & cachableSession) { cachableSession.mPeerCATs.values[i] = LittleEndian::HostSwap32(GetPeerCATs().values[i]); } - // TODO: Get the fabric index - cachableSession.mLocalFabricIndex = 0; + cachableSession.mLocalFabricIndex = (mFabricInfo != nullptr) ? mFabricInfo->GetFabricIndex() : kUndefinedFabricIndex; cachableSession.mSessionSetupTimeStamp = LittleEndian::HostSwap64(mSessionSetupTimeStamp); memcpy(cachableSession.mResumptionId, mResumptionId, sizeof(mResumptionId)); memcpy(cachableSession.mSharedSecret, mSharedSecret, mSharedSecret.Length()); + memcpy(cachableSession.mIPK, mIPK, sizeof(mIPK)); return CHIP_NO_ERROR; } @@ -176,14 +175,12 @@ CHIP_ERROR CASESession::FromCachable(const CASESessionCachable & cachableSession } SetPeerCATs(peerCATs); SetSessionTimeStamp(LittleEndian::HostSwap64(cachableSession.mSessionSetupTimeStamp)); - // TODO: Set the fabric index correctly mLocalFabricIndex = cachableSession.mLocalFabricIndex; memcpy(mResumptionId, cachableSession.mResumptionId, sizeof(mResumptionId)); - const ByteSpan * ipkListSpan = GetIPKList(); - VerifyOrReturnError(ipkListSpan->size() == sizeof(mIPK), CHIP_ERROR_INVALID_ARGUMENT); - memcpy(mIPK, ipkListSpan->data(), sizeof(mIPK)); + // TODO: Handle data dependency between IPK caching and the possible underlying changes of that IPK + memcpy(mIPK, cachableSession.mIPK, sizeof(mIPK)); mCASESessionEstablished = true; @@ -194,6 +191,8 @@ CHIP_ERROR CASESession::Init(uint16_t localSessionId, SessionEstablishmentDelega { VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + Clear(); ReturnErrorOnFailure(mCommissioningHash.Begin()); @@ -311,6 +310,38 @@ CHIP_ERROR CASESession::DeriveSecureSession(CryptoContext & session, CryptoConte return CHIP_NO_ERROR; } +CHIP_ERROR CASESession::RecoverInitiatorIpk() +{ + Credentials::GroupDataProvider::KeySet ipkKeySet; + FabricIndex fabricIndex = mFabricInfo->GetFabricIndex(); + + CHIP_ERROR err = mGroupDataProvider->GetIpkKeySet(fabricIndex, ipkKeySet); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(SecureChannel, "Failed to obtain IPK for initiating: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + else if ((ipkKeySet.num_keys_used == 0) || (ipkKeySet.num_keys_used > Credentials::GroupDataProvider::KeySet::kEpochKeysMax)) + { + ChipLogError(SecureChannel, "Found invalid IPK keyset for initiator."); + return CHIP_ERROR_INTERNAL; + } + + // For the generation of the Destination Identifier, + // the originator SHALL use the operational group key with the second oldest + // EpochStartTime, if one exists, otherwise it SHALL use the single operational + // group key available. The EpochStartTime are already ordered + size_t ipkIndex = (ipkKeySet.num_keys_used > 1) ? ((ipkKeySet.num_keys_used - 1) - 1) : 0; + memcpy(&mIPK[0], ipkKeySet.epoch_keys[ipkIndex].key, sizeof(mIPK)); + + ChipLogProgress(Support, "RecoverInitiatorIpk: GroupDataProvider %p, Got IPK for FabricIndex %u", mGroupDataProvider, + (unsigned) mFabricInfo->GetFabricIndex()); + ChipLogByteSpan(Support, ByteSpan(mIPK)); + + return CHIP_NO_ERROR; +} + CHIP_ERROR CASESession::SendSigma1() { MATTER_TRACE_EVENT_SCOPE("SendSigma1", "CASESession"); @@ -342,13 +373,22 @@ CHIP_ERROR CASESession::SendSigma1() ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(1), ByteSpan(mInitiatorRandom))); // Retrieve Session Identifier ReturnErrorOnFailure(tlvWriter.Put(TLV::ContextTag(2), GetLocalSessionId())); - // Generate a Destination Identifier + // Generate a Destination Identifier based on the node we are attempting to reach { - MutableByteSpan destinationIdSpan(destinationIdentifier); ReturnErrorCodeIf(mFabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE); - memcpy(mIPK, GetIPKList()->data(), sizeof(mIPK)); - ReturnErrorOnFailure( - mFabricInfo->GenerateDestinationID(ByteSpan(mIPK), ByteSpan(mInitiatorRandom), GetPeerNodeId(), destinationIdSpan)); + + // Obtain originator IPK matching the fabric where we are trying to open a session. mIPK + // will be properly set thereafter. + ReturnErrorOnFailure(RecoverInitiatorIpk()); + + FabricId fabricId = mFabricInfo->GetFabricId(); + uint8_t rootPubKeyBuf[Crypto::kP256_Point_Length]; + Credentials::P256PublicKeySpan rootPubKeySpan(&rootPubKeyBuf[0]); + ReturnErrorOnFailure(mFabricInfo->GetRootPubkey(rootPubKeySpan)); + + MutableByteSpan destinationIdSpan(destinationIdentifier); + ReturnErrorOnFailure(GenerateCaseDestinationId(ByteSpan(mIPK), ByteSpan(mInitiatorRandom), rootPubKeySpan, fabricId, + GetPeerNodeId(), destinationIdSpan)); } ReturnErrorOnFailure(tlvWriter.PutBytes(TLV::ContextTag(3), destinationIdentifier, sizeof(destinationIdentifier))); @@ -402,6 +442,58 @@ CHIP_ERROR CASESession::HandleSigma1_and_SendSigma2(System::PacketBufferHandle & return CHIP_NO_ERROR; } +CHIP_ERROR CASESession::FindLocalNodeFromDestionationId(const ByteSpan & destinationId, const ByteSpan & initiatorRandom) +{ + VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE); + + bool found = false; + for (const FabricInfo & fabricInfo : *mFabricsTable) + { + // Basic data for candidate fabric, used to compute candidate destination identifiers + FabricId fabricId = fabricInfo.GetFabricId(); + NodeId nodeId = fabricInfo.GetNodeId(); + uint8_t rootPubKeyBuf[Crypto::kP256_Point_Length]; + Credentials::P256PublicKeySpan rootPubKeySpan(&rootPubKeyBuf[0]); + ReturnErrorOnFailure(fabricInfo.GetRootPubkey(rootPubKeySpan)); + + // Get IPK operational group key set for current candidate fabric + GroupDataProvider::KeySet ipkKeySet; + CHIP_ERROR err = mGroupDataProvider->GetIpkKeySet(fabricInfo.GetFabricIndex(), ipkKeySet); + if ((err != CHIP_NO_ERROR) || + ((ipkKeySet.num_keys_used == 0) || (ipkKeySet.num_keys_used > Credentials::GroupDataProvider::KeySet::kEpochKeysMax))) + { + continue; + } + + // Try every IPK candidate we have for a match + for (size_t keyIdx = 0; keyIdx <= ipkKeySet.num_keys_used; ++keyIdx) + { + uint8_t candidateDestinationId[kSHA256_Hash_Length]; + MutableByteSpan candidateDestinationIdSpan(candidateDestinationId); + ByteSpan candidateIpkSpan(ipkKeySet.epoch_keys[keyIdx].key); + + err = GenerateCaseDestinationId(ByteSpan(candidateIpkSpan), ByteSpan(initiatorRandom), rootPubKeySpan, fabricId, nodeId, + candidateDestinationIdSpan); + if ((err == CHIP_NO_ERROR) && (candidateDestinationIdSpan.data_equal(destinationId))) + { + // Found a match, stop working, cache IPK, update local fabric context + found = true; + MutableByteSpan ipkSpan(mIPK); + CopySpanToMutableSpan(candidateIpkSpan, ipkSpan); + mFabricInfo = &fabricInfo; + break; + } + } + + if (found) + { + break; + } + } + + return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND; +} + CHIP_ERROR CASESession::HandleSigma1(System::PacketBufferHandle && msg) { MATTER_TRACE_EVENT_SCOPE("HandleSigma1", "CASESession"); @@ -419,9 +511,6 @@ CHIP_ERROR CASESession::HandleSigma1(System::PacketBufferHandle && msg) ByteSpan resume1MIC; ByteSpan initiatorPubKey; - const ByteSpan * ipkListSpan = GetIPKList(); - FabricIndex fabricIndex = kUndefinedFabricIndex; - SuccessOrExit(err = mCommissioningHash.AddData(ByteSpan{ msg->Start(), msg->DataLength() })); tlvReader.Init(std::move(msg)); @@ -447,15 +536,17 @@ CHIP_ERROR CASESession::HandleSigma1(System::PacketBufferHandle && msg) } } - memcpy(mIPK, ipkListSpan->data(), sizeof(mIPK)); - - VerifyOrExit(mFabricsTable != nullptr, err = CHIP_ERROR_INCORRECT_STATE); - fabricIndex = - mFabricsTable->FindDestinationIDCandidate(destinationIdentifier, initiatorRandom, ipkListSpan, GetIPKListEntries()); - VerifyOrExit(fabricIndex != kUndefinedFabricIndex, err = CHIP_ERROR_KEY_NOT_FOUND); - - mFabricInfo = mFabricsTable->FindFabricWithIndex(fabricIndex); - VerifyOrExit(mFabricInfo != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + err = FindLocalNodeFromDestionationId(destinationIdentifier, initiatorRandom); + if (err == CHIP_NO_ERROR) + { + ChipLogProgress(SecureChannel, "CASE matched destination ID: fabricIndex %u, NodeID 0x" ChipLogFormatX64, + static_cast(mFabricInfo->GetFabricIndex()), ChipLogValueX64(mFabricInfo->GetNodeId())); + } + else + { + ChipLogError(SecureChannel, "CASE failed to match destination ID with local fabrics"); + ChipLogByteSpan(SecureChannel, destinationIdentifier); + } // ParseSigma1 ensures that: // mRemotePubKey.Length() == initiatorPubKey.size() == kP256_PublicKey_Length. @@ -542,9 +633,6 @@ CHIP_ERROR CASESession::SendSigma2() ByteSpan nocCert; ReturnErrorOnFailure(mFabricInfo->GetNOCCert(nocCert)); - ReturnErrorOnFailure(mFabricInfo->GetTrustedRootId(mTrustedRootId)); - VerifyOrReturnError(!mTrustedRootId.empty(), CHIP_ERROR_INTERNAL); - // Fill in the random value uint8_t msg_rand[kSigmaParamRandomNumberSize]; ReturnErrorOnFailure(DRBG_get_bytes(&msg_rand[0], sizeof(msg_rand))); @@ -928,9 +1016,6 @@ CHIP_ERROR CASESession::SendSigma3() SuccessOrExit(err = mFabricInfo->GetICACert(icaCert)); SuccessOrExit(err = mFabricInfo->GetNOCCert(nocCert)); - SuccessOrExit(err = mFabricInfo->GetTrustedRootId(mTrustedRootId)); - VerifyOrExit(!mTrustedRootId.empty(), err = CHIP_ERROR_INTERNAL); - // Prepare Sigma3 TBS Data Blob msg_r3_signed_len = TLV::EstimateStructOverhead(icaCert.size(), nocCert.size(), kP256_PublicKey_Length, kP256_PublicKey_Length); @@ -1320,12 +1405,6 @@ CHIP_ERROR CASESession::ConstructTBSData(const ByteSpan & senderNOC, const ByteS return CHIP_NO_ERROR; } -CHIP_ERROR CASESession::RetrieveIPK(FabricId fabricId, MutableByteSpan & ipk) -{ - memset(ipk.data(), static_cast(fabricId), ipk.size()); - return CHIP_NO_ERROR; -} - CHIP_ERROR CASESession::GetHardcodedTime() { using namespace ASN1; diff --git a/src/protocols/secure_channel/CASESession.h b/src/protocols/secure_channel/CASESession.h index d5e8aed5edfde9..b945affa127087 100644 --- a/src/protocols/secure_channel/CASESession.h +++ b/src/protocols/secure_channel/CASESession.h @@ -31,10 +31,12 @@ #include #endif #include +#include #include #include #include #include +#include #include #include #include @@ -46,12 +48,6 @@ namespace chip { -constexpr uint16_t kSigmaParamRandomNumberSize = 32; -constexpr uint16_t kTrustedRootIdSize = Crypto::kSubjectKeyIdentifierLength; -constexpr uint16_t kMaxTrustedRootIds = 5; - -constexpr uint16_t kIPKSize = 16; - constexpr size_t kCASEResumptionIDSize = 16; #ifdef ENABLE_HSM_CASE_EPHEMERAL_KEY @@ -67,6 +63,7 @@ struct CASESessionCachable CATValues mPeerCATs; uint8_t mResumptionId[kCASEResumptionIDSize] = { 0 }; uint64_t mSessionSetupTimeStamp = 0; + uint8_t mIPK[kIPKSize] = { 0 }; }; class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public PairingSession @@ -110,6 +107,17 @@ class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public Pairin Messaging::ExchangeContext * exchangeCtxt, SessionEstablishmentDelegate * delegate, Optional mrpConfig = Optional::Missing()); + /** + * @brief Set the Group Data Provider which will be used to look-up IPKs + * + * The GroupDataProvider set MUST have key sets available through `GetIpkKeySet` method + * for the FabricIndex that is associated with the CASESession's FabricInfo. + * + * @param groupDataProvider - Pointer to the group data provider (if nullptr, will error at start of + * establishment, not here). + */ + void SetGroupDataProvider(Credentials::GroupDataProvider * groupDataProvider) { mGroupDataProvider = groupDataProvider; } + /** * Parse a sigma1 message. This function will return success only if the * message passes schema checks. Specifically: @@ -184,6 +192,12 @@ class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public Pairin CHIP_ERROR Init(uint16_t mySessionId, SessionEstablishmentDelegate * delegate); + // On success, sets mIpk to the correct value for outgoing Sigma1 based on internal state + CHIP_ERROR RecoverInitiatorIpk(); + // On success, sets locally maching mFabricInfo in internal state to the entry matched by + // destinationId/initiatorRandom from processing of Sigma1 + CHIP_ERROR FindLocalNodeFromDestionationId(const ByteSpan & destinationId, const ByteSpan & initiatorRandom); + CHIP_ERROR SendSigma1(); CHIP_ERROR HandleSigma1_and_SendSigma2(System::PacketBufferHandle && msg); CHIP_ERROR HandleSigma1(System::PacketBufferHandle && msg); @@ -203,7 +217,6 @@ class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public Pairin CHIP_ERROR ConstructTBSData(const ByteSpan & senderNOC, const ByteSpan & senderICAC, const ByteSpan & senderPubKey, const ByteSpan & receiverPubKey, uint8_t * tbsData, size_t & tbsDataLen); CHIP_ERROR ConstructSaltSigma3(const ByteSpan & ipk, MutableByteSpan & salt); - CHIP_ERROR RetrieveIPK(FabricId fabricId, MutableByteSpan & ipk); CHIP_ERROR ConstructSigmaResumeKey(const ByteSpan & initiatorRandom, const ByteSpan & resumptionID, const ByteSpan & skInfo, const ByteSpan & nonce, MutableByteSpan & resumeKey); @@ -241,16 +254,16 @@ class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public Pairin Crypto::P256Keypair mEphemeralKey; #endif Crypto::P256ECDHDerivedSecret mSharedSecret; - Credentials::CertificateKeyId mTrustedRootId; Credentials::ValidationContext mValidContext; + Credentials::GroupDataProvider * mGroupDataProvider = nullptr; uint8_t mMessageDigest[Crypto::kSHA256_Hash_Length]; uint8_t mIPK[kIPKSize]; Messaging::ExchangeContext * mExchangeCtxt = nullptr; - FabricTable * mFabricsTable = nullptr; - FabricInfo * mFabricInfo = nullptr; + FabricTable * mFabricsTable = nullptr; + const FabricInfo * mFabricInfo = nullptr; uint8_t mResumptionId[kCASEResumptionIDSize]; // Sigma1 initiator random, maintained to be reused post-Sigma1, such as when generating Sigma2 S2RK key @@ -266,17 +279,6 @@ class DLL_EXPORT CASESession : public Messaging::ExchangeDelegate, public Pairin protected: bool mCASESessionEstablished = false; - virtual ByteSpan * GetIPKList() const - { - // TODO: Remove this list. Replace it with an actual method to retrieve an IPK list (e.g. from a Crypto Store API) - static uint8_t sIPKList[][kIPKSize] = { - { 0 }, /* Corresponds to the FabricID for the Commissioning Example. All zeros. */ - }; - static ByteSpan ipkListSpan[] = { ByteSpan(sIPKList[0]) }; - return ipkListSpan; - } - virtual size_t GetIPKListEntries() const { return 1; } - void SetSessionTimeStamp(uint64_t timestamp) { mSessionSetupTimeStamp = timestamp; } }; diff --git a/src/protocols/secure_channel/tests/TestCASESession.cpp b/src/protocols/secure_channel/tests/TestCASESession.cpp index 252acda9d88aa6..c89f68b929cd5f 100644 --- a/src/protocols/secure_channel/tests/TestCASESession.cpp +++ b/src/protocols/secure_channel/tests/TestCASESession.cpp @@ -21,17 +21,19 @@ * This file implements unit tests for the CASESession implementation. */ -#include -#include - #include +#include +#include #include #include +#include #include #include #include +#include #include #include +#include #include #include #include @@ -58,16 +60,15 @@ auto & gLoopback = sContext.GetLoopback(); FabricTable gCommissionerFabrics; FabricIndex gCommissionerFabricIndex; +GroupDataProviderImpl gCommissionerGroupDataProvider; +TestPersistentStorageDelegate gCommissionerStorageDelegate; + FabricTable gDeviceFabrics; FabricIndex gDeviceFabricIndex; +GroupDataProviderImpl gDeviceGroupDataProvider; +TestPersistentStorageDelegate gDeviceStorageDelegate; NodeId Node01_01 = 0xDEDEDEDE00010001; -} // namespace - -enum -{ - kStandardCertsCount = 3, -}; class TestCASESecurePairingDelegate : public SessionEstablishmentDelegate { @@ -76,45 +77,57 @@ class TestCASESecurePairingDelegate : public SessionEstablishmentDelegate void OnSessionEstablished() override { mNumPairingComplete++; } + // TODO: Rename mNumPairing* to mNumEstablishment* uint32_t mNumPairingErrors = 0; uint32_t mNumPairingComplete = 0; }; -class TestCASESessionIPK : public CASESession -{ -protected: - ByteSpan * GetIPKList() const override - { - // TODO: Remove this list. Replace it with an actual method to retrieve an IPK list (e.g. from a Crypto Store API) - static uint8_t sIPKList[][kIPKSize] = { - { 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, 0x1D, - 0x1D }, /* Corresponds to the FabricID for the Node01_01 Test Vector */ - }; - static ByteSpan ipkListSpan[] = { ByteSpan(sIPKList[0]) }; - return ipkListSpan; - } - size_t GetIPKListEntries() const override { return 1; } -}; - -class TestCASEServerIPK : public CASEServer +class CASEServerForTest : public CASEServer { public: - TestCASESessionIPK & GetSession() override { return mPairingSession; } + CASESession & GetSession() override { return mCaseSession; } private: - TestCASESessionIPK mPairingSession; + CASESession mCaseSession; }; -static CHIP_ERROR InitCredentialSets() +CHIP_ERROR InitTestIpk(GroupDataProvider & groupDataProvider, const FabricInfo & fabricInfo, size_t numIpks) { + VerifyOrReturnError((numIpks > 0) && (numIpks <= 3), CHIP_ERROR_INVALID_ARGUMENT); + using KeySet = chip::Credentials::GroupDataProvider::KeySet; + using SecurityPolicy = chip::Credentials::GroupDataProvider::SecurityPolicy; + + KeySet ipkKeySet(GroupDataProvider::kIdentityProtectionKeySetId, SecurityPolicy::kTrustFirst, static_cast(numIpks)); + + for (size_t ipkIndex = 0; ipkIndex < numIpks; ++ipkIndex) + { + // Set start time to 0, 1000, 2000, etc + ipkKeySet.epoch_keys[ipkIndex].start_time = static_cast(ipkIndex * 1000); + // Set IPK Epoch key to 00.....00, 01....01, 02.....02, etc + memset(&ipkKeySet.epoch_keys[ipkIndex].key, static_cast(ipkIndex), sizeof(ipkKeySet.epoch_keys[ipkIndex].key)); + } + + uint8_t compressedId[sizeof(uint64_t)]; + MutableByteSpan compressedIdSpan(compressedId); + ReturnErrorOnFailure(fabricInfo.GetCompressedId(compressedIdSpan)); + return groupDataProvider.SetKeySet(fabricInfo.GetFabricIndex(), compressedIdSpan, ipkKeySet); +} + +CHIP_ERROR InitCredentialSets() +{ + gCommissionerStorageDelegate.ClearStorage(); + gCommissionerGroupDataProvider.SetStorageDelegate(&gCommissionerStorageDelegate); + ReturnErrorOnFailure(gCommissionerGroupDataProvider.Init()); + FabricInfo commissionerFabric; P256SerializedKeypair opKeysSerialized; - 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); + // TODO: Rename gCommissioner* to gInitiator* + memcpy((uint8_t *) (opKeysSerialized), sTestCert_Node01_02_PublicKey, sTestCert_Node01_02_PublicKey_Len); + memcpy((uint8_t *) (opKeysSerialized) + sTestCert_Node01_02_PublicKey_Len, sTestCert_Node01_02_PrivateKey, + sTestCert_Node01_02_PrivateKey_Len); - ReturnErrorOnFailure(opKeysSerialized.SetLength(sTestCert_Node01_01_PublicKey_Len + sTestCert_Node01_01_PrivateKey_Len)); + ReturnErrorOnFailure(opKeysSerialized.SetLength(sTestCert_Node01_02_PublicKey_Len + sTestCert_Node01_02_PrivateKey_Len)); P256Keypair opKey; ReturnErrorOnFailure(opKey.Deserialize(opKeysSerialized)); @@ -122,10 +135,17 @@ static CHIP_ERROR InitCredentialSets() ReturnErrorOnFailure(commissionerFabric.SetRootCert(ByteSpan(sTestCert_Root01_Chip, sTestCert_Root01_Chip_Len))); ReturnErrorOnFailure(commissionerFabric.SetICACert(ByteSpan(sTestCert_ICA01_Chip, sTestCert_ICA01_Chip_Len))); - ReturnErrorOnFailure(commissionerFabric.SetNOCCert(ByteSpan(sTestCert_Node01_01_Chip, sTestCert_Node01_01_Chip_Len))); + ReturnErrorOnFailure(commissionerFabric.SetNOCCert(ByteSpan(sTestCert_Node01_02_Chip, sTestCert_Node01_02_Chip_Len))); ReturnErrorOnFailure(gCommissionerFabrics.AddNewFabric(commissionerFabric, &gCommissionerFabricIndex)); + FabricInfo * newFabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); + VerifyOrReturnError(newFabric != nullptr, CHIP_ERROR_INTERNAL); + ReturnErrorOnFailure(InitTestIpk(gCommissionerGroupDataProvider, *newFabric, /* numIpks= */ 1)); + + gDeviceStorageDelegate.ClearStorage(); + gDeviceGroupDataProvider.SetStorageDelegate(&gDeviceStorageDelegate); + ReturnErrorOnFailure(gDeviceGroupDataProvider.Init()); FabricInfo deviceFabric; memcpy((uint8_t *) (opKeysSerialized), sTestCert_Node01_01_PublicKey, sTestCert_Node01_01_PublicKey_Len); @@ -143,14 +163,21 @@ static CHIP_ERROR InitCredentialSets() ReturnErrorOnFailure(gDeviceFabrics.AddNewFabric(deviceFabric, &gDeviceFabricIndex)); + // TODO: Validate more cases of number of IPKs on both sides + newFabric = gDeviceFabrics.FindFabricWithIndex(gDeviceFabricIndex); + VerifyOrReturnError(newFabric != nullptr, CHIP_ERROR_INTERNAL); + ReturnErrorOnFailure(InitTestIpk(gDeviceGroupDataProvider, *newFabric, /* numIpks= */ 1)); + return CHIP_NO_ERROR; } +} // namespace + void CASE_SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext) { // Test all combinations of invalid parameters TestCASESecurePairingDelegate delegate; - TestCASESessionIPK pairing; + CASESession pairing; FabricTable fabrics; NL_TEST_ASSERT(inSuite, pairing.GetSecureSessionType() == SecureSession::Type::kCASE); @@ -158,6 +185,7 @@ void CASE_SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext) peerCATs = pairing.GetPeerCATs(); NL_TEST_ASSERT(inSuite, memcmp(&peerCATs, &kUndefinedCATs, sizeof(CATValues)) == 0); + pairing.SetGroupDataProvider(&gDeviceGroupDataProvider); NL_TEST_ASSERT(inSuite, pairing.ListenForSessionEstablishment(0, nullptr, nullptr) == CHIP_ERROR_INVALID_ARGUMENT); NL_TEST_ASSERT(inSuite, pairing.ListenForSessionEstablishment(0, nullptr, &delegate) == CHIP_ERROR_INVALID_ARGUMENT); NL_TEST_ASSERT(inSuite, pairing.ListenForSessionEstablishment(0, &fabrics, &delegate) == CHIP_NO_ERROR); @@ -170,6 +198,8 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) // Test all combinations of invalid parameters TestCASESecurePairingDelegate delegate; CASESession pairing; + pairing.SetGroupDataProvider(&gCommissionerGroupDataProvider); + FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); NL_TEST_ASSERT(inSuite, fabric != nullptr); @@ -200,6 +230,7 @@ void CASE_SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) gLoopback.mMessageSendError = CHIP_ERROR_BAD_REQUEST; CASESession pairing1; + pairing1.SetGroupDataProvider(&gCommissionerGroupDataProvider); gLoopback.mSentMessageCount = 0; gLoopback.mMessageSendError = CHIP_ERROR_BAD_REQUEST; @@ -220,7 +251,7 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte // Test all combinations of invalid parameters TestCASESecurePairingDelegate delegateAccessory; - TestCASESessionIPK pairingAccessory; + CASESession pairingAccessory; CASESessionCachable serializableCommissioner; CASESessionCachable serializableAccessory; @@ -235,6 +266,7 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte FabricInfo * fabric = gCommissionerFabrics.FindFabricWithIndex(gCommissionerFabricIndex); NL_TEST_ASSERT(inSuite, fabric != nullptr); + pairingAccessory.SetGroupDataProvider(&gDeviceGroupDataProvider); NL_TEST_ASSERT(inSuite, pairingAccessory.ListenForSessionEstablishment(0, &gDeviceFabrics, &delegateAccessory) == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, @@ -256,118 +288,21 @@ void CASE_SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inConte void CASE_SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) { TestCASESecurePairingDelegate delegateCommissioner; - TestCASESessionIPK pairingCommissioner; + CASESession pairingCommissioner; + pairingCommissioner.SetGroupDataProvider(&gCommissionerGroupDataProvider); CASE_SecurePairingHandshakeTestCommon(inSuite, inContext, pairingCommissioner, delegateCommissioner); } -class TestCASESessionPersistentStorageDelegate : public PersistentStorageDelegate -{ -public: - TestCASESessionPersistentStorageDelegate() - { - memset(keys, 0, sizeof(keys)); - memset(keysize, 0, sizeof(keysize)); - memset(values, 0, sizeof(values)); - memset(valuesize, 0, sizeof(valuesize)); - } - - ~TestCASESessionPersistentStorageDelegate() { Cleanup(); } - - void Cleanup() - { - for (int i = 0; i < 16; i++) - { - if (keys[i] != nullptr) - { - chip::Platform::MemoryFree(keys[i]); - keys[i] = nullptr; - } - if (values[i] != nullptr) - { - chip::Platform::MemoryFree(values[i]); - values[i] = nullptr; - } - } - } - - CHIP_ERROR SyncGetKeyValue(const char * key, void * buffer, uint16_t & size) override - { - for (int i = 0; i < 16; i++) - { - if (keys[i] != nullptr && keysize[i] != 0 && strncmp(key, keys[i], keysize[i]) == 0) - { - if (size >= valuesize[i]) - { - memcpy(buffer, values[i], valuesize[i]); - size = valuesize[i]; - return CHIP_NO_ERROR; - } - else - { - size = (valuesize[i]); - return CHIP_ERROR_BUFFER_TOO_SMALL; - } - } - } - return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; - } - - CHIP_ERROR SyncSetKeyValue(const char * key, const void * value, uint16_t size) override - { - for (int i = 0; i < 16; i++) - { - if (keys[i] == nullptr && keysize[i] == 0 && valuesize[i] == 0) - { - keysize[i] = static_cast(strlen(key)); - keysize[i]++; - keys[i] = reinterpret_cast(chip::Platform::MemoryAlloc(keysize[i])); - memcpy(keys[i], key, keysize[i]); - values[i] = reinterpret_cast(chip::Platform::MemoryAlloc(size)); - memcpy(values[i], value, size); - valuesize[i] = size; - return CHIP_NO_ERROR; - } - } - return CHIP_ERROR_INTERNAL; - } - - CHIP_ERROR SyncDeleteKeyValue(const char * key) override - { - for (int i = 0; i < 16; i++) - { - if (keys[i] != nullptr && keysize[i] != 0 && strncmp(key, keys[i], keysize[i]) == 0) - { - Platform::MemoryFree(keys[i]); - keys[i] = nullptr; - - if (values[i] != nullptr) - { - Platform::MemoryFree(values[i]); - values[i] = nullptr; - } - return CHIP_NO_ERROR; - } - } - return CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND; - } - -private: - char * keys[16]; // Not null-terminated - void * values[16]; - uint16_t keysize[16]; - uint16_t valuesize[16]; -}; - -TestCASESessionPersistentStorageDelegate gCommissionerStorageDelegate; -TestCASESessionPersistentStorageDelegate gDeviceStorageDelegate; - -TestCASEServerIPK gPairingServer; +CASEServerForTest gPairingServer; void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inContext) { + // TODO: Add cases for mismatching IPK config between initiator/responder + TestCASESecurePairingDelegate delegateCommissioner; - auto * pairingCommissioner = chip::Platform::New(); + auto * pairingCommissioner = chip::Platform::New(); + pairingCommissioner->SetGroupDataProvider(&gCommissionerGroupDataProvider); TestContext & ctx = *reinterpret_cast(inContext); @@ -378,7 +313,8 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte #if CONFIG_NETWORK_LAYER_BLE nullptr, #endif - &ctx.GetSecureSessionManager(), &gDeviceFabrics) == CHIP_NO_ERROR); + &ctx.GetSecureSessionManager(), &gDeviceFabrics, + &gDeviceGroupDataProvider) == CHIP_NO_ERROR); ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner); @@ -393,7 +329,8 @@ void CASE_SecurePairingHandshakeServerTest(nlTestSuite * inSuite, void * inConte NL_TEST_ASSERT(inSuite, gLoopback.mSentMessageCount == 5); NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 1); - auto * pairingCommissioner1 = chip::Platform::New(); + auto * pairingCommissioner1 = chip::Platform::New(); + pairingCommissioner1->SetGroupDataProvider(&gCommissionerGroupDataProvider); ExchangeContext * contextCommissioner1 = ctx.NewUnauthenticatedExchangeToBob(pairingCommissioner1); NL_TEST_ASSERT(inSuite, @@ -429,6 +366,57 @@ struct Sigma1Params static constexpr bool expectSuccess = true; }; +void CASE_DestinationIdTest(nlTestSuite * inSuite, void * inContext) +{ + // Validate example test vector from CASE section of spec + + const uint8_t kRootPubKeyFromSpec[Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES] = { + 0x04, 0x4a, 0x9f, 0x42, 0xb1, 0xca, 0x48, 0x40, 0xd3, 0x72, 0x92, 0xbb, 0xc7, 0xf6, 0xa7, 0xe1, 0x1e, + 0x22, 0x20, 0x0c, 0x97, 0x6f, 0xc9, 0x00, 0xdb, 0xc9, 0x8a, 0x7a, 0x38, 0x3a, 0x64, 0x1c, 0xb8, 0x25, + 0x4a, 0x2e, 0x56, 0xd4, 0xe2, 0x95, 0xa8, 0x47, 0x94, 0x3b, 0x4e, 0x38, 0x97, 0xc4, 0xa7, 0x73, 0xe9, + 0x30, 0x27, 0x7b, 0x4d, 0x9f, 0xbe, 0xde, 0x8a, 0x05, 0x26, 0x86, 0xbf, 0xac, 0xfa + }; + + const uint8_t kIpkOperationalGroupKeyFromSpec[Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES] = { + 0x9b, 0xc6, 0x1c, 0xd9, 0xc6, 0x2a, 0x2d, 0xf6, 0xd6, 0x4d, 0xfc, 0xaa, 0x9d, 0xc4, 0x72, 0xd4 + }; + + const uint8_t kInitiatorRandomFromSpec[Sigma1Params::initiatorRandomLen] = { 0x7e, 0x17, 0x12, 0x31, 0x56, 0x8d, 0xfa, 0x17, + 0x20, 0x6b, 0x3a, 0xcc, 0xf8, 0xfa, 0xec, 0x2f, + 0x4d, 0x21, 0xb5, 0x80, 0x11, 0x31, 0x96, 0xf4, + 0x7c, 0x7c, 0x4d, 0xeb, 0x81, 0x0a, 0x73, 0xdc }; + + const uint8_t kExpectedDestinationIdFromSpec[Crypto::kSHA256_Hash_Length] = { 0xdc, 0x35, 0xdd, 0x5f, 0xc9, 0x13, 0x4c, 0xc5, + 0x54, 0x45, 0x38, 0xc9, 0xc3, 0xfc, 0x42, 0x97, + 0xc1, 0xec, 0x33, 0x70, 0xc8, 0x39, 0x13, 0x6a, + 0x80, 0xe1, 0x07, 0x96, 0x45, 0x1d, 0x4c, 0x53 }; + + const FabricId kFabricIdFromSpec = 0x2906C908D115D362; + const NodeId kNodeIdFromSpec = 0xCD5544AA7B13EF14; + + uint8_t destinationIdBuf[Crypto::kSHA256_Hash_Length] = { 0 }; + MutableByteSpan destinationIdSpan(destinationIdBuf); + + // Test exact example + CHIP_ERROR err = + GenerateCaseDestinationId(ByteSpan(kIpkOperationalGroupKeyFromSpec), ByteSpan(kInitiatorRandomFromSpec), + ByteSpan(kRootPubKeyFromSpec), kFabricIdFromSpec, kNodeIdFromSpec, destinationIdSpan); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == err); + NL_TEST_ASSERT(inSuite, destinationIdSpan.size() == sizeof(destinationIdBuf)); + NL_TEST_ASSERT(inSuite, destinationIdSpan.data_equal(ByteSpan(kExpectedDestinationIdFromSpec))); + + memset(destinationIdSpan.data(), 0, destinationIdSpan.size()); + + // Test changing input: should yield different + err = GenerateCaseDestinationId(ByteSpan(kIpkOperationalGroupKeyFromSpec), ByteSpan(kInitiatorRandomFromSpec), + ByteSpan(kRootPubKeyFromSpec), kFabricIdFromSpec, + kNodeIdFromSpec + 1, // <--- Change node ID + destinationIdSpan); + NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == err); + NL_TEST_ASSERT(inSuite, destinationIdSpan.size() == sizeof(destinationIdBuf)); + NL_TEST_ASSERT(inSuite, !destinationIdSpan.data_equal(ByteSpan(kExpectedDestinationIdFromSpec))); +} + template static CHIP_ERROR EncodeSigma1(MutableByteSpan & buf) { @@ -633,6 +621,7 @@ static const nlTest sTests[] = NL_TEST_DEF("Handshake", CASE_SecurePairingHandshakeTest), NL_TEST_DEF("ServerHandshake", CASE_SecurePairingHandshakeServerTest), NL_TEST_DEF("Sigma1Parsing", CASE_Sigma1ParsingTest), + NL_TEST_DEF("DestinationId", CASE_DestinationIdTest), NL_TEST_SENTINEL() }; @@ -675,7 +664,13 @@ CHIP_ERROR CASETestSecurePairingSetup(void * inContext) */ int CASE_TestSecurePairing_Setup(void * inContext) { - return CASETestSecurePairingSetup(inContext) == CHIP_NO_ERROR ? SUCCESS : FAILURE; + CHIP_ERROR err = CASETestSecurePairingSetup(inContext); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Support, "Failed to init tests %" CHIP_ERROR_FORMAT, err.Format()); + return FAILURE; + } + return SUCCESS; } /** @@ -683,8 +678,8 @@ int CASE_TestSecurePairing_Setup(void * inContext) */ int CASE_TestSecurePairing_Teardown(void * inContext) { - gCommissionerStorageDelegate.Cleanup(); - gDeviceStorageDelegate.Cleanup(); + gCommissionerStorageDelegate.ClearStorage(); + gDeviceStorageDelegate.ClearStorage(); gCommissionerFabrics.DeleteAllFabrics(); gDeviceFabrics.DeleteAllFabrics(); static_cast(inContext)->Shutdown();