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();