blob: 1ebfe3f4c1a261f4b54fe998fae3c88ced313bf4 [file] [log] [blame]
/*
*
* Copyright (c) 2021-2022 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.
*/
/**
* @file
* This file implements the the CHIP CASE Session object that provides
* APIs for constructing a secure session using a certificate from the device's
* operational credentials.
*
*/
#include <protocols/secure_channel/CASESession.h>
#include <atomic>
#include <inttypes.h>
#include <memory>
#include <string.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/support/CHIPFaultInjection.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/SafeInt.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/TypeTraits.h>
#include <messaging/SessionParameters.h>
#include <platform/PlatformManager.h>
#include <protocols/Protocols.h>
#include <protocols/secure_channel/CASEDestinationId.h>
#include <protocols/secure_channel/PairingSession.h>
#include <protocols/secure_channel/SessionResumptionStorage.h>
#include <protocols/secure_channel/StatusReport.h>
#include <system/SystemClock.h>
#include <tracing/macros.h>
#include <tracing/metric_event.h>
#include <transport/SessionManager.h>
namespace {
// TBEDataTags works for both sigma-2-tbedata and sigma-3-tbedata as they have the same tag numbers for the elements common between
// them.
enum class TBEDataTags : uint8_t
{
kSenderNOC = 1,
kSenderICAC = 2,
kSignature = 3,
kResumptionID = 4,
};
// TBSDataTags works for both sigma-2-tbsdata and sigma-3-tbsdata as they have the same tag numbers for the elements common between
// them.
enum class TBSDataTags : uint8_t
{
kSenderNOC = 1,
kSenderICAC = 2,
kSenderPubKey = 3,
kReceiverPubKey = 4,
};
enum class Sigma1Tags : uint8_t
{
kInitiatorRandom = 1,
kInitiatorSessionId = 2,
kDestinationId = 3,
kInitiatorEphPubKey = 4,
kInitiatorSessionParams = 5,
kResumptionID = 6,
kResume1MIC = 7,
};
enum class Sigma2Tags : uint8_t
{
kResponderRandom = 1,
kResponderSessionId = 2,
kResponderEphPubKey = 3,
kEncrypted2 = 4,
kResponderSessionParams = 5,
};
enum class Sigma2ResumeTags : uint8_t
{
kResumptionID = 1,
kSigma2ResumeMIC = 2,
kResponderSessionID = 3,
kResponderSessionParams = 4,
};
enum class Sigma3Tags : uint8_t
{
kEncrypted3 = 1,
};
// Utility to extract the underlying value of TLV Tag enum classes, used in TLV encoding and parsing.
template <typename Enum>
constexpr chip::TLV::Tag AsTlvContextTag(Enum e)
{
return chip::TLV::ContextTag(chip::to_underlying(e));
}
constexpr size_t kCaseOverheadForFutureTBEData = 128;
} // namespace
namespace chip {
using namespace Crypto;
using namespace Credentials;
using namespace Messaging;
using namespace Encoding;
using namespace Protocols::SecureChannel;
using namespace Tracing;
using namespace TLV;
constexpr uint8_t kKDFSR2Info[] = { 0x53, 0x69, 0x67, 0x6d, 0x61, 0x32 };
constexpr uint8_t kKDFSR3Info[] = { 0x53, 0x69, 0x67, 0x6d, 0x61, 0x33 };
constexpr uint8_t kKDFS1RKeyInfo[] = { 0x53, 0x69, 0x67, 0x6d, 0x61, 0x31, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65 };
constexpr uint8_t kKDFS2RKeyInfo[] = { 0x53, 0x69, 0x67, 0x6d, 0x61, 0x32, 0x5f, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65 };
constexpr uint8_t kResume1MIC_Nonce[] =
/* "NCASE_SigmaS1" */ { 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x53, 0x31 };
constexpr uint8_t kResume2MIC_Nonce[] =
/* "NCASE_SigmaS2" */ { 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x53, 0x32 };
constexpr uint8_t kTBEData2_Nonce[] =
/* "NCASE_Sigma2N" */ { 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x32, 0x4e };
constexpr uint8_t kTBEData3_Nonce[] =
/* "NCASE_Sigma3N" */ { 0x4e, 0x43, 0x41, 0x53, 0x45, 0x5f, 0x53, 0x69, 0x67, 0x6d, 0x61, 0x33, 0x4e };
constexpr size_t kTBEDataNonceLength = sizeof(kTBEData2_Nonce);
static_assert(sizeof(kTBEData2_Nonce) == sizeof(kTBEData3_Nonce), "TBEData2_Nonce and TBEData3_Nonce must be same size");
// Amounts of time to allow for server-side processing of messages.
//
// These timeout values only allow for the server-side processing and assume that any transport-specific
// latency will be added to them.
//
// The session establishment fails if the response is not received within the resulting timeout window,
// which accounts for both transport latency and the server-side latency.
static constexpr ExchangeContext::Timeout kExpectedLowProcessingTime = System::Clock::Seconds16(2);
static constexpr ExchangeContext::Timeout kExpectedSigma1ProcessingTime = kExpectedLowProcessingTime;
static constexpr ExchangeContext::Timeout kExpectedHighProcessingTime = System::Clock::Seconds16(30);
// Helper for managing a session's outstanding work.
// Holds work data which is provided to a scheduled work callback (standalone),
// then (if not canceled) to a scheduled after work callback (on the session).
template <class DATA>
class CASESession::WorkHelper
{
public:
// Work callback, processed in the background via `PlatformManager::ScheduleBackgroundWork`.
// This is a non-member function which does not use the associated session.
// The return value is passed to the after work callback (called afterward).
// Set `cancel` to true if calling the after work callback is not necessary.
typedef CHIP_ERROR (*WorkCallback)(DATA & data, bool & cancel);
// After work callback, processed in the main Matter task via `PlatformManager::ScheduleWork`.
// This is a member function to be called on the associated session after the work callback.
// The `status` value is the result of the work callback (called beforehand), or the status of
// queueing the after work callback back to the Matter thread, if the work callback succeeds
// but queueing fails.
//
// When this callback is called asynchronously (i.e. via ScheduleWork), the helper guarantees
// that it will keep itself (and hence `data`) alive until the callback completes.
typedef CHIP_ERROR (CASESession::*AfterWorkCallback)(DATA & data, CHIP_ERROR status);
public:
// Create a work helper using the specified session, work callback, after work callback, and data (template arg).
// Lifetime is managed by sharing between the caller (typically the session) and the helper itself (while work is scheduled).
static Platform::SharedPtr<WorkHelper> Create(CASESession & session, WorkCallback workCallback,
AfterWorkCallback afterWorkCallback)
{
struct EnableShared : public WorkHelper
{
EnableShared(CASESession & session, WorkCallback workCallback, AfterWorkCallback afterWorkCallback) :
WorkHelper(session, workCallback, afterWorkCallback)
{}
};
auto ptr = Platform::MakeShared<EnableShared>(session, workCallback, afterWorkCallback);
if (ptr)
{
ptr->mWeakPtr = ptr; // used by `ScheduleWork`
}
return ptr;
}
// Do the work immediately.
// No scheduling, no outstanding work, no shared lifetime management.
//
// The caller must guarantee that it keeps the helper alive across this call, most likely by
// holding a reference to it on the stack.
CHIP_ERROR DoWork()
{
// Ensure that this function is being called from main Matter thread
assertChipStackLockedByCurrentThread();
VerifyOrReturnError(mSession && mWorkCallback && mAfterWorkCallback, CHIP_ERROR_INCORRECT_STATE);
auto * helper = this;
bool cancel = false;
helper->mStatus = helper->mWorkCallback(helper->mData, cancel);
if (!cancel)
{
helper->mStatus = (helper->mSession->*(helper->mAfterWorkCallback))(helper->mData, helper->mStatus);
}
return helper->mStatus;
}
// Schedule the work for later execution.
// If lifetime is managed, the helper shares management while work is outstanding.
CHIP_ERROR ScheduleWork()
{
VerifyOrReturnError(mSession && mWorkCallback && mAfterWorkCallback, CHIP_ERROR_INCORRECT_STATE);
// Hold strong ptr while work is outstanding
mStrongPtr = mWeakPtr.lock(); // set in `Create`
auto status = DeviceLayer::PlatformMgr().ScheduleBackgroundWork(WorkHandler, reinterpret_cast<intptr_t>(this));
if (status != CHIP_NO_ERROR)
{
// Release strong ptr since scheduling failed.
mStrongPtr.reset();
}
return status;
}
// Cancel the work, by clearing the associated session.
void CancelWork() { mSession.store(nullptr); }
bool IsCancelled() const { return mSession.load() == nullptr; }
// This API returns true when background thread fails to schedule the AfterWorkCallback
bool UnableToScheduleAfterWorkCallback() { return mScheduleAfterWorkFailed.load(); }
// Do after work immediately.
// No scheduling, no outstanding work, no shared lifetime management.
void DoAfterWork()
{
VerifyOrDie(UnableToScheduleAfterWorkCallback());
AfterWorkHandler(reinterpret_cast<intptr_t>(this));
}
private:
// Create a work helper using the specified session, work callback, after work callback, and data (template arg).
// Lifetime is not managed, see `Create` for that option.
WorkHelper(CASESession & session, WorkCallback workCallback, AfterWorkCallback afterWorkCallback) :
mSession(&session), mWorkCallback(workCallback), mAfterWorkCallback(afterWorkCallback)
{}
// Handler for the work callback.
static void WorkHandler(intptr_t arg)
{
auto * helper = reinterpret_cast<WorkHelper *>(arg);
// Hold strong ptr while work is handled
auto strongPtr(std::move(helper->mStrongPtr));
VerifyOrReturn(!helper->IsCancelled());
bool cancel = false;
// Execute callback in background thread; data must be OK with this
helper->mStatus = helper->mWorkCallback(helper->mData, cancel);
VerifyOrReturn(!cancel && !helper->IsCancelled());
// Hold strong ptr to ourselves while work is outstanding
helper->mStrongPtr.swap(strongPtr);
auto status = DeviceLayer::PlatformMgr().ScheduleWork(AfterWorkHandler, reinterpret_cast<intptr_t>(helper));
if (status != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Failed to Schedule the AfterWorkCallback on foreground thread: %" CHIP_ERROR_FORMAT,
status.Format());
// We failed to schedule after work callback, so setting mScheduleAfterWorkFailed flag to true
// This can be checked from foreground thread and after work callback can be retried
helper->mStatus = status;
// Release strong ptr to self since scheduling failed, because nothing guarantees
// that AfterWorkHandler will get called at this point to release the reference,
// and we don't want to leak. That said, we want to ensure that "helper" stays
// alive through the end of this function (so we can set mScheduleAfterWorkFailed
// on it), but also want to avoid racing on the single SharedPtr instance in
// helper->mStrongPtr. That means we need to not touch helper->mStrongPtr after
// writing to mScheduleAfterWorkFailed.
//
// The simplest way to do this is to move the reference in helper->mStrongPtr to
// our stack, where it outlives all our accesses to "helper".
strongPtr.swap(helper->mStrongPtr);
// helper and any of its state should not be touched after storing mScheduleAfterWorkFailed.
helper->mScheduleAfterWorkFailed.store(true);
}
}
// Handler for the after work callback.
static void AfterWorkHandler(intptr_t arg)
{
// Ensure that this function is being called from main Matter thread
assertChipStackLockedByCurrentThread();
auto * helper = reinterpret_cast<WorkHelper *>(arg);
// Hold strong ptr while work is handled, and ensure that helper->mStrongPtr does not keep
// holding a reference.
auto strongPtr(std::move(helper->mStrongPtr));
if (!strongPtr)
{
// This can happen if scheduling AfterWorkHandler failed. Just grab a strong ref
// to handler directly, to fulfill our API contract of holding a strong reference
// across the after-work callback. At this point, we are guaranteed that the
// background thread is not touching the helper anymore.
strongPtr = helper->mWeakPtr.lock();
}
if (auto * session = helper->mSession.load())
{
// Execute callback in Matter thread; session should be OK with this
(session->*(helper->mAfterWorkCallback))(helper->mData, helper->mStatus);
}
}
private:
// Lifetime management: `ScheduleWork` sets `mStrongPtr` from `mWeakPtr`.
Platform::WeakPtr<WorkHelper> mWeakPtr;
// Lifetime management: `ScheduleWork` sets `mStrongPtr` from `mWeakPtr`.
Platform::SharedPtr<WorkHelper> mStrongPtr;
// Associated session, cleared by `CancelWork`.
std::atomic<CASESession *> mSession;
// Work callback, called by `WorkHandler`.
WorkCallback mWorkCallback;
// After work callback, called by `AfterWorkHandler`.
AfterWorkCallback mAfterWorkCallback;
// Return value of `mWorkCallback`, passed to `mAfterWorkCallback`.
CHIP_ERROR mStatus;
// If background thread fails to schedule AfterWorkCallback then this flag is set to true
// and CASEServer then can check this one and run the AfterWorkCallback for us.
//
// When this happens, the write to this boolean _must_ be the last code that touches this
// object on the background thread. After that, the Matter thread owns the object.
std::atomic<bool> mScheduleAfterWorkFailed{ false };
public:
// Data passed to `mWorkCallback` and `mAfterWorkCallback`.
DATA mData;
};
CASESession::~CASESession()
{
// Let's clear out any security state stored in the object, before destroying it.
Clear();
}
void CASESession::OnSessionReleased()
{
// Call into our super-class before we clear our state.
PairingSession::OnSessionReleased();
Clear();
}
void CASESession::Clear()
{
MATTER_TRACE_SCOPE("Clear", "CASESession");
// Cancel any outstanding work.
if (mSendSigma3Helper)
{
mSendSigma3Helper->CancelWork();
mSendSigma3Helper.reset();
}
if (mHandleSigma3Helper)
{
mHandleSigma3Helper->CancelWork();
mHandleSigma3Helper.reset();
}
// This function zeroes out and resets the memory used by the object.
// It's done so that no security related information will be leaked.
mCommissioningHash.Clear();
PairingSession::Clear();
mState = State::kInitialized;
Crypto::ClearSecretData(mIPK);
if (mFabricsTable != nullptr)
{
mFabricsTable->RemoveFabricDelegate(this);
mFabricsTable->ReleaseEphemeralKeypair(mEphemeralKey);
mEphemeralKey = nullptr;
}
mLocalNodeId = kUndefinedNodeId;
mPeerNodeId = kUndefinedNodeId;
mFabricsTable = nullptr;
mFabricIndex = kUndefinedFabricIndex;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
// Clear the context object.
mTCPConnCbCtxt.appContext = nullptr;
mTCPConnCbCtxt.connCompleteCb = nullptr;
mTCPConnCbCtxt.connClosedCb = nullptr;
mTCPConnCbCtxt.connReceivedCb = nullptr;
if (mPeerConnState)
{
// Set the app state callback object in the Connection state to null
// to prevent any dangling pointer to memory(mTCPConnCbCtxt) owned
// by the CASESession object, that is now getting cleared.
mPeerConnState->mAppState = nullptr;
if (mPeerConnState->mConnectionState != Transport::TCPState::kConnected)
{
// Abort the connection if the CASESession is being destroyed and the
// connection is in the middle of being set up.
mSessionManager->TCPDisconnect(mPeerConnState, /* shouldAbort = */ true);
mPeerConnState = nullptr;
}
}
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
}
void CASESession::InvalidateIfPendingEstablishmentOnFabric(FabricIndex fabricIndex)
{
if (mFabricIndex != fabricIndex)
{
return;
}
if (!IsSessionEstablishmentInProgress())
{
return;
}
AbortPendingEstablish(CHIP_ERROR_CANCELLED);
}
CHIP_ERROR CASESession::Init(SessionManager & sessionManager, Credentials::CertificateValidityPolicy * policy,
SessionEstablishmentDelegate * delegate, const ScopedNodeId & sessionEvictionHint)
{
MATTER_TRACE_SCOPE("Init", "CASESession");
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(mGroupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(sessionManager.GetSessionKeystore() != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
Clear();
ReturnErrorOnFailure(mCommissioningHash.Begin());
mDelegate = delegate;
mSessionManager = &sessionManager;
ReturnErrorOnFailure(AllocateSecureSession(sessionManager, sessionEvictionHint));
mValidContext.Reset();
mValidContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature);
mValidContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth);
mValidContext.mValidityPolicy = policy;
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
mTCPConnCbCtxt.appContext = this;
mTCPConnCbCtxt.connCompleteCb = HandleConnectionAttemptComplete;
mTCPConnCbCtxt.connClosedCb = HandleConnectionClosed;
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
return CHIP_NO_ERROR;
}
CHIP_ERROR
CASESession::PrepareForSessionEstablishment(SessionManager & sessionManager, FabricTable * fabricTable,
SessionResumptionStorage * sessionResumptionStorage,
Credentials::CertificateValidityPolicy * policy,
SessionEstablishmentDelegate * delegate, const ScopedNodeId & previouslyEstablishedPeer,
Optional<ReliableMessageProtocolConfig> mrpLocalConfig)
{
MATTER_TRACE_SCOPE("PrepareForSessionEstablishment", "CASESession");
// Below VerifyOrReturnError is not SuccessOrExit since we only want to goto `exit:` after
// Init has been successfully called.
VerifyOrReturnError(fabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(Init(sessionManager, policy, delegate, previouslyEstablishedPeer));
CHIP_ERROR err = CHIP_NO_ERROR;
SuccessOrExit(err = fabricTable->AddFabricDelegate(this));
mFabricsTable = fabricTable;
mRole = CryptoContext::SessionRole::kResponder;
mSessionResumptionStorage = sessionResumptionStorage;
mLocalMRPConfig = MakeOptional(mrpLocalConfig.ValueOr(GetDefaultMRPConfig()));
ChipLogDetail(SecureChannel, "Allocated SecureSession (%p) - waiting for Sigma1 msg",
mSecureSessionHolder.Get().Value()->AsSecureSession());
exit:
if (err != CHIP_NO_ERROR)
{
Clear();
}
return err;
}
CHIP_ERROR CASESession::EstablishSession(SessionManager & sessionManager, FabricTable * fabricTable, ScopedNodeId peerScopedNodeId,
ExchangeContext * exchangeCtxt, SessionResumptionStorage * sessionResumptionStorage,
Credentials::CertificateValidityPolicy * policy, SessionEstablishmentDelegate * delegate,
Optional<ReliableMessageProtocolConfig> mrpLocalConfig)
{
MATTER_TRACE_SCOPE("EstablishSession", "CASESession");
CHIP_ERROR err = CHIP_NO_ERROR;
// Return early on error here, as we have not initialized any state yet
VerifyOrReturnErrorWithMetric(kMetricDeviceCASESession, exchangeCtxt != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnErrorWithMetric(kMetricDeviceCASESession, fabricTable != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// Use FabricTable directly to avoid situation of dangling index from stale FabricInfo
// until we factor-out any FabricInfo direct usage.
VerifyOrReturnErrorWithMetric(kMetricDeviceCASESession, peerScopedNodeId.GetFabricIndex() != kUndefinedFabricIndex,
CHIP_ERROR_INVALID_ARGUMENT);
const auto * fabricInfo = fabricTable->FindFabricWithIndex(peerScopedNodeId.GetFabricIndex());
VerifyOrReturnErrorWithMetric(kMetricDeviceCASESession, fabricInfo != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
err = Init(sessionManager, policy, delegate, peerScopedNodeId);
mRole = CryptoContext::SessionRole::kInitiator;
// We are setting the exchange context specifically before checking for error.
// This is to make sure the exchange will get closed if Init() returned an error.
mExchangeCtxt.Emplace(*exchangeCtxt);
Transport::PeerAddress peerAddress = mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->GetPeerAddress();
// From here onwards, let's go to exit on error, as some state might have already
// been initialized
SuccessOrExitWithMetric(kMetricDeviceCASESession, err);
SuccessOrExitWithMetric(kMetricDeviceCASESession, err = fabricTable->AddFabricDelegate(this));
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCASESession);
// Set the PeerAddress in the secure session up front to indicate the
// Transport Type of the session that is being set up.
mSecureSessionHolder->AsSecureSession()->SetPeerAddress(peerAddress);
mFabricsTable = fabricTable;
mFabricIndex = fabricInfo->GetFabricIndex();
mSessionResumptionStorage = sessionResumptionStorage;
mLocalMRPConfig = MakeOptional(mrpLocalConfig.ValueOr(GetDefaultMRPConfig()));
mExchangeCtxt.Value()->UseSuggestedResponseTimeout(kExpectedSigma1ProcessingTime);
mPeerNodeId = peerScopedNodeId.GetNodeId();
mLocalNodeId = fabricInfo->GetNodeId();
ChipLogProgress(SecureChannel, "Initiating session on local FabricIndex %u from 0x" ChipLogFormatX64 " -> 0x" ChipLogFormatX64,
static_cast<unsigned>(mFabricIndex), ChipLogValueX64(mLocalNodeId), ChipLogValueX64(mPeerNodeId));
if (peerAddress.GetTransportType() == Transport::Type::kTcp)
{
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
err = sessionManager.TCPConnect(peerAddress, &mTCPConnCbCtxt, &mPeerConnState);
SuccessOrExit(err);
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
}
else
{
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCASESessionSigma1);
err = SendSigma1();
SuccessOrExit(err);
}
exit:
if (err != CHIP_NO_ERROR)
{
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma1, err);
MATTER_LOG_METRIC_END(kMetricDeviceCASESession, err);
Clear();
}
return err;
}
void CASESession::OnResponseTimeout(ExchangeContext * ec)
{
MATTER_TRACE_SCOPE("OnResponseTimeout", "CASESession");
VerifyOrReturn(ec != nullptr, ChipLogError(SecureChannel, "CASESession::OnResponseTimeout was called by null exchange"));
VerifyOrReturn(mExchangeCtxt.HasValue() && (&mExchangeCtxt.Value().Get() == ec),
ChipLogError(SecureChannel, "CASESession::OnResponseTimeout exchange doesn't match"));
ChipLogError(SecureChannel,
"CASESession timed out while waiting for a response from peer " ChipLogFormatScopedNodeId ". Current state was %u",
ChipLogValueScopedNodeId(GetPeer()), to_underlying(mState));
MATTER_TRACE_COUNTER("CASETimeout");
// Discard the exchange so that Clear() doesn't try aborting it. The
// exchange will handle that.
DiscardExchange();
AbortPendingEstablish(CHIP_ERROR_TIMEOUT);
}
void CASESession::AbortPendingEstablish(CHIP_ERROR err)
{
MATTER_LOG_METRIC_END(kMetricDeviceCASESession, err);
MATTER_TRACE_SCOPE("AbortPendingEstablish", "CASESession");
// This needs to come before Clear() which will reset mState.
SessionEstablishmentStage state = MapCASEStateToSessionEstablishmentStage(mState);
Clear();
// Do this last in case the delegate frees us.
NotifySessionEstablishmentError(err, state);
}
CHIP_ERROR CASESession::DeriveSecureSession(CryptoContext & session)
{
switch (mState)
{
case State::kFinished: {
std::array<uint8_t, sizeof(mIPK) + kSHA256_Hash_Length> msg_salt;
{
Encoding::LittleEndian::BufferWriter bbuf(msg_salt);
bbuf.Put(mIPK, sizeof(mIPK));
bbuf.Put(mMessageDigest, sizeof(mMessageDigest));
VerifyOrReturnError(bbuf.Fit(), CHIP_ERROR_BUFFER_TOO_SMALL);
}
ReturnErrorOnFailure(session.InitFromSecret(*mSessionManager->GetSessionKeystore(), mSharedSecret.Span(),
ByteSpan(msg_salt), CryptoContext::SessionInfoType::kSessionEstablishment,
mRole));
return CHIP_NO_ERROR;
}
case State::kFinishedViaResume: {
std::array<uint8_t, sizeof(mInitiatorRandom) + decltype(mResumeResumptionId)().size()> msg_salt;
{
Encoding::LittleEndian::BufferWriter bbuf(msg_salt);
bbuf.Put(mInitiatorRandom, sizeof(mInitiatorRandom));
bbuf.Put(mResumeResumptionId.data(), mResumeResumptionId.size());
VerifyOrReturnError(bbuf.Fit(), CHIP_ERROR_BUFFER_TOO_SMALL);
}
ReturnErrorOnFailure(session.InitFromSecret(*mSessionManager->GetSessionKeystore(), mSharedSecret.Span(),
ByteSpan(msg_salt), CryptoContext::SessionInfoType::kSessionResumption, mRole));
return CHIP_NO_ERROR;
}
default:
return CHIP_ERROR_INCORRECT_STATE;
}
}
CHIP_ERROR CASESession::RecoverInitiatorIpk()
{
Credentials::GroupDataProvider::KeySet ipkKeySet;
CHIP_ERROR err = mGroupDataProvider->GetIpkKeySet(mFabricIndex, ipkKeySet);
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Failed to obtain IPK for initiating: %" CHIP_ERROR_FORMAT, err.Format());
return err;
}
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));
// Leaving this logging code for debug, but this cannot be enabled at runtime
// since it leaks private security material.
#if 0
ChipLogProgress(SecureChannel, "RecoverInitiatorIpk: GroupDataProvider %p, Got IPK for FabricIndex %u", mGroupDataProvider,
static_cast<unsigned>(mFabricIndex));
ChipLogByteSpan(SecureChannel, ByteSpan(mIPK));
#endif
return CHIP_NO_ERROR;
}
#if INET_CONFIG_ENABLE_TCP_ENDPOINT
void CASESession::HandleConnectionAttemptComplete(Transport::ActiveTCPConnectionState * conn, CHIP_ERROR err)
{
VerifyOrReturn(conn != nullptr);
// conn->mAppState should not be NULL. SessionManager has already checked
// before calling this callback.
VerifyOrDie(conn->mAppState != nullptr);
char peerAddrBuf[chip::Transport::PeerAddress::kMaxToStringSize];
conn->mPeerAddr.ToString(peerAddrBuf);
CASESession * caseSession = reinterpret_cast<CASESession *>(conn->mAppState->appContext);
VerifyOrReturn(caseSession != nullptr);
// Exit and disconnect if connection setup encountered an error.
SuccessOrExit(err);
ChipLogDetail(SecureChannel, "TCP Connection established with %s before session establishment", peerAddrBuf);
// Associate the connection with the current unauthenticated session for the
// CASE exchange.
caseSession->mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->SetTCPConnection(conn);
// Associate the connection with the current secure session that is being
// set up.
caseSession->mSecureSessionHolder.Get().Value()->AsSecureSession()->SetTCPConnection(conn);
// Send Sigma1 after connection is established for sessions over TCP
err = caseSession->SendSigma1();
SuccessOrExit(err);
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Connection establishment failed with peer at %s: %" CHIP_ERROR_FORMAT, peerAddrBuf,
err.Format());
// Close the underlying connection and ensure that the CASESession is
// not holding on to a stale ActiveTCPConnectionState. We call
// TCPDisconnect() here explicitly in order to abort the connection
// even after it establishes successfully, but SendSigma1() fails for
// some reason.
caseSession->mSessionManager->TCPDisconnect(conn, /* shouldAbort = */ true);
caseSession->mPeerConnState = nullptr;
caseSession->Clear();
}
}
void CASESession::HandleConnectionClosed(Transport::ActiveTCPConnectionState * conn, CHIP_ERROR conErr)
{
VerifyOrReturn(conn != nullptr);
// conn->mAppState should not be NULL. SessionManager has already checked
// before calling this callback.
VerifyOrDie(conn->mAppState != nullptr);
CASESession * caseSession = reinterpret_cast<CASESession *>(conn->mAppState->appContext);
VerifyOrReturn(caseSession != nullptr);
// Drop our pointer to the now-invalid connection state.
//
// Since the connection is closed, message sends over the ExchangeContext
// will just fail and be handled like normal send errors.
//
// Additionally, SessionManager notifies (via ExchangeMgr) all ExchangeContexts on the
// connection closures for the attached sessions and the ExchangeContexts
// can close proactively if that's appropriate.
caseSession->mPeerConnState = nullptr;
ChipLogDetail(SecureChannel, "TCP Connection for this session has closed");
}
#endif // INET_CONFIG_ENABLE_TCP_ENDPOINT
CHIP_ERROR CASESession::SendSigma1()
{
MATTER_TRACE_SCOPE("SendSigma1", "CASESession");
uint8_t destinationIdentifier[kSHA256_Hash_Length] = { 0 };
// Struct that will be used as input to EncodeSigma1() method
EncodeSigma1Inputs encodeSigma1Inputs;
// Lookup fabric info.
const auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE);
// Validate that we have a session ID allocated.
VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE);
encodeSigma1Inputs.initiatorSessionId = GetLocalSessionId().Value();
// Generate an ephemeral keypair
mEphemeralKey = mFabricsTable->AllocateEphemeralKeypairForCASE();
VerifyOrReturnError(mEphemeralKey != nullptr, CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(mEphemeralKey->Initialize(ECPKeyTarget::ECDH));
encodeSigma1Inputs.initiatorEphPubKey = &mEphemeralKey->Pubkey();
// Fill in the random value
ReturnErrorOnFailure(DRBG_get_bytes(mInitiatorRandom, sizeof(mInitiatorRandom)));
encodeSigma1Inputs.initiatorRandom = ByteSpan(mInitiatorRandom);
// Generate a Destination Identifier based on the node we are attempting to reach
{
// Obtain originator IPK matching the fabric where we are trying to open a session. mIPK
// will be properly set thereafter.
ReturnErrorOnFailure(RecoverInitiatorIpk());
FabricId fabricId = fabricInfo->GetFabricId();
Crypto::P256PublicKey rootPubKey;
ReturnErrorOnFailure(mFabricsTable->FetchRootPubkey(mFabricIndex, rootPubKey));
Credentials::P256PublicKeySpan rootPubKeySpan{ rootPubKey.ConstBytes() };
MutableByteSpan destinationIdSpan(destinationIdentifier);
ReturnErrorOnFailure(GenerateCaseDestinationId(ByteSpan(mIPK), encodeSigma1Inputs.initiatorRandom, rootPubKeySpan, fabricId,
mPeerNodeId, destinationIdSpan));
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptDestinationID, destinationIdentifier[0] ^= 0xFF);
encodeSigma1Inputs.destinationId = destinationIdSpan;
}
VerifyOrReturnError(mLocalMRPConfig.HasValue(), CHIP_ERROR_INCORRECT_STATE);
encodeSigma1Inputs.initiatorMrpConfig = &mLocalMRPConfig.Value();
// Try to find persistent session, and resume it.
if (mSessionResumptionStorage != nullptr)
{
CHIP_ERROR err = mSessionResumptionStorage->FindByScopedNodeId(fabricInfo->GetScopedNodeIdForNode(mPeerNodeId),
mResumeResumptionId, mSharedSecret, mPeerCATs);
if (err == CHIP_NO_ERROR)
{
// Found valid resumption state, try to resume the session.
encodeSigma1Inputs.resumptionId = mResumeResumptionId;
MutableByteSpan resumeMICSpan(encodeSigma1Inputs.initiatorResume1MICBuffer);
ReturnErrorOnFailure(GenerateSigmaResumeMIC(encodeSigma1Inputs.initiatorRandom, encodeSigma1Inputs.resumptionId,
ByteSpan(kKDFS1RKeyInfo), ByteSpan(kResume1MIC_Nonce), resumeMICSpan));
encodeSigma1Inputs.initiatorResumeMIC = resumeMICSpan;
encodeSigma1Inputs.sessionResumptionRequested = true;
}
}
System::PacketBufferHandle msgR1;
// Encode Sigma1 in CHIP TLV Format
ReturnErrorOnFailure(EncodeSigma1(msgR1, encodeSigma1Inputs));
ReturnErrorOnFailure(mCommissioningHash.AddData(ByteSpan{ msgR1->Start(), msgR1->DataLength() }));
// Call delegate to send the msg to peer
ReturnErrorOnFailure(mExchangeCtxt.Value()->SendMessage(Protocols::SecureChannel::MsgType::CASE_Sigma1, std::move(msgR1),
SendFlags(SendMessageFlags::kExpectResponse)));
if (encodeSigma1Inputs.sessionResumptionRequested)
{
mState = State::kSentSigma1Resume;
// Flags that Resume is being attempted
MATTER_LOG_METRIC(kMetricDeviceCASESessionSigma1Resume);
}
else
{
mState = State::kSentSigma1;
}
#if CHIP_PROGRESS_LOGGING
const auto localMRPConfig = mLocalMRPConfig.Value();
#endif // CHIP_PROGRESS_LOGGING
ChipLogProgress(SecureChannel, "Sent Sigma1 msg to " ChipLogFormatScopedNodeId " [II:%" PRIu32 "ms AI:%" PRIu32 "ms AT:%ums]",
ChipLogValueScopedNodeId(GetPeer()), localMRPConfig.mIdleRetransTimeout.count(),
localMRPConfig.mActiveRetransTimeout.count(), localMRPConfig.mActiveThresholdTime.count());
mDelegate->OnSessionEstablishmentStarted();
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::EncodeSigma1(System::PacketBufferHandle & msg, EncodeSigma1Inputs & input)
{
MATTER_TRACE_SCOPE("EncodeSigma1", "CASESession");
VerifyOrReturnError(input.initiatorEphPubKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
size_t dataLen = EstimateStructOverhead(kSigmaParamRandomNumberSize, // initiatorRandom
sizeof(uint16_t), // initiatorSessionId,
kSHA256_Hash_Length, // destinationId
kP256_PublicKey_Length, // InitiatorEphPubKey,
SessionParameters::kEstimatedTLVSize, // initiatorSessionParams
SessionResumptionStorage::kResumptionIdSize, // resumptionId
CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES // initiatorResumeMIC
);
msg = System::PacketBufferHandle::New(dataLen);
VerifyOrReturnError(!msg.IsNull(), CHIP_ERROR_NO_MEMORY);
System::PacketBufferTLVWriter tlvWriter;
tlvWriter.Init(std::move(msg));
TLVType outerContainerType = kTLVType_NotSpecified;
ReturnErrorOnFailure(tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma1Tags::kInitiatorRandom), input.initiatorRandom));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma1Tags::kInitiatorSessionId), input.initiatorSessionId));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma1Tags::kDestinationId), input.destinationId));
ReturnErrorOnFailure(tlvWriter.PutBytes(AsTlvContextTag(Sigma1Tags::kInitiatorEphPubKey), *input.initiatorEphPubKey,
static_cast<uint32_t>(input.initiatorEphPubKey->Length())));
VerifyOrReturnError(input.initiatorMrpConfig != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
ReturnErrorOnFailure(
EncodeSessionParameters(AsTlvContextTag(Sigma1Tags::kInitiatorSessionParams), *input.initiatorMrpConfig, tlvWriter));
if (input.sessionResumptionRequested)
{
bool testOnlySkipResumptionID = false;
bool testOnlySkipInitiatorResumeMIC = false;
CHIP_FAULT_INJECT(FaultInjection::kFault_CASESkipResumptionID, testOnlySkipResumptionID = true);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASESkipInitiatorResumeMIC, testOnlySkipInitiatorResumeMIC = true);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptInitiatorResumeMIC, input.initiatorResume1MICBuffer[0] ^= 0xFF);
if (!testOnlySkipResumptionID)
{
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma1Tags::kResumptionID), input.resumptionId));
}
if (!testOnlySkipInitiatorResumeMIC)
{
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma1Tags::kResume1MIC), input.initiatorResumeMIC));
}
}
ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize(&msg));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::HandleSigma1_and_SendSigma2(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma1_and_SendSigma2", "CASESession");
CHIP_ERROR err = CHIP_NO_ERROR;
// Parse and Validate Received Sigma1, and decide next step
NextStep nextStep = HandleSigma1(std::move(msg));
VerifyOrExit(nextStep.Is<Step>(), err = nextStep.Get<CHIP_ERROR>());
switch (nextStep.Get<Step>())
{
case Step::kSendSigma2: {
System::PacketBufferHandle msgR2;
EncodeSigma2Inputs encodeSigma2;
SuccessOrExit(err = PrepareSigma2(encodeSigma2));
SuccessOrExit(err = EncodeSigma2(msgR2, encodeSigma2));
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCASESessionSigma2);
SuccessOrExitAction(err = SendSigma2(std::move(msgR2)), MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma2, err));
mDelegate->OnSessionEstablishmentStarted();
break;
}
case Step::kSendSigma2Resume: {
System::PacketBufferHandle msgR2Resume;
EncodeSigma2ResumeInputs encodeSigma2Resume;
SuccessOrExit(err = PrepareSigma2Resume(encodeSigma2Resume));
SuccessOrExit(err = EncodeSigma2Resume(msgR2Resume, encodeSigma2Resume));
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCASESessionSigma2Resume);
SuccessOrExitAction(err = SendSigma2Resume(std::move(msgR2Resume)),
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma2Resume, err));
mDelegate->OnSessionEstablishmentStarted();
break;
}
default:
break;
}
exit:
if (err == CHIP_ERROR_KEY_NOT_FOUND)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeNoSharedRoot);
mState = State::kInitialized;
}
else if (err != CHIP_NO_ERROR)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
mState = State::kInitialized;
}
return err;
}
CHIP_ERROR CASESession::FindLocalNodeFromDestinationId(const ByteSpan & destinationId, const ByteSpan & initiatorRandom)
{
MATTER_TRACE_SCOPE("FindLocalNodeFromDestinationId", "CASESession");
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();
Crypto::P256PublicKey rootPubKey;
ReturnErrorOnFailure(mFabricsTable->FetchRootPubkey(fabricInfo.GetFabricIndex(), rootPubKey));
Credentials::P256PublicKeySpan rootPubKeySpan{ rootPubKey.ConstBytes() };
// 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(candidateIpkSpan, 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);
mFabricIndex = fabricInfo.GetFabricIndex();
mLocalNodeId = nodeId;
break;
}
}
if (found)
{
break;
}
}
return found ? CHIP_NO_ERROR : CHIP_ERROR_KEY_NOT_FOUND;
}
CHIP_ERROR CASESession::TryResumeSession(SessionResumptionStorage::ConstResumptionIdView resumptionId, ByteSpan resume1MIC,
ByteSpan initiatorRandom)
{
MATTER_TRACE_SCOPE("TryResumeSession", "CASESession");
VerifyOrReturnError(mSessionResumptionStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
SessionResumptionStorage::ConstResumptionIdView resumptionIdSpan(resumptionId);
ScopedNodeId node;
ReturnErrorOnFailure(mSessionResumptionStorage->FindByResumptionId(resumptionIdSpan, node, mSharedSecret, mPeerCATs));
// Cross check resume1MIC with the shared secret
ReturnErrorOnFailure(
ValidateSigmaResumeMIC(resume1MIC, initiatorRandom, resumptionId, ByteSpan(kKDFS1RKeyInfo), ByteSpan(kResume1MIC_Nonce)));
const auto * fabricInfo = mFabricsTable->FindFabricWithIndex(node.GetFabricIndex());
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE);
mFabricIndex = node.GetFabricIndex();
mPeerNodeId = node.GetNodeId();
mLocalNodeId = fabricInfo->GetNodeId();
return CHIP_NO_ERROR;
}
CASESession::NextStep CASESession::HandleSigma1(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma1", "CASESession");
ChipLogProgress(SecureChannel, "Received Sigma1 msg");
MATTER_TRACE_COUNTER("Sigma1");
VerifyOrReturnError(mFabricsTable != nullptr, NextStep::Create<CHIP_ERROR>(CHIP_ERROR_INCORRECT_STATE));
ReturnErrorVariantOnFailure(NextStep, mCommissioningHash.AddData(ByteSpan{ msg->Start(), msg->DataLength() }));
System::PacketBufferTLVReader tlvReader;
tlvReader.Init(std::move(msg));
// Struct that will serve as output in ParseSigma1
ParsedSigma1 parsedSigma1;
ReturnErrorVariantOnFailure(NextStep, ParseSigma1(tlvReader, parsedSigma1));
ChipLogDetail(SecureChannel, "Peer (Initiator) assigned session ID %d", parsedSigma1.initiatorSessionId);
SetPeerSessionId(parsedSigma1.initiatorSessionId);
// Set the Session parameters provided in the Sigma1 message
if (parsedSigma1.initiatorSessionParamStructPresent)
{
SetRemoteSessionParameters(parsedSigma1.initiatorSessionParams);
mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->SetRemoteSessionParameters(
GetRemoteSessionParameters());
}
if (parsedSigma1.sessionResumptionRequested &&
parsedSigma1.resumptionId.size() == SessionResumptionStorage::kResumptionIdSize &&
CHIP_NO_ERROR ==
TryResumeSession(SessionResumptionStorage::ConstResumptionIdView(parsedSigma1.resumptionId.data()),
parsedSigma1.initiatorResumeMIC, parsedSigma1.initiatorRandom))
{
std::copy(parsedSigma1.initiatorRandom.begin(), parsedSigma1.initiatorRandom.end(), mInitiatorRandom);
std::copy(parsedSigma1.resumptionId.begin(), parsedSigma1.resumptionId.end(), mResumeResumptionId.begin());
// Early returning here, since the next Step is known to be Sigma2Resume, and no further processing is needed for the
// Sigma1 message
return NextStep::Create<Step>(Step::kSendSigma2Resume);
}
// ParseSigma1 ensures that:
// mRemotePubKey.Length() == initiatorPubKey.size() == kP256_PublicKey_Length.
memcpy(mRemotePubKey.Bytes(), parsedSigma1.initiatorEphPubKey.data(), mRemotePubKey.Length());
CHIP_ERROR err = CHIP_NO_ERROR;
// Attempt to match the initiator's desired destination based on local fabric table.
err = FindLocalNodeFromDestinationId(parsedSigma1.destinationId, parsedSigma1.initiatorRandom);
if (err == CHIP_NO_ERROR)
{
ChipLogProgress(SecureChannel, "CASE matched destination ID: fabricIndex %u, NodeID 0x" ChipLogFormatX64,
static_cast<unsigned>(mFabricIndex), ChipLogValueX64(mLocalNodeId));
// Side-effect of FindLocalNodeFromDestinationId success was that mFabricIndex/mLocalNodeId are now
// set to the local fabric and associated NodeId that was targeted by the initiator.
return NextStep::Create<Step>(Step::kSendSigma2);
}
ChipLogError(SecureChannel, "CASE failed to match destination ID with local fabrics");
ChipLogByteSpan(SecureChannel, parsedSigma1.destinationId);
// FindLocalNodeFromDestinationId returns CHIP_ERROR_KEY_NOT_FOUND if Sigma1's DestinationId does not match any
// candidateDestinationId, this will trigger a status Report with ProtocolCode = NoSharedTrustRoots.
// Returning a CHIP_ERROR variant that will trigger a corresponding Status Report.
return NextStep::Create<CHIP_ERROR>(err);
}
CHIP_ERROR CASESession::PrepareSigma2Resume(EncodeSigma2ResumeInputs & outSigma2ResData)
{
MATTER_TRACE_SCOPE("PrepareSigma2Resume", "CASESession");
VerifyOrReturnError(mLocalMRPConfig.HasValue(), CHIP_ERROR_INCORRECT_STATE);
// Validate that we have a session ID allocated.
VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE);
outSigma2ResData.responderSessionId = GetLocalSessionId().Value();
// Generate a new resumption ID
ReturnErrorOnFailure(DRBG_get_bytes(mNewResumptionId.data(), mNewResumptionId.size()));
outSigma2ResData.resumptionId = mNewResumptionId;
ReturnErrorOnFailure(GenerateSigmaResumeMIC(ByteSpan(mInitiatorRandom), mNewResumptionId, ByteSpan(kKDFS2RKeyInfo),
ByteSpan(kResume2MIC_Nonce), outSigma2ResData.sigma2ResumeMIC));
outSigma2ResData.responderMrpConfig = &mLocalMRPConfig.Value();
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::EncodeSigma2Resume(System::PacketBufferHandle & msgR2Resume, EncodeSigma2ResumeInputs & input)
{
MATTER_TRACE_SCOPE("EncodeSigma2Resume", "CASESession");
VerifyOrReturnError(input.responderMrpConfig != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
size_t maxDatalLen = EstimateStructOverhead(SessionResumptionStorage::kResumptionIdSize, // resumptionID
CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, // sigma2ResumeMIC
sizeof(uint16_t), // responderSessionID
SessionParameters::kEstimatedTLVSize // responderSessionParams
);
msgR2Resume = System::PacketBufferHandle::New(maxDatalLen);
VerifyOrReturnError(!msgR2Resume.IsNull(), CHIP_ERROR_NO_MEMORY);
System::PacketBufferTLVWriter tlvWriter;
tlvWriter.Init(std::move(msgR2Resume));
TLVType outerContainerType = kTLVType_NotSpecified;
ReturnErrorOnFailure(tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma2ResumeTags::kResumptionID), input.resumptionId));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma2ResumeTags::kSigma2ResumeMIC), input.sigma2ResumeMIC));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(Sigma2ResumeTags::kResponderSessionID), input.responderSessionId));
ReturnErrorOnFailure(
EncodeSessionParameters(AsTlvContextTag(Sigma2ResumeTags::kResponderSessionParams), *input.responderMrpConfig, tlvWriter));
ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize(&msgR2Resume));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SendSigma2Resume(System::PacketBufferHandle && msgR2Resume)
{
// Call delegate to send the msg to peer
ReturnErrorOnFailure(mExchangeCtxt.Value()->SendMessage(Protocols::SecureChannel::MsgType::CASE_Sigma2Resume,
std::move(msgR2Resume), SendFlags(SendMessageFlags::kExpectResponse)));
mState = State::kSentSigma2Resume;
ChipLogProgress(SecureChannel, "Sent Sigma2Resume msg");
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::PrepareSigma2(EncodeSigma2Inputs & outSigma2Data)
{
MATTER_TRACE_SCOPE("PrepareSigma2", "CASESession");
VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mLocalMRPConfig.HasValue(), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(GetLocalSessionId().HasValue(), CHIP_ERROR_INCORRECT_STATE);
outSigma2Data.responderSessionId = GetLocalSessionId().Value();
chip::Platform::ScopedMemoryBuffer<uint8_t> icacBuf;
VerifyOrReturnError(icacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
VerifyOrReturnError(nocBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan icaCert{ icacBuf.Get(), kMaxCHIPCertLength };
ReturnErrorOnFailure(mFabricsTable->FetchICACert(mFabricIndex, icaCert));
MutableByteSpan nocCert{ nocBuf.Get(), kMaxCHIPCertLength };
ReturnErrorOnFailure(mFabricsTable->FetchNOCCert(mFabricIndex, nocCert));
// Fill in the random value
ReturnErrorOnFailure(DRBG_get_bytes(&outSigma2Data.responderRandom[0], sizeof(outSigma2Data.responderRandom)));
// Generate an ephemeral keypair
mEphemeralKey = mFabricsTable->AllocateEphemeralKeypairForCASE();
VerifyOrReturnError(mEphemeralKey != nullptr, CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(mEphemeralKey->Initialize(ECPKeyTarget::ECDH));
outSigma2Data.responderEphPubKey = &mEphemeralKey->Pubkey();
// Generate a Shared Secret
ReturnErrorOnFailure(mEphemeralKey->ECDH_derive_secret(mRemotePubKey, mSharedSecret));
uint8_t msgSalt[kIPKSize + kSigmaParamRandomNumberSize + kP256_PublicKey_Length + kSHA256_Hash_Length];
MutableByteSpan saltSpan(msgSalt);
ReturnErrorOnFailure(
ConstructSaltSigma2(ByteSpan(outSigma2Data.responderRandom), mEphemeralKey->Pubkey(), ByteSpan(mIPK), saltSpan));
AutoReleaseSessionKey sr2k(*mSessionManager->GetSessionKeystore());
ReturnErrorOnFailure(DeriveSigmaKey(saltSpan, ByteSpan(kKDFSR2Info), sr2k));
// Construct Sigma2 TBS Data
P256ECDSASignature tbsData2Signature;
{
size_t msgR2SignedLen = EstimateStructOverhead(kMaxCHIPCertLength, // responderNoc
kMaxCHIPCertLength, // responderICAC
kP256_PublicKey_Length, // responderEphPubKey
kP256_PublicKey_Length // InitiatorEphPubKey
);
chip::Platform::ScopedMemoryBuffer<uint8_t> msgR2Signed;
VerifyOrReturnError(msgR2Signed.Alloc(msgR2SignedLen), CHIP_ERROR_NO_MEMORY);
MutableByteSpan msgR2SignedSpan{ msgR2Signed.Get(), msgR2SignedLen };
ReturnErrorOnFailure(ConstructTBSData(nocCert, icaCert, ByteSpan(mEphemeralKey->Pubkey(), mEphemeralKey->Pubkey().Length()),
ByteSpan(mRemotePubKey, mRemotePubKey.Length()), msgR2SignedSpan));
// Generate a Signature
ReturnErrorOnFailure(mFabricsTable->SignWithOpKeypair(mFabricIndex, msgR2SignedSpan, tbsData2Signature));
}
// Construct Sigma2 TBE Data
size_t msgR2SignedEncLen = EstimateStructOverhead(nocCert.size(), // responderNoc
icaCert.size(), // responderICAC
tbsData2Signature.Length(), // signature
SessionResumptionStorage::kResumptionIdSize // resumptionID
);
VerifyOrReturnError(outSigma2Data.msgR2Encrypted.Alloc(msgR2SignedEncLen + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES),
CHIP_ERROR_NO_MEMORY);
TLVWriter tlvWriter;
tlvWriter.Init(outSigma2Data.msgR2Encrypted.Get(), msgR2SignedEncLen);
TLVType outerContainerType = kTLVType_NotSpecified;
ReturnErrorOnFailure(tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma2NOC, *nocCert.data() ^= 0xFF);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma2ICAC, *icaCert.data() ^= 0xFF);
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBEDataTags::kSenderNOC), nocCert));
if (!icaCert.empty())
{
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBEDataTags::kSenderICAC), icaCert));
}
// We are now done with ICAC and NOC certs so we can release the memory.
{
icacBuf.Free();
icaCert = MutableByteSpan{};
nocBuf.Free();
nocCert = MutableByteSpan{};
}
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma2Signature, *tbsData2Signature.Bytes() ^= 0xFF);
ReturnErrorOnFailure(tlvWriter.PutBytes(AsTlvContextTag(TBEDataTags::kSignature), tbsData2Signature.ConstBytes(),
static_cast<uint32_t>(tbsData2Signature.Length())));
// Generate a new resumption ID
ReturnErrorOnFailure(DRBG_get_bytes(mNewResumptionId.data(), mNewResumptionId.size()));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBEDataTags::kResumptionID), mNewResumptionId));
ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize());
msgR2SignedEncLen = static_cast<size_t>(tlvWriter.GetLengthWritten());
outSigma2Data.encrypted2Length = msgR2SignedEncLen + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES;
// Generate the encrypted data blob
ReturnErrorOnFailure(AES_CCM_encrypt(outSigma2Data.msgR2Encrypted.Get(), msgR2SignedEncLen, nullptr, 0, sr2k.KeyHandle(),
kTBEData2_Nonce, kTBEDataNonceLength, outSigma2Data.msgR2Encrypted.Get(),
outSigma2Data.msgR2Encrypted.Get() + msgR2SignedEncLen,
CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
outSigma2Data.responderMrpConfig = &mLocalMRPConfig.Value();
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::EncodeSigma2(System::PacketBufferHandle & msgR2, EncodeSigma2Inputs & input)
{
VerifyOrReturnError(input.responderEphPubKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(input.msgR2Encrypted, CHIP_ERROR_INCORRECT_STATE);
// Check if length of msgR2Encrypted is set and is at least larger than the MIC length
VerifyOrReturnError(input.encrypted2Length > CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(input.responderMrpConfig != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
size_t dataLen = EstimateStructOverhead(kSigmaParamRandomNumberSize, // responderRandom
sizeof(uint16_t), // responderSessionId
kP256_PublicKey_Length, // responderEphPubKey
input.encrypted2Length, // encrypted2
SessionParameters::kEstimatedTLVSize // responderSessionParams
);
msgR2 = System::PacketBufferHandle::New(dataLen);
VerifyOrReturnError(!msgR2.IsNull(), CHIP_ERROR_NO_MEMORY);
System::PacketBufferTLVWriter tlvWriterMsg2;
tlvWriterMsg2.Init(std::move(msgR2));
TLVType outerContainerType = kTLVType_NotSpecified;
ReturnErrorOnFailure(tlvWriterMsg2.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriterMsg2.PutBytes(AsTlvContextTag(Sigma2Tags::kResponderRandom), &input.responderRandom[0],
sizeof(input.responderRandom)));
ReturnErrorOnFailure(tlvWriterMsg2.Put(AsTlvContextTag(Sigma2Tags::kResponderSessionId), input.responderSessionId));
ReturnErrorOnFailure(tlvWriterMsg2.PutBytes(AsTlvContextTag(Sigma2Tags::kResponderEphPubKey), *input.responderEphPubKey,
static_cast<uint32_t>(input.responderEphPubKey->Length())));
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptTBEData2Encrypted, *input.msgR2Encrypted.Get() ^= 0xFF);
ReturnErrorOnFailure(tlvWriterMsg2.PutBytes(AsTlvContextTag(Sigma2Tags::kEncrypted2), input.msgR2Encrypted.Get(),
static_cast<uint32_t>(input.encrypted2Length)));
input.msgR2Encrypted.Free();
ReturnErrorOnFailure(
EncodeSessionParameters(AsTlvContextTag(Sigma2Tags::kResponderSessionParams), *input.responderMrpConfig, tlvWriterMsg2));
ReturnErrorOnFailure(tlvWriterMsg2.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriterMsg2.Finalize(&msgR2));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SendSigma2(System::PacketBufferHandle && msgR2)
{
MATTER_TRACE_SCOPE("SendSigma2", "CASESession");
ReturnErrorOnFailure(mCommissioningHash.AddData(ByteSpan{ msgR2->Start(), msgR2->DataLength() }));
// Call delegate to send the msg to peer
ReturnErrorOnFailure(mExchangeCtxt.Value()->SendMessage(Protocols::SecureChannel::MsgType::CASE_Sigma2, std::move(msgR2),
SendFlags(SendMessageFlags::kExpectResponse)));
mState = State::kSentSigma2;
ChipLogProgress(SecureChannel, "Sent Sigma2 msg");
MATTER_TRACE_COUNTER("Sigma2");
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::HandleSigma2Resume(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma2Resume", "CASESession");
CHIP_ERROR err = CHIP_NO_ERROR;
ChipLogDetail(SecureChannel, "Received Sigma2Resume msg");
MATTER_TRACE_COUNTER("Sigma2Resume");
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma1, err);
System::PacketBufferTLVReader tlvReader;
tlvReader.Init(std::move(msg));
ParsedSigma2Resume parsedSigma2Resume;
SuccessOrExit(err = ParseSigma2Resume(tlvReader, parsedSigma2Resume));
SuccessOrExit(err = ValidateSigmaResumeMIC(parsedSigma2Resume.sigma2ResumeMIC, ByteSpan(mInitiatorRandom),
parsedSigma2Resume.resumptionId, ByteSpan(kKDFS2RKeyInfo),
ByteSpan(kResume2MIC_Nonce)));
if (parsedSigma2Resume.responderSessionParamStructPresent)
{
SetRemoteSessionParameters(parsedSigma2Resume.responderSessionParams);
mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->SetRemoteSessionParameters(
GetRemoteSessionParameters());
}
ChipLogDetail(SecureChannel, "Peer " ChipLogFormatScopedNodeId " assigned session ID %d", ChipLogValueScopedNodeId(GetPeer()),
parsedSigma2Resume.responderSessionId);
SetPeerSessionId(parsedSigma2Resume.responderSessionId);
if (mSessionResumptionStorage != nullptr)
{
CHIP_ERROR err2 = mSessionResumptionStorage->Save(
GetPeer(), SessionResumptionStorage::ConstResumptionIdView(parsedSigma2Resume.resumptionId.data()), mSharedSecret,
mPeerCATs);
if (err2 != CHIP_NO_ERROR)
ChipLogError(SecureChannel, "Unable to save session resumption state: %" CHIP_ERROR_FORMAT, err2.Format());
}
MATTER_LOG_METRIC(kMetricDeviceCASESessionSigmaFinished);
SendStatusReport(mExchangeCtxt, kProtocolCodeSuccess);
mState = State::kFinishedViaResume;
Finish();
exit:
if (err != CHIP_NO_ERROR)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
}
return err;
}
CHIP_ERROR CASESession::ParseSigma2Resume(ContiguousBufferTLVReader & tlvReader, ParsedSigma2Resume & outParsedSigma2Resume)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(tlvReader.Next(containerType, AnonymousTag()));
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2ResumeTags::kResumptionID)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma2Resume.resumptionId));
VerifyOrReturnError(outParsedSigma2Resume.resumptionId.size() == SessionResumptionStorage::kResumptionIdSize,
CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2ResumeTags::kSigma2ResumeMIC)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma2Resume.sigma2ResumeMIC));
VerifyOrReturnError(outParsedSigma2Resume.sigma2ResumeMIC.size() == CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES,
CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2ResumeTags::kResponderSessionID)));
ReturnErrorOnFailure(tlvReader.Get(outParsedSigma2Resume.responderSessionId));
CHIP_ERROR err = tlvReader.Next();
if (err == CHIP_NO_ERROR && tlvReader.GetTag() == AsTlvContextTag(Sigma2ResumeTags::kResponderSessionParams))
{
ReturnErrorOnFailure(DecodeSessionParametersIfPresent(AsTlvContextTag(Sigma2ResumeTags::kResponderSessionParams), tlvReader,
outParsedSigma2Resume.responderSessionParams));
outParsedSigma2Resume.responderSessionParamStructPresent = true;
err = tlvReader.Next();
}
// Future-proofing: CHIP_NO_ERROR will be returned by Next() if we have additional non-parsed TLV Elements, which could
// happen in the future if additional elements are added to the specification.
VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_NO_ERROR, err);
// Exit Container will fail (return CHIP_END_OF_TLV) if the received encoded message is not properly terminated with an
// EndOfContainer TLV Element.
ReturnErrorOnFailure(tlvReader.ExitContainer(containerType));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::HandleSigma2_and_SendSigma3(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma2_and_SendSigma3", "CASESession");
CHIP_ERROR err = HandleSigma2(std::move(msg));
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma1, err);
SuccessOrExit(err);
MATTER_LOG_METRIC_BEGIN(kMetricDeviceCASESessionSigma3);
err = SendSigma3a();
if (CHIP_NO_ERROR != err)
{
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma3, err);
}
exit:
if (CHIP_NO_ERROR != err)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
mState = State::kInitialized;
}
return err;
}
CHIP_ERROR CASESession::HandleSigma2(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma2", "CASESession");
ChipLogProgress(SecureChannel, "Received Sigma2 msg");
VerifyOrReturnError(mEphemeralKey != nullptr, CHIP_ERROR_INTERNAL);
const uint8_t * buf = msg->Start();
size_t buflen = msg->DataLength();
VerifyOrReturnError(buf != nullptr, CHIP_ERROR_MESSAGE_INCOMPLETE);
FabricId fabricId = kUndefinedFabricId;
{
VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
const auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE);
fabricId = fabricInfo->GetFabricId();
}
System::PacketBufferTLVReader tlvReader;
tlvReader.Init(std::move(msg));
ParsedSigma2 parsedSigma2;
ReturnErrorOnFailure(ParseSigma2(tlvReader, parsedSigma2));
// ParseSigma2 ensures that:
// mRemotePubKey.Length() == responderEphPubKey.size() == kP256_PublicKey_Length.
memcpy(mRemotePubKey.Bytes(), parsedSigma2.responderEphPubKey.data(), mRemotePubKey.Length());
// Generate a Shared Secret
ReturnErrorOnFailure(mEphemeralKey->ECDH_derive_secret(mRemotePubKey, mSharedSecret));
// Generate the S2K key
AutoReleaseSessionKey sr2k(*mSessionManager->GetSessionKeystore());
{
uint8_t msg_salt[kIPKSize + kSigmaParamRandomNumberSize + kP256_PublicKey_Length + kSHA256_Hash_Length];
MutableByteSpan saltSpan(msg_salt);
ReturnErrorOnFailure(ConstructSaltSigma2(parsedSigma2.responderRandom, mRemotePubKey, ByteSpan(mIPK), saltSpan));
ReturnErrorOnFailure(DeriveSigmaKey(saltSpan, ByteSpan(kKDFSR2Info), sr2k));
}
// Msg2 should only be added to MessageDigest after we construct SaltSigma2 that is used to derive S2K,
// Because constructing SaltSigma2 uses the MessageDigest at a point when it should only include Msg1.
ReturnErrorOnFailure(mCommissioningHash.AddData(ByteSpan{ buf, buflen }));
ReturnErrorOnFailure(AES_CCM_decrypt(parsedSigma2.msgR2EncryptedPayload.data(), parsedSigma2.msgR2EncryptedPayload.size(),
nullptr, 0, parsedSigma2.msgR2MIC.data(), parsedSigma2.msgR2MIC.size(), sr2k.KeyHandle(),
kTBEData2_Nonce, kTBEDataNonceLength, parsedSigma2.msgR2EncryptedPayload.data()));
parsedSigma2.msgR2Decrypted = std::move(parsedSigma2.msgR2Encrypted);
size_t msgR2DecryptedLength = parsedSigma2.msgR2EncryptedPayload.size();
ContiguousBufferTLVReader decryptedDataTlvReader;
decryptedDataTlvReader.Init(parsedSigma2.msgR2Decrypted.Get(), msgR2DecryptedLength);
ParsedSigma2TBEData parsedSigma2TBEData;
ReturnErrorOnFailure(ParseSigma2TBEData(decryptedDataTlvReader, parsedSigma2TBEData));
// Validate responder identity located in msgR2Decrypted
// Constructing responder identity
P256PublicKey responderPublicKey;
{
NodeId responderNodeId;
CompressedFabricId unused;
FabricId responderFabricId;
ReturnErrorOnFailure(SetEffectiveTime());
ReturnErrorOnFailure(mFabricsTable->VerifyCredentials(mFabricIndex, parsedSigma2TBEData.responderNOC,
parsedSigma2TBEData.responderICAC, mValidContext, unused,
responderFabricId, responderNodeId, responderPublicKey));
VerifyOrReturnError(fabricId == responderFabricId, CHIP_ERROR_INVALID_CASE_PARAMETER);
// Verify that responderNodeId (from responderNOC) matches one that was included
// in the computation of the Destination Identifier when generating Sigma1.
VerifyOrReturnError(mPeerNodeId == responderNodeId, CHIP_ERROR_INVALID_CASE_PARAMETER);
}
// Construct msgR2Signed and validate the signature in msgR2Decrypted.
size_t msgR2SignedLen = EstimateStructOverhead(parsedSigma2TBEData.responderNOC.size(), // resonderNOC
parsedSigma2TBEData.responderICAC.size(), // responderICAC
kP256_PublicKey_Length, // responderEphPubKey
kP256_PublicKey_Length // initiatorEphPubKey
);
chip::Platform::ScopedMemoryBuffer<uint8_t> msgR2Signed;
VerifyOrReturnError(msgR2Signed.Alloc(msgR2SignedLen), CHIP_ERROR_NO_MEMORY);
MutableByteSpan msgR2SignedSpan{ msgR2Signed.Get(), msgR2SignedLen };
ReturnErrorOnFailure(ConstructTBSData(parsedSigma2TBEData.responderNOC, parsedSigma2TBEData.responderICAC,
ByteSpan(mRemotePubKey, mRemotePubKey.Length()),
ByteSpan(mEphemeralKey->Pubkey(), mEphemeralKey->Pubkey().Length()), msgR2SignedSpan));
// Validate signature
ReturnErrorOnFailure(responderPublicKey.ECDSA_validate_msg_signature(msgR2SignedSpan.data(), msgR2SignedSpan.size(),
parsedSigma2TBEData.tbsData2Signature));
ChipLogDetail(SecureChannel, "Peer " ChipLogFormatScopedNodeId " assigned session ID %d", ChipLogValueScopedNodeId(GetPeer()),
parsedSigma2.responderSessionId);
SetPeerSessionId(parsedSigma2.responderSessionId);
std::copy(parsedSigma2TBEData.resumptionId.begin(), parsedSigma2TBEData.resumptionId.end(), mNewResumptionId.begin());
// Retrieve peer CASE Authenticated Tags (CATs) from peer's NOC.
ReturnErrorOnFailure(ExtractCATsFromOpCert(parsedSigma2TBEData.responderNOC, mPeerCATs));
if (parsedSigma2.responderSessionParamStructPresent)
{
SetRemoteSessionParameters(parsedSigma2.responderSessionParams);
mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->SetRemoteSessionParameters(
GetRemoteSessionParameters());
}
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ParseSigma2(ContiguousBufferTLVReader & tlvReader, ParsedSigma2 & outParsedSigma2)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(tlvReader.Next(containerType, AnonymousTag()));
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));
// Retrieve Responder's Random value
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2Tags::kResponderRandom)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma2.responderRandom));
VerifyOrReturnError(outParsedSigma2.responderRandom.size() == kSigmaParamRandomNumberSize, CHIP_ERROR_INVALID_CASE_PARAMETER);
// Assign Session ID
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2Tags::kResponderSessionId)));
ReturnErrorOnFailure(tlvReader.Get(outParsedSigma2.responderSessionId));
// Retrieve Responder's Ephemeral Pubkey
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2Tags::kResponderEphPubKey)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma2.responderEphPubKey));
VerifyOrReturnError(outParsedSigma2.responderEphPubKey.size() == kP256_PublicKey_Length, CHIP_ERROR_INVALID_CASE_PARAMETER);
// Generate decrypted data
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma2Tags::kEncrypted2)));
size_t maxMsgR2SignedEncLen = EstimateStructOverhead(kMaxCHIPCertLength, // responderNOC
kMaxCHIPCertLength, // responderICAC
kMax_ECDSA_Signature_Length, // signature
SessionResumptionStorage::kResumptionIdSize, // resumptionID
kCaseOverheadForFutureTBEData // extra bytes for future-proofing
);
size_t msgR2EncryptedLenWithTag = tlvReader.GetLength();
// Validate we did not receive a buffer larger than legal
VerifyOrReturnError(msgR2EncryptedLenWithTag <= maxMsgR2SignedEncLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
VerifyOrReturnError(msgR2EncryptedLenWithTag > CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, CHIP_ERROR_INVALID_TLV_ELEMENT);
VerifyOrReturnError(outParsedSigma2.msgR2Encrypted.Alloc(msgR2EncryptedLenWithTag), CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(tlvReader.GetBytes(outParsedSigma2.msgR2Encrypted.Get(), outParsedSigma2.msgR2Encrypted.AllocatedSize()));
size_t msgR2EncryptedPayloadLen = msgR2EncryptedLenWithTag - CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES;
outParsedSigma2.msgR2EncryptedPayload = MutableByteSpan(outParsedSigma2.msgR2Encrypted.Get(), msgR2EncryptedPayloadLen);
outParsedSigma2.msgR2MIC =
ByteSpan(outParsedSigma2.msgR2Encrypted.Get() + msgR2EncryptedPayloadLen, CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES);
// Retrieve responderSessionParams if present
CHIP_ERROR err = tlvReader.Next();
if (err == CHIP_NO_ERROR && tlvReader.GetTag() == AsTlvContextTag(Sigma2Tags::kResponderSessionParams))
{
ReturnErrorOnFailure(DecodeSessionParametersIfPresent(AsTlvContextTag(Sigma2Tags::kResponderSessionParams), tlvReader,
outParsedSigma2.responderSessionParams));
outParsedSigma2.responderSessionParamStructPresent = true;
err = tlvReader.Next();
}
// Future-proofing: CHIP_NO_ERROR will be returned by Next() if we have additional non-parsed TLV Elements, which could
// happen in the future if additional elements are added to the specification.
VerifyOrReturnError(err == CHIP_END_OF_TLV || err == CHIP_NO_ERROR, err);
// Exit Container will fail (return CHIP_END_OF_TLV) if the received encoded message is not properly terminated with an
// EndOfContainer TLV Element.
ReturnErrorOnFailure(tlvReader.ExitContainer(containerType));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ParseSigma2TBEData(ContiguousBufferTLVReader & decryptedDataTlvReader,
ParsedSigma2TBEData & outParsedSigma2TBE)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(decryptedDataTlvReader.Next(containerType, AnonymousTag()));
ReturnErrorOnFailure(decryptedDataTlvReader.EnterContainer(containerType));
ReturnErrorOnFailure(decryptedDataTlvReader.Next(AsTlvContextTag(TBEDataTags::kSenderNOC)));
ReturnErrorOnFailure(decryptedDataTlvReader.GetByteView(outParsedSigma2TBE.responderNOC));
VerifyOrReturnError(outParsedSigma2TBE.responderNOC.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(decryptedDataTlvReader.Next());
if (decryptedDataTlvReader.GetTag() == AsTlvContextTag(TBEDataTags::kSenderICAC))
{
ReturnErrorOnFailure(decryptedDataTlvReader.GetByteView(outParsedSigma2TBE.responderICAC));
VerifyOrReturnError(outParsedSigma2TBE.responderICAC.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(decryptedDataTlvReader.Next(kTLVType_ByteString, AsTlvContextTag(TBEDataTags::kSignature)));
}
VerifyOrReturnError(decryptedDataTlvReader.GetTag() == AsTlvContextTag(TBEDataTags::kSignature), CHIP_ERROR_INVALID_TLV_TAG);
// tbsData2Signature's length should equal kMax_ECDSA_Signature_Length as per the Specification
size_t signatureLen = decryptedDataTlvReader.GetLength();
VerifyOrReturnError(outParsedSigma2TBE.tbsData2Signature.Capacity() == signatureLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
outParsedSigma2TBE.tbsData2Signature.SetLength(signatureLen);
ReturnErrorOnFailure(decryptedDataTlvReader.GetBytes(outParsedSigma2TBE.tbsData2Signature.Bytes(),
outParsedSigma2TBE.tbsData2Signature.Length()));
// Retrieve session resumption ID
ReturnErrorOnFailure(decryptedDataTlvReader.Next(AsTlvContextTag(TBEDataTags::kResumptionID)));
ReturnErrorOnFailure(decryptedDataTlvReader.GetByteView(outParsedSigma2TBE.resumptionId));
VerifyOrReturnError(outParsedSigma2TBE.resumptionId.size() == SessionResumptionStorage::kResumptionIdSize,
CHIP_ERROR_INVALID_CASE_PARAMETER);
// Exit Container will fail (return CHIP_END_OF_TLV) if the received encoded message is not properly terminated with an
// EndOfContainer TLV Element.
ReturnErrorOnFailure(decryptedDataTlvReader.ExitContainer(containerType));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SendSigma3a()
{
MATTER_TRACE_SCOPE("SendSigma3", "CASESession");
ChipLogDetail(SecureChannel, "Sending Sigma3");
auto helper = WorkHelper<SendSigma3Data>::Create(*this, &SendSigma3b, &CASESession::SendSigma3c);
VerifyOrReturnError(helper, CHIP_ERROR_NO_MEMORY);
{
auto & data = helper->mData;
VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
data.fabricIndex = mFabricIndex;
data.fabricTable = nullptr;
data.keystore = nullptr;
{
const FabricInfo * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
auto * keystore = mFabricsTable->GetOperationalKeystore();
if (!fabricInfo->HasOperationalKey() && keystore != nullptr && keystore->SupportsSignWithOpKeypairInBackground())
{
// NOTE: used to sign in background.
data.keystore = keystore;
}
else
{
// NOTE: used to sign in foreground.
data.fabricTable = mFabricsTable;
}
}
VerifyOrReturnError(mEphemeralKey != nullptr, CHIP_ERROR_INTERNAL);
VerifyOrReturnError(data.icacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
data.icaCert = MutableByteSpan{ data.icacBuf.Get(), kMaxCHIPCertLength };
VerifyOrReturnError(data.nocBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
data.nocCert = MutableByteSpan{ data.nocBuf.Get(), kMaxCHIPCertLength };
ReturnErrorOnFailure(mFabricsTable->FetchICACert(mFabricIndex, data.icaCert));
ReturnErrorOnFailure(mFabricsTable->FetchNOCCert(mFabricIndex, data.nocCert));
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma3NOC, *data.nocCert.data() ^= 0xFF);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma3ICAC, *data.icaCert.data() ^= 0xFF);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma3InitiatorEphPubKey,
*(mEphemeralKey->TestOnlyMutablePubkey().Bytes()) ^= 0xFF);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma3ResponderEphPubKey, *mRemotePubKey ^= 0xFF);
// Prepare Sigma3 TBS Data Blob
size_t msgR3SignedLen = EstimateStructOverhead(data.nocCert.size(), // initiatorNOC
data.icaCert.size(), // initiatorICAC
kP256_PublicKey_Length, // initiatorEphPubKey
kP256_PublicKey_Length // responderEphPubKey
);
VerifyOrReturnError(data.msgR3Signed.Alloc(msgR3SignedLen), CHIP_ERROR_NO_MEMORY);
data.msgR3SignedSpan = MutableByteSpan{ data.msgR3Signed.Get(), msgR3SignedLen };
ReturnErrorOnFailure(ConstructTBSData(data.nocCert, data.icaCert,
ByteSpan(mEphemeralKey->Pubkey(), mEphemeralKey->Pubkey().Length()),
ByteSpan(mRemotePubKey, mRemotePubKey.Length()), data.msgR3SignedSpan));
if (data.keystore != nullptr)
{
ReturnErrorOnFailure(helper->ScheduleWork());
mSendSigma3Helper = helper;
mExchangeCtxt.Value()->WillSendMessage();
mState = State::kSendSigma3Pending;
}
else
{
ReturnErrorOnFailure(helper->DoWork());
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SendSigma3b(SendSigma3Data & data, bool & cancel)
{
// Generate a signature
if (data.keystore != nullptr)
{
// Recommended case: delegate to operational keystore
ReturnErrorOnFailure(data.keystore->SignWithOpKeypair(data.fabricIndex, data.msgR3SignedSpan, data.tbsData3Signature));
}
else
{
// Legacy case: delegate to fabric table fabric info
ReturnErrorOnFailure(data.fabricTable->SignWithOpKeypair(data.fabricIndex, data.msgR3SignedSpan, data.tbsData3Signature));
}
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptSigma3Signature, *data.tbsData3Signature.Bytes() ^= 0xFF);
// Prepare Sigma3 TBE Data Blob
data.msg_r3_encrypted_len =
TLV::EstimateStructOverhead(data.nocCert.size(), data.icaCert.size(), data.tbsData3Signature.Length());
VerifyOrReturnError(data.msg_R3_Encrypted.Alloc(data.msg_r3_encrypted_len + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES),
CHIP_ERROR_NO_MEMORY);
{
TLVWriter tlvWriter;
TLVType outerContainerType = kTLVType_NotSpecified;
tlvWriter.Init(data.msg_R3_Encrypted.Get(), data.msg_r3_encrypted_len);
ReturnErrorOnFailure(tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBEDataTags::kSenderNOC), data.nocCert));
if (!data.icaCert.empty())
{
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBEDataTags::kSenderICAC), data.icaCert));
}
// We are now done with ICAC and NOC certs so we can release the memory.
{
data.icacBuf.Free();
data.icaCert = MutableByteSpan{};
data.nocBuf.Free();
data.nocCert = MutableByteSpan{};
}
ReturnErrorOnFailure(tlvWriter.PutBytes(AsTlvContextTag(TBEDataTags::kSignature), data.tbsData3Signature.ConstBytes(),
static_cast<uint32_t>(data.tbsData3Signature.Length())));
ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize());
data.msg_r3_encrypted_len = static_cast<size_t>(tlvWriter.GetLengthWritten());
}
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SendSigma3c(SendSigma3Data & data, CHIP_ERROR status)
{
CHIP_ERROR err = CHIP_NO_ERROR;
System::PacketBufferHandle msg_R3;
size_t data_len;
uint8_t msg_salt[kIPKSize + kSHA256_Hash_Length];
AutoReleaseSessionKey sr3k(*mSessionManager->GetSessionKeystore());
VerifyOrDieWithMsg(data.keystore == nullptr || mState == State::kSendSigma3Pending, SecureChannel, "Bad internal state.");
SuccessOrExit(err = status);
// Generate S3K key
{
MutableByteSpan saltSpan(msg_salt);
SuccessOrExit(err = ConstructSaltSigma3(ByteSpan(mIPK), saltSpan));
SuccessOrExit(err = DeriveSigmaKey(saltSpan, ByteSpan(kKDFSR3Info), sr3k));
}
// Generated Encrypted data blob
SuccessOrExit(err =
AES_CCM_encrypt(data.msg_R3_Encrypted.Get(), data.msg_r3_encrypted_len, nullptr, 0, sr3k.KeyHandle(),
kTBEData3_Nonce, kTBEDataNonceLength, data.msg_R3_Encrypted.Get(),
data.msg_R3_Encrypted.Get() + data.msg_r3_encrypted_len, CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
// Generate Sigma3 Msg
data_len = TLV::EstimateStructOverhead(CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, data.msg_r3_encrypted_len);
msg_R3 = System::PacketBufferHandle::New(data_len);
VerifyOrExit(!msg_R3.IsNull(), err = CHIP_ERROR_NO_MEMORY);
{
System::PacketBufferTLVWriter tlvWriter;
TLV::TLVType outerContainerType = TLV::kTLVType_NotSpecified;
tlvWriter.Init(std::move(msg_R3));
err = tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType);
SuccessOrExit(err);
CHIP_FAULT_INJECT(FaultInjection::kFault_CASECorruptTBEData3Encrypted, *data.msg_R3_Encrypted.Get() ^= 0xFF);
err = tlvWriter.PutBytes(AsTlvContextTag(Sigma3Tags::kEncrypted3), data.msg_R3_Encrypted.Get(),
static_cast<uint32_t>(data.msg_r3_encrypted_len + CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
SuccessOrExit(err);
err = tlvWriter.EndContainer(outerContainerType);
SuccessOrExit(err);
err = tlvWriter.Finalize(&msg_R3);
SuccessOrExit(err);
}
err = mCommissioningHash.AddData(ByteSpan{ msg_R3->Start(), msg_R3->DataLength() });
SuccessOrExit(err);
// Call delegate to send the Msg3 to peer
err = mExchangeCtxt.Value()->SendMessage(Protocols::SecureChannel::MsgType::CASE_Sigma3, std::move(msg_R3),
SendFlags(SendMessageFlags::kExpectResponse));
SuccessOrExit(err);
ChipLogProgress(SecureChannel, "Sent Sigma3 msg");
{
MutableByteSpan messageDigestSpan(mMessageDigest);
SuccessOrExit(err = mCommissioningHash.Finish(messageDigestSpan));
}
mState = State::kSentSigma3;
exit:
mSendSigma3Helper.reset();
// If data.keystore is set, processing occurred in the background, so if an error occurred,
// need to send status report (normally occurs in SendSigma3a), and discard exchange and
// abort pending establish (normally occurs in OnMessageReceived).
if (data.keystore != nullptr && err != CHIP_NO_ERROR)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
DiscardExchange();
AbortPendingEstablish(err);
}
return err;
}
CHIP_ERROR CASESession::HandleSigma3a(System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("HandleSigma3", "CASESession");
CHIP_ERROR err = CHIP_NO_ERROR;
ContiguousBufferTLVReader decryptedDataTlvReader;
TLVType containerType = kTLVType_Structure;
const uint8_t * buf = msg->Start();
const size_t bufLen = msg->DataLength();
AutoReleaseSessionKey sr3k(*mSessionManager->GetSessionKeystore());
uint8_t msg_salt[kIPKSize + kSHA256_Hash_Length];
ChipLogProgress(SecureChannel, "Received Sigma3 msg");
MATTER_TRACE_COUNTER("Sigma3");
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma2, err);
auto helper = WorkHelper<HandleSigma3Data>::Create(*this, &HandleSigma3b, &CASESession::HandleSigma3c);
VerifyOrExit(helper, err = CHIP_ERROR_NO_MEMORY);
{
auto & data = helper->mData;
{
VerifyOrExit(mFabricsTable != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
const auto * fabricInfo = mFabricsTable->FindFabricWithIndex(mFabricIndex);
VerifyOrExit(fabricInfo != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
data.fabricId = fabricInfo->GetFabricId();
}
VerifyOrExit(mEphemeralKey != nullptr, err = CHIP_ERROR_INTERNAL);
// Step 1
// msgR3Encrypted will be allocated and initialised within ParseSigma3()
Platform::ScopedMemoryBufferWithSize<uint8_t> msgR3Encrypted;
// both msgR3EncryptedPayload and msgR3MIC will become backed by msgR3Encrypted in ParseSigma3()
MutableByteSpan msgR3EncryptedPayload;
ByteSpan msgR3MIC;
{
System::PacketBufferTLVReader tlvReader;
tlvReader.Init(std::move(msg));
SuccessOrExit(err = ParseSigma3(tlvReader, msgR3Encrypted, msgR3EncryptedPayload, msgR3MIC));
// Generate the S3K key
MutableByteSpan saltSpan(msg_salt);
SuccessOrExit(err = ConstructSaltSigma3(ByteSpan(mIPK), saltSpan));
SuccessOrExit(err = DeriveSigmaKey(saltSpan, ByteSpan(kKDFSR3Info), sr3k));
// Add Sigma3 to the TranscriptHash which will be used to generate the Session Encryption Keys
SuccessOrExit(err = mCommissioningHash.AddData(ByteSpan{ buf, bufLen }));
}
// Step 2 - Decrypt data blob
SuccessOrExit(err = AES_CCM_decrypt(msgR3EncryptedPayload.data(), msgR3EncryptedPayload.size(), nullptr, 0, msgR3MIC.data(),
msgR3MIC.size(), sr3k.KeyHandle(), kTBEData3_Nonce, kTBEDataNonceLength,
msgR3EncryptedPayload.data()));
decryptedDataTlvReader.Init(msgR3EncryptedPayload.data(), msgR3EncryptedPayload.size());
SuccessOrExit(err = ParseSigma3TBEData(decryptedDataTlvReader, data));
// Step 3 - Construct Sigma3 TBS Data
size_t msgR3SignedLen = TLV::EstimateStructOverhead(data.initiatorNOC.size(), // initiatorNOC
data.initiatorICAC.size(), // initiatorICAC
kP256_PublicKey_Length, // initiatorEphPubKey
kP256_PublicKey_Length // responderEphPubKey
);
VerifyOrExit(data.msgR3Signed.Alloc(msgR3SignedLen), err = CHIP_ERROR_NO_MEMORY);
data.msgR3SignedSpan = MutableByteSpan{ data.msgR3Signed.Get(), msgR3SignedLen };
SuccessOrExit(err = ConstructTBSData(data.initiatorNOC, data.initiatorICAC, ByteSpan(mRemotePubKey, mRemotePubKey.Length()),
ByteSpan(mEphemeralKey->Pubkey(), mEphemeralKey->Pubkey().Length()),
data.msgR3SignedSpan));
// Prepare for Step 4/5
{
MutableByteSpan fabricRCAC{ data.rootCertBuf };
SuccessOrExit(err = mFabricsTable->FetchRootCert(mFabricIndex, fabricRCAC));
data.fabricRCAC = fabricRCAC;
// TODO probably should make SetEffectiveTime static and call closer to VerifyCredentials
SuccessOrExit(err = SetEffectiveTime());
}
// Copy remaining needed data into work structure
{
data.validContext = mValidContext;
// initiatorNOC and initiatorICAC are spans into msgR3Encrypted
// which is going away, so to save memory, redirect them to their
// copies in msgR3Signed, which is staying around
TLV::ContiguousBufferTLVReader signedDataTlvReader;
signedDataTlvReader.Init(data.msgR3SignedSpan);
SuccessOrExit(err = signedDataTlvReader.Next(containerType, AnonymousTag()));
SuccessOrExit(err = signedDataTlvReader.EnterContainer(containerType));
SuccessOrExit(err = signedDataTlvReader.Next(AsTlvContextTag(TBSDataTags::kSenderNOC)));
SuccessOrExit(err = signedDataTlvReader.GetByteView(data.initiatorNOC));
if (!data.initiatorICAC.empty())
{
SuccessOrExit(err = signedDataTlvReader.Next(AsTlvContextTag(TBSDataTags::kSenderICAC)));
SuccessOrExit(err = signedDataTlvReader.GetByteView(data.initiatorICAC));
}
SuccessOrExit(err = signedDataTlvReader.ExitContainer(containerType));
}
SuccessOrExit(err = helper->ScheduleWork());
mHandleSigma3Helper = helper;
mExchangeCtxt.Value()->WillSendMessage();
mState = State::kHandleSigma3Pending;
}
exit:
if (err != CHIP_NO_ERROR)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
}
return err;
}
CHIP_ERROR CASESession::ParseSigma3(ContiguousBufferTLVReader & tlvReader,
Platform::ScopedMemoryBufferWithSize<uint8_t> & outMsgR3Encrypted,
MutableByteSpan & outMsgR3EncryptedPayload, ByteSpan & outMsgR3MIC)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(tlvReader.Next(containerType, AnonymousTag()));
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));
// Fetch encrypted data
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma3Tags::kEncrypted3)));
size_t maxMsgR3SignedEncLen = EstimateStructOverhead(kMaxCHIPCertLength, // initiatorNOC
kMaxCHIPCertLength, // initiatorICAC
kMax_ECDSA_Signature_Length, // signature
kCaseOverheadForFutureTBEData // extra bytes for future-proofing
);
size_t msgR3EncryptedLenWithTag = tlvReader.GetLength();
// Validate we did not receive a buffer larger than legal
VerifyOrReturnError(msgR3EncryptedLenWithTag <= maxMsgR3SignedEncLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
VerifyOrReturnError(msgR3EncryptedLenWithTag > CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, CHIP_ERROR_INVALID_TLV_ELEMENT);
VerifyOrReturnError(outMsgR3Encrypted.Alloc(msgR3EncryptedLenWithTag), CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(tlvReader.GetBytes(outMsgR3Encrypted.Get(), outMsgR3Encrypted.AllocatedSize()));
size_t msgR3EncryptedPayloadLen = msgR3EncryptedLenWithTag - CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES;
outMsgR3EncryptedPayload = MutableByteSpan(outMsgR3Encrypted.Get(), msgR3EncryptedPayloadLen);
outMsgR3MIC = ByteSpan(outMsgR3Encrypted.Get() + msgR3EncryptedPayloadLen, CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES);
ReturnErrorOnFailure(tlvReader.ExitContainer(containerType));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ParseSigma3TBEData(ContiguousBufferTLVReader & decryptedDataTlvReader,
HandleSigma3Data & outHandleSigma3TBEData)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(decryptedDataTlvReader.Next(containerType, TLV::AnonymousTag()));
ReturnErrorOnFailure(decryptedDataTlvReader.EnterContainer(containerType));
ReturnErrorOnFailure(decryptedDataTlvReader.Next(AsTlvContextTag(TBEDataTags::kSenderNOC)));
ReturnErrorOnFailure(decryptedDataTlvReader.GetByteView(outHandleSigma3TBEData.initiatorNOC));
VerifyOrReturnError(outHandleSigma3TBEData.initiatorNOC.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(decryptedDataTlvReader.Next());
if (decryptedDataTlvReader.GetTag() == AsTlvContextTag(TBEDataTags::kSenderICAC))
{
ReturnErrorOnFailure(decryptedDataTlvReader.GetByteView(outHandleSigma3TBEData.initiatorICAC));
VerifyOrReturnError(outHandleSigma3TBEData.initiatorICAC.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(decryptedDataTlvReader.Next(TLV::kTLVType_ByteString, AsTlvContextTag(TBEDataTags::kSignature)));
}
VerifyOrReturnError(decryptedDataTlvReader.GetTag() == AsTlvContextTag(TBEDataTags::kSignature), CHIP_ERROR_INVALID_TLV_TAG);
size_t signatureLen = decryptedDataTlvReader.GetLength();
VerifyOrReturnError(outHandleSigma3TBEData.tbsData3Signature.Capacity() == signatureLen, CHIP_ERROR_INVALID_TLV_ELEMENT);
outHandleSigma3TBEData.tbsData3Signature.SetLength(signatureLen);
ReturnErrorOnFailure(decryptedDataTlvReader.GetBytes(outHandleSigma3TBEData.tbsData3Signature.Bytes(),
outHandleSigma3TBEData.tbsData3Signature.Length()));
ReturnErrorOnFailure(decryptedDataTlvReader.ExitContainer(containerType));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::HandleSigma3b(HandleSigma3Data & data, bool & cancel)
{
// Step 5/6
// Validate initiator identity located in msg->Start()
// Constructing responder identity
CompressedFabricId unused;
FabricId initiatorFabricId;
P256PublicKey initiatorPublicKey;
ReturnErrorOnFailure(FabricTable::VerifyCredentials(data.initiatorNOC, data.initiatorICAC, data.fabricRCAC, data.validContext,
unused, initiatorFabricId, data.initiatorNodeId, initiatorPublicKey));
VerifyOrReturnError(data.fabricId == initiatorFabricId, CHIP_ERROR_INVALID_CASE_PARAMETER);
// Step 7 - Validate Signature
ReturnErrorOnFailure(initiatorPublicKey.ECDSA_validate_msg_signature(data.msgR3SignedSpan.data(), data.msgR3SignedSpan.size(),
data.tbsData3Signature));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::HandleSigma3c(HandleSigma3Data & data, CHIP_ERROR status)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(mState == State::kHandleSigma3Pending, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = status);
mPeerNodeId = data.initiatorNodeId;
{
MutableByteSpan messageDigestSpan(mMessageDigest);
SuccessOrExit(err = mCommissioningHash.Finish(messageDigestSpan));
}
// Retrieve peer CASE Authenticated Tags (CATs) from peer's NOC.
{
SuccessOrExit(err = ExtractCATsFromOpCert(data.initiatorNOC, mPeerCATs));
}
if (mSessionResumptionStorage != nullptr)
{
CHIP_ERROR err2 = mSessionResumptionStorage->Save(GetPeer(), mNewResumptionId, mSharedSecret, mPeerCATs);
if (err2 != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Unable to save session resumption state: %" CHIP_ERROR_FORMAT, err2.Format());
}
}
MATTER_LOG_METRIC(kMetricDeviceCASESessionSigmaFinished);
SendStatusReport(mExchangeCtxt, kProtocolCodeSuccess);
mState = State::kFinished;
Finish();
exit:
mHandleSigma3Helper.reset();
if (err != CHIP_NO_ERROR)
{
SendStatusReport(mExchangeCtxt, kProtocolCodeInvalidParam);
// Abort the pending establish, which is normally done by CASESession::OnMessageReceived,
// but in the background processing case must be done here.
DiscardExchange();
AbortPendingEstablish(err);
}
return err;
}
CHIP_ERROR CASESession::DeriveSigmaKey(const ByteSpan & salt, const ByteSpan & info, AutoReleaseSessionKey & key) const
{
return mSessionManager->GetSessionKeystore()->DeriveKey(mSharedSecret, salt, info, key.KeyHandle());
}
CHIP_ERROR CASESession::ConstructSaltSigma2(const ByteSpan & rand, const Crypto::P256PublicKey & pubkey, const ByteSpan & ipk,
MutableByteSpan & salt)
{
uint8_t md[kSHA256_Hash_Length];
memset(salt.data(), 0, salt.size());
Encoding::LittleEndian::BufferWriter bbuf(salt.data(), salt.size());
bbuf.Put(ipk.data(), ipk.size());
bbuf.Put(rand.data(), kSigmaParamRandomNumberSize);
bbuf.Put(pubkey, pubkey.Length());
MutableByteSpan messageDigestSpan(md);
ReturnErrorOnFailure(mCommissioningHash.GetDigest(messageDigestSpan));
bbuf.Put(messageDigestSpan.data(), messageDigestSpan.size());
size_t saltWritten = 0;
VerifyOrReturnError(bbuf.Fit(saltWritten), CHIP_ERROR_BUFFER_TOO_SMALL);
salt = salt.SubSpan(0, saltWritten);
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ConstructSaltSigma3(const ByteSpan & ipk, MutableByteSpan & salt)
{
uint8_t md[kSHA256_Hash_Length];
memset(salt.data(), 0, salt.size());
Encoding::LittleEndian::BufferWriter bbuf(salt.data(), salt.size());
bbuf.Put(ipk.data(), ipk.size());
MutableByteSpan messageDigestSpan(md);
ReturnErrorOnFailure(mCommissioningHash.GetDigest(messageDigestSpan));
bbuf.Put(messageDigestSpan.data(), messageDigestSpan.size());
size_t saltWritten = 0;
VerifyOrReturnError(bbuf.Fit(saltWritten), CHIP_ERROR_BUFFER_TOO_SMALL);
salt = salt.SubSpan(0, saltWritten);
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ConstructSigmaResumeKey(const ByteSpan & initiatorRandom, const ByteSpan & resumptionID,
const ByteSpan & skInfo, const ByteSpan & nonce, AutoReleaseSessionKey & resumeKey)
{
constexpr size_t saltSize = kSigmaParamRandomNumberSize + SessionResumptionStorage::kResumptionIdSize;
uint8_t salt[saltSize];
memset(salt, 0, saltSize);
Encoding::LittleEndian::BufferWriter bbuf(salt, saltSize);
bbuf.Put(initiatorRandom.data(), initiatorRandom.size());
bbuf.Put(resumptionID.data(), resumptionID.size());
size_t saltWritten = 0;
VerifyOrReturnError(bbuf.Fit(saltWritten), CHIP_ERROR_BUFFER_TOO_SMALL);
return DeriveSigmaKey(ByteSpan(salt, saltWritten), skInfo, resumeKey);
}
CHIP_ERROR CASESession::GenerateSigmaResumeMIC(const ByteSpan & initiatorRandom, const ByteSpan & resumptionID,
const ByteSpan & skInfo, const ByteSpan & nonce, MutableByteSpan & resumeMIC)
{
VerifyOrReturnError(resumeMIC.size() >= CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, CHIP_ERROR_BUFFER_TOO_SMALL);
AutoReleaseSessionKey srk(*mSessionManager->GetSessionKeystore());
ReturnErrorOnFailure(ConstructSigmaResumeKey(initiatorRandom, resumptionID, skInfo, nonce, srk));
ReturnErrorOnFailure(AES_CCM_encrypt(nullptr, 0, nullptr, 0, srk.KeyHandle(), nonce.data(), nonce.size(), nullptr,
resumeMIC.data(), CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES));
resumeMIC.reduce_size(CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES);
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ValidateSigmaResumeMIC(const ByteSpan & resumeMIC, const ByteSpan & initiatorRandom,
const ByteSpan & resumptionID, const ByteSpan & skInfo, const ByteSpan & nonce)
{
VerifyOrReturnError(resumeMIC.size() == CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES, CHIP_ERROR_BUFFER_TOO_SMALL);
AutoReleaseSessionKey srk(*mSessionManager->GetSessionKeystore());
ReturnErrorOnFailure(ConstructSigmaResumeKey(initiatorRandom, resumptionID, skInfo, nonce, srk));
ReturnErrorOnFailure(AES_CCM_decrypt(nullptr, 0, nullptr, 0, resumeMIC.data(), resumeMIC.size(), srk.KeyHandle(), nonce.data(),
nonce.size(), nullptr));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ConstructTBSData(const ByteSpan & senderNOC, const ByteSpan & senderICAC, const ByteSpan & senderPubKey,
const ByteSpan & receiverPubKey, MutableByteSpan & outTbsData)
{
TLVWriter tlvWriter;
TLVType outerContainerType = kTLVType_NotSpecified;
tlvWriter.Init(outTbsData);
ReturnErrorOnFailure(tlvWriter.StartContainer(AnonymousTag(), kTLVType_Structure, outerContainerType));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBSDataTags::kSenderNOC), senderNOC));
if (!senderICAC.empty())
{
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBSDataTags::kSenderICAC), senderICAC));
}
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBSDataTags::kSenderPubKey), senderPubKey));
ReturnErrorOnFailure(tlvWriter.Put(AsTlvContextTag(TBSDataTags::kReceiverPubKey), receiverPubKey));
ReturnErrorOnFailure(tlvWriter.EndContainer(outerContainerType));
ReturnErrorOnFailure(tlvWriter.Finalize());
outTbsData.reduce_size(static_cast<size_t>(tlvWriter.GetLengthWritten()));
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::SetEffectiveTime()
{
System::Clock::Milliseconds64 currentUnixTimeMS;
CHIP_ERROR err = System::SystemClock().GetClock_RealTimeMS(currentUnixTimeMS);
if (err == CHIP_NO_ERROR)
{
// If the system has given us a wall clock time, we must use it or
// fail. Conversion failures here are therefore always an error.
System::Clock::Seconds32 currentUnixTime = std::chrono::duration_cast<System::Clock::Seconds32>(currentUnixTimeMS);
ReturnErrorOnFailure(mValidContext.SetEffectiveTimeFromUnixTime<CurrentChipEpochTime>(currentUnixTime));
}
else
{
// If we don't have wall clock time, the spec dictates that we should
// fall back to Last Known Good Time. Ultimately, the calling application's
// validity policy will determine whether this is permissible.
System::Clock::Seconds32 lastKnownGoodChipEpochTime;
ChipLogError(SecureChannel,
"The device does not support GetClock_RealTimeMS() API: %" CHIP_ERROR_FORMAT
". Falling back to Last Known Good UTC Time",
err.Format());
VerifyOrReturnError(mFabricsTable != nullptr, CHIP_ERROR_INCORRECT_STATE);
err = mFabricsTable->GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime);
if (err != CHIP_NO_ERROR)
{
// If we have no time available, the Validity Policy will
// determine what to do.
ChipLogError(SecureChannel, "Failed to retrieve Last Known Good UTC Time");
}
else
{
mValidContext.SetEffectiveTime<LastKnownGoodChipEpochTime>(lastKnownGoodChipEpochTime);
}
}
return CHIP_NO_ERROR;
}
void CASESession::OnSuccessStatusReport()
{
ChipLogProgress(SecureChannel, "Success status report received. Session was established");
if (mSessionResumptionStorage != nullptr)
{
CHIP_ERROR err2 = mSessionResumptionStorage->Save(GetPeer(), mNewResumptionId, mSharedSecret, mPeerCATs);
if (err2 != CHIP_NO_ERROR)
ChipLogError(SecureChannel, "Unable to save session resumption state: %" CHIP_ERROR_FORMAT, err2.Format());
}
switch (mState)
{
case State::kSentSigma3:
mState = State::kFinished;
break;
case State::kSentSigma2Resume:
mState = State::kFinishedViaResume;
break;
default:
VerifyOrDie(false && "Reached invalid internal state keeping in CASE session");
break;
}
Finish();
}
CHIP_ERROR CASESession::OnFailureStatusReport(Protocols::SecureChannel::GeneralStatusCode generalCode, uint16_t protocolCode,
Optional<uintptr_t> protocolData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
switch (protocolCode)
{
case kProtocolCodeInvalidParam:
err = CHIP_ERROR_INVALID_CASE_PARAMETER;
break;
case kProtocolCodeNoSharedRoot:
err = CHIP_ERROR_NO_SHARED_TRUSTED_ROOT;
break;
case kProtocolCodeBusy:
err = CHIP_ERROR_BUSY;
if (protocolData.HasValue())
{
mDelegate->OnResponderBusy(System::Clock::Milliseconds16(static_cast<uint16_t>(protocolData.Value())));
}
break;
default:
err = CHIP_ERROR_INTERNAL;
break;
};
mState = State::kInitialized;
ChipLogError(SecureChannel, "Received error (protocol code %d) during pairing process: %" CHIP_ERROR_FORMAT, protocolCode,
err.Format());
return err;
}
CHIP_ERROR CASESession::ParseSigma1(TLV::ContiguousBufferTLVReader & tlvReader, ParsedSigma1 & outParsedSigma1)
{
TLVType containerType = kTLVType_Structure;
ReturnErrorOnFailure(tlvReader.Next(containerType, AnonymousTag()));
ReturnErrorOnFailure(tlvReader.EnterContainer(containerType));
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma1Tags::kInitiatorRandom)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma1.initiatorRandom));
VerifyOrReturnError(outParsedSigma1.initiatorRandom.size() == kSigmaParamRandomNumberSize, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma1Tags::kInitiatorSessionId)));
ReturnErrorOnFailure(tlvReader.Get(outParsedSigma1.initiatorSessionId));
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma1Tags::kDestinationId)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma1.destinationId));
VerifyOrReturnError(outParsedSigma1.destinationId.size() == kSHA256_Hash_Length, CHIP_ERROR_INVALID_CASE_PARAMETER);
ReturnErrorOnFailure(tlvReader.Next(AsTlvContextTag(Sigma1Tags::kInitiatorEphPubKey)));
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma1.initiatorEphPubKey));
VerifyOrReturnError(outParsedSigma1.initiatorEphPubKey.size() == kP256_PublicKey_Length, CHIP_ERROR_INVALID_CASE_PARAMETER);
// Optional members start here.
CHIP_ERROR err = tlvReader.Next();
if (err == CHIP_NO_ERROR && tlvReader.GetTag() == AsTlvContextTag(Sigma1Tags::kInitiatorSessionParams))
{
ReturnErrorOnFailure(DecodeSessionParametersIfPresent(AsTlvContextTag(Sigma1Tags::kInitiatorSessionParams), tlvReader,
outParsedSigma1.initiatorSessionParams));
outParsedSigma1.initiatorSessionParamStructPresent = true;
err = tlvReader.Next();
}
bool resumptionIDTagFound = false;
bool resume1MICTagFound = false;
if (err == CHIP_NO_ERROR && tlvReader.GetTag() == AsTlvContextTag(Sigma1Tags::kResumptionID))
{
resumptionIDTagFound = true;
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma1.resumptionId));
VerifyOrReturnError(outParsedSigma1.resumptionId.size() == SessionResumptionStorage::kResumptionIdSize,
CHIP_ERROR_INVALID_CASE_PARAMETER);
err = tlvReader.Next();
}
if (err == CHIP_NO_ERROR && tlvReader.GetTag() == AsTlvContextTag(Sigma1Tags::kResume1MIC))
{
resume1MICTagFound = true;
ReturnErrorOnFailure(tlvReader.GetByteView(outParsedSigma1.initiatorResumeMIC));
VerifyOrReturnError(outParsedSigma1.initiatorResumeMIC.size() == CHIP_CRYPTO_AEAD_MIC_LENGTH_BYTES,
CHIP_ERROR_INVALID_CASE_PARAMETER);
err = tlvReader.Next();
}
if (err == CHIP_END_OF_TLV)
{
// We ran out of struct members, but that's OK, because they were optional.
err = CHIP_NO_ERROR;
}
ReturnErrorOnFailure(err);
ReturnErrorOnFailure(tlvReader.ExitContainer(containerType));
if (resumptionIDTagFound && resume1MICTagFound)
{
outParsedSigma1.sessionResumptionRequested = true;
}
else if (!resumptionIDTagFound && !resume1MICTagFound)
{
outParsedSigma1.sessionResumptionRequested = false;
}
else
{
return CHIP_ERROR_UNEXPECTED_TLV_ELEMENT;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::ValidateReceivedMessage(ExchangeContext * ec, const PayloadHeader & payloadHeader,
const System::PacketBufferHandle & msg)
{
VerifyOrReturnError(ec != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// mExchangeCtxt can be nullptr if this is the first message (CASE_Sigma1) received by CASESession
// via UnsolicitedMessageHandler. The exchange context is allocated by exchange manager and provided
// to the handler (CASESession object).
if (mExchangeCtxt.HasValue())
{
if (&mExchangeCtxt.Value().Get() != ec)
{
ReturnErrorOnFailure(CHIP_ERROR_INVALID_ARGUMENT);
}
}
else
{
mExchangeCtxt.Emplace(*ec);
}
mExchangeCtxt.Value()->UseSuggestedResponseTimeout(kExpectedHighProcessingTime);
VerifyOrReturnError(!msg.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
return CHIP_NO_ERROR;
}
CHIP_ERROR CASESession::OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && msg)
{
MATTER_TRACE_SCOPE("OnMessageReceived", "CASESession");
CHIP_ERROR err = ValidateReceivedMessage(ec, payloadHeader, msg);
Protocols::SecureChannel::MsgType msgType = static_cast<Protocols::SecureChannel::MsgType>(payloadHeader.GetMessageType());
SuccessOrExit(err);
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
if (mStopHandshakeAtState.HasValue() && mState == mStopHandshakeAtState.Value())
{
mStopHandshakeAtState = Optional<State>::Missing();
// For testing purposes we are trying to stop a successful CASESession from happening by dropping part of the
// handshake in the middle. We are trying to keep both sides of the CASESession establishment in an active
// pending state. In order to keep this side open we have to tell the exchange context that we will send an
// async message.
//
// Should you need to resume the CASESession, you could theoretically pass along the msg to a callback that gets
// registered when setting mStopHandshakeAtState.
mExchangeCtxt.Value()->WillSendMessage();
return CHIP_NO_ERROR;
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
#if CHIP_CONFIG_SLOW_CRYPTO
if ((msgType == Protocols::SecureChannel::MsgType::CASE_Sigma1 || msgType == Protocols::SecureChannel::MsgType::CASE_Sigma2 ||
msgType == Protocols::SecureChannel::MsgType::CASE_Sigma2Resume ||
msgType == Protocols::SecureChannel::MsgType::CASE_Sigma3) &&
mExchangeCtxt.Value()->GetSessionHandle()->AsUnauthenticatedSession()->GetPeerAddress().GetTransportType() !=
Transport::Type::kTcp)
{
// TODO: Rename FlushAcks() to something more semantically correct and
// call unconditionally for TCP or MRP from here. Inside, the
// PeerAddress type could be consulted to selectively flush MRP Acks
// when transport is not TCP. Issue #33183
SuccessOrExit(err = mExchangeCtxt.Value()->FlushAcks());
}
#endif // CHIP_CONFIG_SLOW_CRYPTO
// By default, CHIP_ERROR_INVALID_MESSAGE_TYPE is returned if in the current state
// a message handler is not defined for the received message type.
err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
switch (mState)
{
case State::kInitialized:
if (msgType == Protocols::SecureChannel::MsgType::CASE_Sigma1)
{
err = HandleSigma1_and_SendSigma2(std::move(msg));
}
break;
case State::kSentSigma1:
switch (static_cast<Protocols::SecureChannel::MsgType>(payloadHeader.GetMessageType()))
{
case Protocols::SecureChannel::MsgType::CASE_Sigma2:
err = HandleSigma2_and_SendSigma3(std::move(msg));
break;
case MsgType::StatusReport:
err = HandleStatusReport(std::move(msg), /* successExpected*/ false);
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma1, err);
break;
default:
// Return the default error that was set above
break;
};
break;
case State::kSentSigma1Resume:
switch (static_cast<Protocols::SecureChannel::MsgType>(payloadHeader.GetMessageType()))
{
case Protocols::SecureChannel::MsgType::CASE_Sigma2:
err = HandleSigma2_and_SendSigma3(std::move(msg));
break;
case Protocols::SecureChannel::MsgType::CASE_Sigma2Resume:
err = HandleSigma2Resume(std::move(msg));
break;
case MsgType::StatusReport:
err = HandleStatusReport(std::move(msg), /* successExpected*/ false);
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma1, err);
break;
default:
// Return the default error that was set above
break;
};
break;
case State::kSentSigma2:
switch (static_cast<Protocols::SecureChannel::MsgType>(payloadHeader.GetMessageType()))
{
case Protocols::SecureChannel::MsgType::CASE_Sigma3:
err = HandleSigma3a(std::move(msg));
break;
case MsgType::StatusReport:
err = HandleStatusReport(std::move(msg), /* successExpected*/ false);
MATTER_LOG_METRIC_END(kMetricDeviceCASESessionSigma2, err);
break;
default:
// Return the default error that was set above
break;
};
break;
case State::kSentSigma3:
case State::kSentSigma2Resume:
if (msgType == Protocols::SecureChannel::MsgType::StatusReport)
{
// Need to capture before invoking status report since 'this' might be deallocated on successful completion of
// sigma3
MetricKey key = (mState == State::kSentSigma3) ? kMetricDeviceCASESessionSigma3 : kMetricDeviceCASESessionSigma2Resume;
err = HandleStatusReport(std::move(msg), /* successExpected*/ true);
MATTER_LOG_METRIC_END(key, err);
IgnoreUnusedVariable(key);
}
break;
default:
// Return the default error that was set above
break;
};
exit:
if (err == CHIP_ERROR_INVALID_MESSAGE_TYPE)
{
ChipLogError(SecureChannel, "Received message (type %d) cannot be handled in %d state.", to_underlying(msgType),
to_underlying(mState));
}
// Call delegate to indicate session establishment failure.
if (err != CHIP_NO_ERROR)
{
// Discard the exchange so that Clear() doesn't try aborting it. The
// exchange will handle that.
DiscardExchange();
AbortPendingEstablish(err);
}
return err;
}
namespace {
System::Clock::Timeout ComputeRoundTripTimeout(ExchangeContext::Timeout serverProcessingTime,
const ReliableMessageProtocolConfig & remoteMrpConfig, bool isFirstMessageOnExchange)
{
// TODO: This is duplicating logic from Session::ComputeRoundTripTimeout. Unfortunately, it's called by
// consumers who do not have a session.
const auto & maybeLocalMRPConfig = GetLocalMRPConfig();
const auto & defaultMRRPConfig = GetDefaultMRPConfig();
const auto & localMRPConfig = maybeLocalMRPConfig.ValueOr(defaultMRRPConfig);
return GetRetransmissionTimeout(remoteMrpConfig.mActiveRetransTimeout, remoteMrpConfig.mIdleRetransTimeout,
// The activity time only matters when isFirstMessageOnExchange is false,
// which only happens for Sigma1. In that case, assume peer is idle,
// as a worst-case assumption, and pass System::Clock::kZero.
System::Clock::kZero, remoteMrpConfig.mActiveThresholdTime, isFirstMessageOnExchange) +
serverProcessingTime +
GetRetransmissionTimeout(localMRPConfig.mActiveRetransTimeout, localMRPConfig.mIdleRetransTimeout,
// Peer will be responding to our message, so isFirstMessageOnExchange should be false
// and the timestamp does not matter.
System::SystemClock().GetMonotonicTimestamp(), localMRPConfig.mActiveThresholdTime,
false /*isFirstMessageOnExchange*/);
}
} // anonymous namespace
System::Clock::Timeout CASESession::ComputeSigma1ResponseTimeout(const ReliableMessageProtocolConfig & remoteMrpConfig)
{
// Sigma1 is the first message on the exchange.
return ComputeRoundTripTimeout(kExpectedSigma1ProcessingTime, remoteMrpConfig, true /*isFirstMessageOnExchange*/);
}
System::Clock::Timeout CASESession::ComputeSigma2ResponseTimeout(const ReliableMessageProtocolConfig & remoteMrpConfig)
{
// Sigma2 is never the first message on the exchange.
return ComputeRoundTripTimeout(kExpectedHighProcessingTime, remoteMrpConfig, false /*isFirstMessageOnExchange*/);
}
bool CASESession::InvokeBackgroundWorkWatchdog()
{
bool watchdogFired = false;
if (mSendSigma3Helper && mSendSigma3Helper->UnableToScheduleAfterWorkCallback())
{
ChipLogError(SecureChannel, "SendSigma3Helper was unable to schedule the AfterWorkCallback");
mSendSigma3Helper->DoAfterWork();
watchdogFired = true;
}
if (mHandleSigma3Helper && mHandleSigma3Helper->UnableToScheduleAfterWorkCallback())
{
ChipLogError(SecureChannel, "HandleSigma3Helper was unable to schedule the AfterWorkCallback");
mHandleSigma3Helper->DoAfterWork();
watchdogFired = true;
}
return watchdogFired;
}
// Helper function to map CASESession::State to SessionEstablishmentStage
SessionEstablishmentStage CASESession::MapCASEStateToSessionEstablishmentStage(State caseState)
{
switch (caseState)
{
case State::kInitialized:
return SessionEstablishmentStage::kNotInKeyExchange;
case State::kSentSigma1:
case State::kSentSigma1Resume:
return SessionEstablishmentStage::kSentSigma1;
case State::kSentSigma2:
case State::kSentSigma2Resume:
return SessionEstablishmentStage::kSentSigma2;
case State::kSendSigma3Pending:
return SessionEstablishmentStage::kReceivedSigma2;
case State::kSentSigma3:
return SessionEstablishmentStage::kSentSigma3;
case State::kHandleSigma3Pending:
return SessionEstablishmentStage::kReceivedSigma3;
// Add more mappings here for other states
default:
return SessionEstablishmentStage::kUnknown; // Default mapping
}
}
} // namespace chip