Skip to content

Commit

Permalink
[ICD] Refresh key to avoid check-in counter rollover problems. (#31465)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
thivya-amazon and restyled-commits authored Feb 3, 2024
1 parent b820953 commit 849f882
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/app/icd/client/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ source_set("handler") {
"CheckInHandler.h",
"DefaultCheckInDelegate.cpp",
"DefaultCheckInDelegate.h",
"RefreshKeySender.cpp",
"RefreshKeySender.h",
]
public_deps = [
":manager",
Expand Down
37 changes: 37 additions & 0 deletions src/app/icd/client/CheckInDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,13 @@
#pragma once

#include <app/icd/client/ICDClientInfo.h>
#include <app/icd/client/ICDClientStorage.h>

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
Expand All @@ -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
Expand Down
18 changes: 15 additions & 3 deletions src/app/icd/client/CheckInHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
*/

#include <app/InteractionModelTimeout.h>
#include <app/icd/client/CheckInDelegate.h>
#include <app/icd/client/CheckInHandler.h>
#include <app/icd/client/RefreshKeySender.h>

#include <cinttypes>

Expand Down Expand Up @@ -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
{
Expand Down
40 changes: 38 additions & 2 deletions src/app/icd/client/DefaultCheckInDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@
* limitations under the License.
*/

#include "CheckInHandler.h"
#include <app/InteractionModelEngine.h>
#include <app/icd/client/DefaultCheckInDelegate.h>
#include <app/icd/client/RefreshKeySender.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <memory>

namespace chip {
namespace app {
Expand All @@ -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<RefreshKeySender>(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
2 changes: 2 additions & 0 deletions src/app/icd/client/DefaultCheckInDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
116 changes: 116 additions & 0 deletions src/app/icd/client/RefreshKeySender.cpp
Original file line number Diff line number Diff line change
@@ -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 <app-common/zap-generated/cluster-objects.h>
#include <app/CommandPathParams.h>
#include <app/InteractionModelEngine.h>
#include <app/OperationalSessionSetup.h>
#include <memory>

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<RefreshKeySender *>(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<RefreshKeySender *>(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
93 changes: 93 additions & 0 deletions src/app/icd/client/RefreshKeySender.h
Original file line number Diff line number Diff line change
@@ -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 <app-common/zap-generated/cluster-objects.h>
#include <app/CommandSender.h>
#include <app/OperationalSessionSetup.h>

#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPConfig.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/ScopedNodeId.h>
#include <lib/support/CodeUtils.h>
#include <stddef.h>

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<Crypto::kAES_CCM128_Key_Length> 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<OnDeviceConnected> mOnConnectedCallback;
Callback::Callback<OnDeviceConnectionFailure> mOnConnectionFailureCallback;
};
} // namespace app
} // namespace chip

0 comments on commit 849f882

Please sign in to comment.