diff --git a/examples/chip-tool/BUILD.gn b/examples/chip-tool/BUILD.gn index 7a652661616296..7a0b440ad536dc 100644 --- a/examples/chip-tool/BUILD.gn +++ b/examples/chip-tool/BUILD.gn @@ -70,6 +70,8 @@ static_library("chip-tool-utils") { "commands/discover/DiscoverCommand.cpp", "commands/discover/DiscoverCommissionablesCommand.cpp", "commands/discover/DiscoverCommissionersCommand.cpp", + "commands/icd/ICDCommand.cpp", + "commands/icd/ICDCommand.h", "commands/pairing/OpenCommissioningWindowCommand.cpp", "commands/pairing/OpenCommissioningWindowCommand.h", "commands/pairing/PairingCommand.cpp", @@ -100,6 +102,7 @@ static_library("chip-tool-utils") { public_deps = [ "${chip_root}/examples/common/tracing:commandline", + "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/app/server", "${chip_root}/src/app/tests/suites/commands/interaction_model", "${chip_root}/src/controller/data_model", diff --git a/examples/chip-tool/commands/common/CHIPCommand.cpp b/examples/chip-tool/commands/common/CHIPCommand.cpp index 163af90fdd6f78..7e721734cd1a80 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.cpp +++ b/examples/chip-tool/commands/common/CHIPCommand.cpp @@ -47,6 +47,9 @@ constexpr char kCDTrustStorePathVariable[] = "CHIPTOOL_CD_TRUST_STORE_PATH" const chip::Credentials::AttestationTrustStore * CHIPCommand::sTrustStore = nullptr; chip::Credentials::GroupDataProviderImpl CHIPCommand::sGroupDataProvider{ kMaxGroupsPerFabric, kMaxGroupKeysPerFabric }; +// All fabrics share the same ICD client storage. +chip::app::DefaultICDClientStorage CHIPCommand::sICDClientStorage; +chip::Crypto::RawKeySessionKeystore CHIPCommand::sSessionKeystore; namespace { @@ -100,13 +103,19 @@ CHIP_ERROR CHIPCommand::MaybeSetUpStack() ReturnLogErrorOnFailure(mOperationalKeystore.Init(&mDefaultStorage)); ReturnLogErrorOnFailure(mOpCertStore.Init(&mDefaultStorage)); + // chip-tool uses a non-persistent keystore. + // ICD storage lifetime is currently tied to the chip-tool's lifetime. Since chip-tool interactive mode is currently used for + // ICD commissioning and check-in validation, this temporary storage meets the test requirements. + // TODO: Implement persistent ICD storage for the chip-tool. + ReturnLogErrorOnFailure(sICDClientStorage.Init(&mDefaultStorage, &sSessionKeystore)); + chip::Controller::FactoryInitParams factoryInitParams; factoryInitParams.fabricIndependentStorage = &mDefaultStorage; factoryInitParams.operationalKeystore = &mOperationalKeystore; factoryInitParams.opCertStore = &mOpCertStore; factoryInitParams.enableServerInteractions = NeedsOperationalAdvertising(); - factoryInitParams.sessionKeystore = &mSessionKeystore; + factoryInitParams.sessionKeystore = &sSessionKeystore; // 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 @@ -486,6 +495,8 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(CommissionerIdentity & identity, chip::Credentials::SetSingleIpkEpochKey(&sGroupDataProvider, fabricIndex, defaultIpk, compressed_fabric_id_span)); } + CHIPCommand::sICDClientStorage.UpdateFabricList(commissioner->GetFabricIndex()); + mCommissioners[identity] = std::move(commissioner); return CHIP_NO_ERROR; diff --git a/examples/chip-tool/commands/common/CHIPCommand.h b/examples/chip-tool/commands/common/CHIPCommand.h index ae906166f28fa5..3376e47ba0f20d 100644 --- a/examples/chip-tool/commands/common/CHIPCommand.h +++ b/examples/chip-tool/commands/common/CHIPCommand.h @@ -25,6 +25,7 @@ #include "Command.h" #include +#include #include #include #include @@ -155,9 +156,10 @@ class CHIPCommand : public Command #endif // CONFIG_USE_LOCAL_STORAGE chip::PersistentStorageOperationalKeystore mOperationalKeystore; chip::Credentials::PersistentStorageOpCertStore mOpCertStore; - chip::Crypto::RawKeySessionKeystore mSessionKeystore; + static chip::Crypto::RawKeySessionKeystore sSessionKeystore; static chip::Credentials::GroupDataProviderImpl sGroupDataProvider; + static chip::app::DefaultICDClientStorage sICDClientStorage; CredentialIssuerCommands * mCredIssuerCmds; std::string GetIdentity(); diff --git a/examples/chip-tool/commands/icd/ICDCommand.cpp b/examples/chip-tool/commands/icd/ICDCommand.cpp new file mode 100644 index 00000000000000..f3b43cfce6497f --- /dev/null +++ b/examples/chip-tool/commands/icd/ICDCommand.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "ICDCommand.h" + +#include +#include + +using namespace ::chip; + +CHIP_ERROR ICDListCommand::RunCommand() +{ + app::ICDClientInfo info; + auto iter = CHIPCommand::sICDClientStorage.IterateICDClientInfo(); + char icdSymmetricKeyHex[Crypto::kAES_CCM128_Key_Length * 2 + 1]; + + fprintf(stderr, " +-----------------------------------------------------------------------------+\n"); + fprintf(stderr, " | %-75s |\n", "Known ICDs:"); + fprintf(stderr, " +-----------------------------------------------------------------------------+\n"); + fprintf(stderr, " | %20s | %15s | %15s | %16s |\n", "Fabric Index:Node ID", "Start Counter", "Counter Offset", + "MonitoredSubject"); + + while (iter->Next(info)) + { + fprintf(stderr, " +-----------------------------------------------------------------------------+\n"); + fprintf(stderr, " | %3" PRIu32 ":" ChipLogFormatX64 " | %15" PRIu32 " | %15" PRIu32 " | " ChipLogFormatX64 " |\n", + static_cast(info.peer_node.GetFabricIndex()), ChipLogValueX64(info.peer_node.GetNodeId()), + info.start_icd_counter, info.offset, ChipLogValueX64(info.monitored_subject)); + + static_assert(std::is_same::value, + "The following BytesToHex can copy/encode the key bytes from sharedKey to hexadecimal format, which only " + "works for RawKeySessionKeystore"); + Encoding::BytesToHex(info.shared_key.As(), Crypto::kAES_CCM128_Key_Length, + icdSymmetricKeyHex, sizeof(icdSymmetricKeyHex), chip::Encoding::HexFlags::kNullTerminate); + fprintf(stderr, " | Symmetric Key: %60s |\n", icdSymmetricKeyHex); + } + + fprintf(stderr, " +-----------------------------------------------------------------------------+\n"); + + iter->Release(); + SetCommandExitStatus(CHIP_NO_ERROR); + return CHIP_NO_ERROR; +} + +void registerCommandsICD(Commands & commands, CredentialIssuerCommands * credsIssuerConfig) +{ + const char * name = "ICD"; + + commands_list list = { + make_unique(credsIssuerConfig), + }; + + commands.RegisterCommandSet(name, list, "Commands for client-side ICD management."); +} diff --git a/examples/chip-tool/commands/icd/ICDCommand.h b/examples/chip-tool/commands/icd/ICDCommand.h new file mode 100644 index 00000000000000..b36d66a7dbfcb9 --- /dev/null +++ b/examples/chip-tool/commands/icd/ICDCommand.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2023 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#include "../common/CHIPCommand.h" +#include "commands/common/Commands.h" + +#include + +class ICDCommand : public CHIPCommand +{ +public: + ICDCommand(const char * commandName, CredentialIssuerCommands * credIssuerCmds, const char * description) : + CHIPCommand(commandName, credIssuerCmds, description) + {} + + chip::System::Clock::Timeout GetWaitDuration() const override { return chip::System::Clock::Seconds16(10); } +}; + +class ICDListCommand : public ICDCommand +{ +public: + ICDListCommand(CredentialIssuerCommands * credIssuerCmds) : + ICDCommand("list", credIssuerCmds, "List ICDs registed by this controller.") + {} + CHIP_ERROR RunCommand() override; +}; + +void registerCommandsICD(Commands & commands, CredentialIssuerCommands * credsIssuerConfig); diff --git a/examples/chip-tool/commands/pairing/PairingCommand.cpp b/examples/chip-tool/commands/pairing/PairingCommand.cpp index 049c34e6aa84a5..e15be0a1e8ae3c 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.cpp +++ b/examples/chip-tool/commands/pairing/PairingCommand.cpp @@ -37,6 +37,8 @@ CHIP_ERROR PairingCommand::RunCommand() // Clear the CATs in OperationalCredentialsIssuer mCredIssuerCmds->SetCredentialIssuerCATValues(kUndefinedCATs); + mDeviceIsICD = false; + if (mCASEAuthTags.HasValue() && mCASEAuthTags.Value().size() <= kMaxSubjectCATAttributeCount) { CATValues cats = kUndefinedCATs; @@ -385,6 +387,16 @@ void PairingCommand::OnCommissioningComplete(NodeId nodeId, CHIP_ERROR err) } else { + // When ICD device commissioning fails, the ICDClientInfo stored in OnICDRegistrationComplete needs to be removed. + if (mDeviceIsICD) + { + CHIP_ERROR deleteEntryError = + CHIPCommand::sICDClientStorage.DeleteEntry(ScopedNodeId(mNodeId, CurrentCommissioner().GetFabricIndex())); + if (deleteEntryError != CHIP_NO_ERROR) + { + ChipLogError(chipTool, "Failed to delete ICD entry: %s", ErrorStr(err)); + } + } ChipLogProgress(chipTool, "Device commissioning Failure: %s", ErrorStr(err)); } @@ -395,6 +407,7 @@ void PairingCommand::OnICDRegistrationInfoRequired() { // Since we compute our ICD Registration info up front, we can call ICDRegistrationInfoReady() directly. CurrentCommissioner().ICDRegistrationInfoReady(); + mDeviceIsICD = true; } void PairingCommand::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounter) @@ -404,8 +417,27 @@ void PairingCommand::OnICDRegistrationComplete(NodeId nodeId, uint32_t icdCounte chip::Encoding::BytesToHex(mICDSymmetricKey.Value().data(), mICDSymmetricKey.Value().size(), icdSymmetricKeyHex, sizeof(icdSymmetricKeyHex), chip::Encoding::HexFlags::kNullTerminate); - // TODO: Persist symmetric key. + app::ICDClientInfo clientInfo; + clientInfo.peer_node = ScopedNodeId(nodeId, CurrentCommissioner().GetFabricIndex()); + clientInfo.monitored_subject = mICDMonitoredSubject.Value(); + clientInfo.start_icd_counter = icdCounter; + + CHIP_ERROR err = CHIPCommand::sICDClientStorage.SetKey(clientInfo, mICDSymmetricKey.Value()); + if (err == CHIP_NO_ERROR) + { + err = CHIPCommand::sICDClientStorage.StoreEntry(clientInfo); + } + + if (err != CHIP_NO_ERROR) + { + CHIPCommand::sICDClientStorage.RemoveKey(clientInfo); + ChipLogError(chipTool, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId), + err.AsString()); + SetCommandExitStatus(err); + return; + } + ChipLogProgress(chipTool, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId)); ChipLogProgress(chipTool, "ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64 " / Monitored Subject: " ChipLogFormatX64 " / Symmetric Key: %s", diff --git a/examples/chip-tool/commands/pairing/PairingCommand.h b/examples/chip-tool/commands/pairing/PairingCommand.h index 089467d4042dde..be4722228a2bc8 100644 --- a/examples/chip-tool/commands/pairing/PairingCommand.h +++ b/examples/chip-tool/commands/pairing/PairingCommand.h @@ -253,6 +253,7 @@ class PairingCommand : public CHIPCommand, uint64_t mDiscoveryFilterCode; char * mDiscoveryFilterInstanceName; + bool mDeviceIsICD; uint8_t mRandomGeneratedICDSymmetricKey[chip::Crypto::kAES_CCM128_Key_Length]; // For unpair @@ -260,4 +261,5 @@ class PairingCommand : public CHIPCommand, chip::Callback::Callback mCurrentFabricRemoveCallback; static void OnCurrentFabricRemove(void * context, NodeId remoteNodeId, CHIP_ERROR status); + void PersistIcdInfo(); }; diff --git a/examples/chip-tool/main.cpp b/examples/chip-tool/main.cpp index a1eb4646a9fd98..6a52941e8b8d9c 100644 --- a/examples/chip-tool/main.cpp +++ b/examples/chip-tool/main.cpp @@ -23,6 +23,7 @@ #include "commands/delay/Commands.h" #include "commands/discover/Commands.h" #include "commands/group/Commands.h" +#include "commands/icd/ICDCommand.h" #include "commands/interactive/Commands.h" #include "commands/pairing/Commands.h" #include "commands/payload/Commands.h" @@ -40,6 +41,7 @@ int main(int argc, char * argv[]) Commands commands; registerCommandsDelay(commands, &credIssuerCommands); registerCommandsDiscover(commands, &credIssuerCommands); + registerCommandsICD(commands, &credIssuerCommands); registerCommandsInteractive(commands, &credIssuerCommands); registerCommandsPayload(commands); registerCommandsPairing(commands, &credIssuerCommands); diff --git a/examples/tv-casting-app/tv-casting-common/BUILD.gn b/examples/tv-casting-app/tv-casting-common/BUILD.gn index 4e87c59f118a99..c5ad03964cb0d9 100644 --- a/examples/tv-casting-app/tv-casting-common/BUILD.gn +++ b/examples/tv-casting-app/tv-casting-common/BUILD.gn @@ -110,6 +110,7 @@ chip_data_model("tv-casting-common") { deps = [ "${chip_root}/examples/common/tracing:commandline", + "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/app/tests/suites/commands/interaction_model", "${chip_root}/src/lib/support/jsontlv", "${chip_root}/src/tracing", diff --git a/src/app/icd/client/DefaultICDClientStorage.h b/src/app/icd/client/DefaultICDClientStorage.h index 83729dced069b1..be1c8a2f4056ae 100644 --- a/src/app/icd/client/DefaultICDClientStorage.h +++ b/src/app/icd/client/DefaultICDClientStorage.h @@ -15,6 +15,10 @@ * limitations under the License. */ +// Do not use the DefaultICDClientStorage class in settings where fabric indices are not stable. +// This class relies on the stability of fabric indices for efficient storage and retrieval of ICD client information. +// If fabric indices are not stable, the functionality of this class will be compromised and can lead to unexpected behavior. + #pragma once #include "ICDClientStorage.h" @@ -52,6 +56,17 @@ class DefaultICDClientStorage : public ICDClientStorage ICDClientInfoIterator * IterateICDClientInfo() override; + /** + * When decrypting check-in messages, the system needs to iterate through all keys + * from all ICD clientInfos. In DefaultICDClientStorage, ICDClientInfos for the same fabric are stored in + * storage using the fabricIndex as the key. To retrieve all relevant ICDClientInfos + * from storage, the system needs to know all fabricIndices in advance. The + * `UpdateFabricList` function provides a way to inject newly created fabricIndices + * into a dedicated table. It is recommended to call this function whenever a controller is created + * with a new fabric index. + * + * @param[in] fabricIndex The newly created fabric index. + */ CHIP_ERROR UpdateFabricList(FabricIndex fabricIndex); CHIP_ERROR SetKey(ICDClientInfo & clientInfo, const ByteSpan keyData) override;