From 849f8824e00147543135a808be67a0b722b93f87 Mon Sep 17 00:00:00 2001 From: thivya-amazon <99231372+thivya-amazon@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:40:02 -0800 Subject: [PATCH] [ICD] Refresh key to avoid check-in counter rollover problems. (#31465) * Refresh key to avoid check-in counter rollover problems. * Restyled by whitespace * Restyled by gn * Added CommandSender callbacks * Addressed review comments. * Modified design for delegate to allocate/deallocate ICDRefreshKeyInfo * Removed newline * Added a comment to suppress lint error * Renamed ICDRefreshKeyInfo to RefreshKeySender * Removed the data structure used for tracking refreshkeysender. * Formatting changes. * Formatting changes. * Formatting changes * Addressed review comments. * Addressed review comments --------- Co-authored-by: Restyled.io --- src/app/icd/client/BUILD.gn | 2 + src/app/icd/client/CheckInDelegate.h | 37 ++++++ src/app/icd/client/CheckInHandler.cpp | 18 ++- src/app/icd/client/DefaultCheckInDelegate.cpp | 40 +++++- src/app/icd/client/DefaultCheckInDelegate.h | 2 + src/app/icd/client/RefreshKeySender.cpp | 116 ++++++++++++++++++ src/app/icd/client/RefreshKeySender.h | 93 ++++++++++++++ 7 files changed, 303 insertions(+), 5 deletions(-) create mode 100644 src/app/icd/client/RefreshKeySender.cpp create mode 100644 src/app/icd/client/RefreshKeySender.h diff --git a/src/app/icd/client/BUILD.gn b/src/app/icd/client/BUILD.gn index fa32e9c68dbfd4..1a1a5acdd34ae7 100644 --- a/src/app/icd/client/BUILD.gn +++ b/src/app/icd/client/BUILD.gn @@ -41,6 +41,8 @@ source_set("handler") { "CheckInHandler.h", "DefaultCheckInDelegate.cpp", "DefaultCheckInDelegate.h", + "RefreshKeySender.cpp", + "RefreshKeySender.h", ] public_deps = [ ":manager", diff --git a/src/app/icd/client/CheckInDelegate.h b/src/app/icd/client/CheckInDelegate.h index 50d378a87a58e7..5c931d414aab2c 100644 --- a/src/app/icd/client/CheckInDelegate.h +++ b/src/app/icd/client/CheckInDelegate.h @@ -19,10 +19,13 @@ #pragma once #include +#include namespace chip { namespace app { +class RefreshKeySender; + /// Callbacks for check in protocol /** * @brief The application implementing an ICD client should inherit the CheckInDelegate and implement the listed callbacks @@ -39,6 +42,40 @@ class DLL_EXPORT CheckInDelegate node that sent the check-in message. */ virtual void OnCheckInComplete(const ICDClientInfo & clientInfo) = 0; + + /** + * @brief Callback used to let the application know that a key refresh is + * needed to avoid counter rollover problems. + * + * The implementer of this function should create a new RefreshKeySender object. This object will be tied to the specific key + * refresh process and will not be used by the caller after that particular key refresh process has ended, regardless of success + * or failure. + * + * The caller guarantees that if a non-null RefreshKeySender pointer is returned, it will call OnKeyRefreshDone + * at some point, and pass it the returned pointer. + * + * If the callee is unable to provide the RefreshKeySender object, that indicates key + * refresh is not possible until the callee is able to provide the required resources. + * + * @param[in] clientInfo - ICDClientInfo object representing the state associated with the + * node that sent the check-in message. The callee can use the clientInfo to determine the type of key + * to generate. + * @param[in] clientStorage - ICDClientStorage object stores the updated ICDClientInfo after re-registration into + * persistent storage. + * @return RefreshKeySender - pointer to RefreshKeySender object + */ + virtual RefreshKeySender * OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) = 0; + + /** + * @brief Callback used to let the application know that the re-registration process is done. This callback will be called for + * both success and failure cases. On failure, the callee should take appropriate corrective action based on the error. + * + * @param[in] refreshKeySender - pointer to the RefreshKeySender object that was used for the key refresh process. The caller + * will NOT use this pointer any more. + * @param[in] error - CHIP_NO_ERROR indicates successful re-registration using the new key + * Other errors indicate the failure reason. + */ + virtual void OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) = 0; }; } // namespace app diff --git a/src/app/icd/client/CheckInHandler.cpp b/src/app/icd/client/CheckInHandler.cpp index f05dcd8b7819ed..165301c45480fd 100644 --- a/src/app/icd/client/CheckInHandler.cpp +++ b/src/app/icd/client/CheckInHandler.cpp @@ -23,8 +23,8 @@ */ #include -#include #include +#include #include @@ -109,8 +109,20 @@ CHIP_ERROR CheckInHandler::OnMessageReceived(Messaging::ExchangeContext * ec, co if (refreshKey) { - // TODO: A new CASE session should be established to re-register the client using a new key. The registration will happen in - // CASE session callback + RefreshKeySender * refreshKeySender = mpCheckInDelegate->OnKeyRefreshNeeded(clientInfo, mpICDClientStorage); + if (refreshKeySender == nullptr) + { + ChipLogError(ICD, "Key Refresh failed for node ID:" ChipLogFormatScopedNodeId, + ChipLogValueScopedNodeId(clientInfo.peer_node)); + return CHIP_NO_ERROR; + } + err = refreshKeySender->EstablishSessionToPeer(); + if (CHIP_NO_ERROR != err) + { + ChipLogError(ICD, "CASE session establishment failed with error : %" CHIP_ERROR_FORMAT, err.Format()); + mpCheckInDelegate->OnKeyRefreshDone(refreshKeySender, err); + return CHIP_NO_ERROR; + } } else { diff --git a/src/app/icd/client/DefaultCheckInDelegate.cpp b/src/app/icd/client/DefaultCheckInDelegate.cpp index cec36d2db5660b..8df98006f6e9e0 100644 --- a/src/app/icd/client/DefaultCheckInDelegate.cpp +++ b/src/app/icd/client/DefaultCheckInDelegate.cpp @@ -15,13 +15,12 @@ * limitations under the License. */ -#include "CheckInHandler.h" #include #include +#include #include #include #include -#include namespace chip { namespace app { @@ -44,5 +43,42 @@ void DefaultCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo) #endif } +RefreshKeySender * DefaultCheckInDelegate::OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + RefreshKeySender::RefreshKeyBuffer newKey; + + err = Crypto::DRBG_get_bytes(newKey.Bytes(), newKey.Capacity()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(ICD, "Generation of new key failed: %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + + auto refreshKeySender = Platform::New(this, clientInfo, clientStorage, newKey); + if (refreshKeySender == nullptr) + { + return nullptr; + } + return refreshKeySender; +} + +void DefaultCheckInDelegate::OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) +{ + if (error == CHIP_NO_ERROR) + { + ChipLogProgress(ICD, "Re-registration with new key completed successfully"); + } + else + { + ChipLogError(ICD, "Re-registration with new key failed with error : %" CHIP_ERROR_FORMAT, error.Format()); + // The callee can take corrective action based on the error received. + } + if (refreshKeySender != nullptr) + { + Platform::Delete(refreshKeySender); + refreshKeySender = nullptr; + } +} } // namespace app } // namespace chip diff --git a/src/app/icd/client/DefaultCheckInDelegate.h b/src/app/icd/client/DefaultCheckInDelegate.h index 5e77186b3c7c9d..e7b856677734bd 100644 --- a/src/app/icd/client/DefaultCheckInDelegate.h +++ b/src/app/icd/client/DefaultCheckInDelegate.h @@ -33,6 +33,8 @@ class DefaultCheckInDelegate : public CheckInDelegate virtual ~DefaultCheckInDelegate() {} CHIP_ERROR Init(ICDClientStorage * storage); void OnCheckInComplete(const ICDClientInfo & clientInfo) override; + RefreshKeySender * OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) override; + void OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) override; private: ICDClientStorage * mpStorage = nullptr; diff --git a/src/app/icd/client/RefreshKeySender.cpp b/src/app/icd/client/RefreshKeySender.cpp new file mode 100644 index 00000000000000..45cbbd9c987a68 --- /dev/null +++ b/src/app/icd/client/RefreshKeySender.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2024 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 "RefreshKeySender.h" +#include "CheckInDelegate.h" +#include "controller/InvokeInteraction.h" +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +RefreshKeySender::RefreshKeySender(CheckInDelegate * checkInDelegate, const ICDClientInfo & icdClientInfo, + ICDClientStorage * icdClientStorage, const RefreshKeyBuffer & refreshKeyBuffer) : + mICDClientInfo(icdClientInfo), + mpICDClientStorage(icdClientStorage), mpCheckInDelegate(checkInDelegate), mOnConnectedCallback(HandleDeviceConnected, this), + mOnConnectionFailureCallback(HandleDeviceConnectionFailure, this) + +{ + mNewKey = refreshKeyBuffer; +} + +CHIP_ERROR RefreshKeySender::RegisterClientWithNewKey(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) +{ + auto onSuccess = [&](const ConcreteCommandPath & commandPath, const StatusIB & status, const auto & dataResponse) { + ChipLogProgress(ICD, "RegisterClient command succeeded"); + CHIP_ERROR error; + + // Update the ICDClientInfo with new key and start counter and store it to persistence + mICDClientInfo.start_icd_counter = dataResponse.ICDCounter; + mICDClientInfo.offset = 0; + mpICDClientStorage->RemoveKey(mICDClientInfo); + error = mpICDClientStorage->SetKey(mICDClientInfo, mNewKey.Span()); + if (error != CHIP_NO_ERROR) + { + ChipLogError(ICD, "Failed to set the new key after re-registration: %" CHIP_ERROR_FORMAT, error.Format()); + mpCheckInDelegate->OnKeyRefreshDone(this, error); + return; + } + + error = mpICDClientStorage->StoreEntry(mICDClientInfo); + if (error != CHIP_NO_ERROR) + { + ChipLogError(ICD, "Failed to store the new key after re-registration: %" CHIP_ERROR_FORMAT, error.Format()); + mpCheckInDelegate->OnKeyRefreshDone(this, error); + return; + } + + mpCheckInDelegate->OnCheckInComplete(mICDClientInfo); + mpCheckInDelegate->OnKeyRefreshDone(this, CHIP_NO_ERROR); + }; + + auto onFailure = [&](CHIP_ERROR error) { + ChipLogError(ICD, "RegisterClient command failed: %" CHIP_ERROR_FORMAT, error.Format()); + mpCheckInDelegate->OnKeyRefreshDone(this, error); + }; + + EndpointId endpointId = 0; + + Clusters::IcdManagement::Commands::RegisterClient::Type registerClientCommand; + registerClientCommand.checkInNodeID = mICDClientInfo.peer_node.GetNodeId(); + registerClientCommand.monitoredSubject = mICDClientInfo.monitored_subject; + registerClientCommand.key = mNewKey.Span(); + return Controller::InvokeCommandRequest(&exchangeMgr, sessionHandle, endpointId, registerClientCommand, onSuccess, onFailure); +} + +CHIP_ERROR RefreshKeySender::EstablishSessionToPeer() +{ + ChipLogProgress(ICD, "Trying to establish a CASE session for re-registering an ICD client"); + auto * caseSessionManager = InteractionModelEngine::GetInstance()->GetCASESessionManager(); + VerifyOrReturnError(caseSessionManager != nullptr, CHIP_ERROR_INVALID_CASE_PARAMETER); + caseSessionManager->FindOrEstablishSession(mICDClientInfo.peer_node, &mOnConnectedCallback, &mOnConnectionFailureCallback); + return CHIP_NO_ERROR; +} + +void RefreshKeySender::HandleDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, + const SessionHandle & sessionHandle) +{ + RefreshKeySender * const _this = static_cast(context); + VerifyOrDie(_this != nullptr); + + CHIP_ERROR err = _this->RegisterClientWithNewKey(exchangeMgr, sessionHandle); + if (CHIP_NO_ERROR != err) + { + ChipLogError(ICD, "Failed to send register client command"); + _this->mpCheckInDelegate->OnKeyRefreshDone(_this, err); + } +} + +void RefreshKeySender::HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR err) +{ + RefreshKeySender * const _this = static_cast(context); + VerifyOrDie(_this != nullptr); + + ChipLogError(ICD, "Failed to establish CASE for re-registration with error '%" CHIP_ERROR_FORMAT "'", err.Format()); + _this->mpCheckInDelegate->OnKeyRefreshDone(_this, err); +} +} // namespace app +} // namespace chip diff --git a/src/app/icd/client/RefreshKeySender.h b/src/app/icd/client/RefreshKeySender.h new file mode 100644 index 00000000000000..a0cf822fa446f9 --- /dev/null +++ b/src/app/icd/client/RefreshKeySender.h @@ -0,0 +1,93 @@ +/* + * + * Copyright (c) 2024 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 "ICDClientInfo.h" +#include "ICDClientStorage.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace app { + +class CheckInDelegate; + +/** + * @brief RefreshKeySender contains all the data and methods needed for key refresh and re-registration of an ICD client. + */ +class RefreshKeySender +{ +public: + typedef Crypto::SensitiveDataBuffer RefreshKeyBuffer; + + RefreshKeySender(CheckInDelegate * checkInDelegate, const ICDClientInfo & icdClientInfo, ICDClientStorage * icdClientStorage, + const RefreshKeyBuffer & refreshKeyBuffer); + + /** + * @brief Sets up a CASE session to the peer for re-registering a client with the peer when a key refresh is required to avoid + * ICD counter rollover. Returns error if we did not even manage to kick off a CASE attempt. + */ + CHIP_ERROR EstablishSessionToPeer(); + +private: + // CASE session callbacks + /** + * @brief Callback received on successfully establishing a CASE session in order to re-register the client with the peer node + * using a new key to avoid counter rollover problems. + * + * @param[in] context - context of the client establishing the CASE session + * @param[in] exchangeMgr - exchange manager to use for the re-registration + * @param[in] sessionHandle - session handle to use for the re-registration + */ + static void HandleDeviceConnected(void * context, Messaging::ExchangeManager & exchangeMgr, + const SessionHandle & sessionHandle); + /** + * @brief Callback received on failure to establish a CASE session in order to re-register the client with the peer node using a + * new key to avoid counter rollover problems. + * + * @param[in] context - context of the client establishing the CASE session + * @param[in] peerId - Scoped Node ID of the peer node + * @param[in] err - failure reason + */ + static void HandleDeviceConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR err); + + /** + * @brief Used to send a re-registration command to the peer using a new key. + * + * @param[in] exchangeMgr - exchange manager to use for the re-registration + * @param[in] sessionHandle - session handle to use for the re-registration + */ + CHIP_ERROR RegisterClientWithNewKey(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle); + + ICDClientInfo mICDClientInfo; + ICDClientStorage * mpICDClientStorage = nullptr; + CheckInDelegate * mpCheckInDelegate = nullptr; + RefreshKeyBuffer mNewKey; + Callback::Callback mOnConnectedCallback; + Callback::Callback mOnConnectionFailureCallback; +}; +} // namespace app +} // namespace chip