Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ICD] Refresh key to avoid check-in counter rollover problems. #31465

Merged
merged 16 commits into from
Feb 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
}
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;
bzbarsky-apple marked this conversation as resolved.
Show resolved Hide resolved
}
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);
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
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;
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved
}
yunhanw-google marked this conversation as resolved.
Show resolved Hide resolved

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
Loading