blob: 26a245618e1d264ee3733ca63d9b3169c77a8d38 [file] [log] [blame]
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file implements the CHIP message counter messages in secure channel protocol.
*
*/
#include <protocols/secure_channel/MessageCounterManager.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPKeyIds.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <protocols/Protocols.h>
#include <protocols/secure_channel/Constants.h>
namespace chip {
namespace secure_channel {
constexpr System::Clock::Timeout MessageCounterManager::kSyncTimeout;
CHIP_ERROR MessageCounterManager::Init(Messaging::ExchangeManager * exchangeMgr)
{
VerifyOrReturnError(exchangeMgr != nullptr, CHIP_ERROR_INCORRECT_STATE);
mExchangeMgr = exchangeMgr;
ReturnErrorOnFailure(
mExchangeMgr->RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::MsgCounterSyncReq, this));
return CHIP_NO_ERROR;
}
void MessageCounterManager::Shutdown()
{
if (mExchangeMgr != nullptr)
{
mExchangeMgr->UnregisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::MsgCounterSyncReq);
mExchangeMgr->CloseAllContextsForDelegate(this);
mExchangeMgr = nullptr;
}
}
CHIP_ERROR MessageCounterManager::StartSync(const SessionHandle & session, Transport::SecureSession * state)
{
// Initiate message counter synchronization if no message counter synchronization is in progress.
Transport::PeerMessageCounter & counter = state->GetSessionMessageCounter().GetPeerMessageCounter();
if (!counter.IsSynchronizing() && !counter.IsSynchronized())
{
ReturnErrorOnFailure(SendMsgCounterSyncReq(session, state));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR MessageCounterManager::QueueReceivedMessageAndStartSync(const PacketHeader & packetHeader, const SessionHandle & session,
Transport::SecureSession * state,
const Transport::PeerAddress & peerAddress,
System::PacketBufferHandle && msgBuf)
{
// Queue the message to be reprocessed when sync completes.
ReturnErrorOnFailure(AddToReceiveTable(packetHeader, peerAddress, std::move(msgBuf)));
ReturnErrorOnFailure(StartSync(session, state));
// After the message that triggers message counter synchronization is stored, and a message counter
// synchronization exchange is initiated, we need to return immediately and re-process the original message
// when the synchronization is completed.
return CHIP_NO_ERROR;
}
CHIP_ERROR MessageCounterManager::OnUnsolicitedMessageReceived(const PayloadHeader & payloadHeader, ExchangeDelegate *& newDelegate)
{
// MessageCounterManager do not use an extra context to handle messages
newDelegate = this;
return CHIP_NO_ERROR;
}
CHIP_ERROR MessageCounterManager::OnMessageReceived(Messaging::ExchangeContext * exchangeContext,
const PayloadHeader & payloadHeader, System::PacketBufferHandle && msgBuf)
{
if (payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncReq))
{
return HandleMsgCounterSyncReq(exchangeContext, std::move(msgBuf));
}
if (payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncRsp))
{
return HandleMsgCounterSyncResp(exchangeContext, std::move(msgBuf));
}
return CHIP_NO_ERROR;
}
void MessageCounterManager::OnResponseTimeout(Messaging::ExchangeContext * exchangeContext)
{
if (exchangeContext->HasSessionHandle())
{
exchangeContext->GetSessionHandle()->AsSecureSession()->GetSessionMessageCounter().GetPeerMessageCounter().SyncFailed();
}
else
{
ChipLogError(SecureChannel, "MCSP Timeout! On a already released session.");
}
}
CHIP_ERROR MessageCounterManager::AddToReceiveTable(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress,
System::PacketBufferHandle && msgBuf)
{
ReturnErrorOnFailure(packetHeader.EncodeBeforeData(msgBuf));
for (ReceiveTableEntry & entry : mReceiveTable)
{
if (entry.msgBuf.IsNull())
{
entry.peerAddress = peerAddress;
entry.msgBuf = std::move(msgBuf);
return CHIP_NO_ERROR;
}
}
ChipLogError(SecureChannel, "MCSP ReceiveTable Already Full");
return CHIP_ERROR_NO_MEMORY;
}
/**
* Reprocess all pending messages that were encrypted with application
* group key and were addressed to the specified node id.
*
* @param[in] peerNodeId Node ID of the destination node.
*
*/
void MessageCounterManager::ProcessPendingMessages(NodeId peerNodeId)
{
auto * sessionManager = mExchangeMgr->GetSessionManager();
// Find all receive entries matching peerNodeId. Note that everything in
// this table was using an application group key; that's why it was added.
for (ReceiveTableEntry & entry : mReceiveTable)
{
if (!entry.msgBuf.IsNull())
{
PacketHeader packetHeader;
uint16_t headerSize = 0;
if (packetHeader.Decode((entry.msgBuf)->Start(), (entry.msgBuf)->DataLength(), &headerSize) != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "MessageCounterManager::ProcessPendingMessages: Failed to decode PacketHeader");
entry.msgBuf = nullptr;
continue;
}
if (packetHeader.GetSourceNodeId().HasValue() && packetHeader.GetSourceNodeId().Value() == peerNodeId)
{
// Reprocess message.
sessionManager->OnMessageReceived(entry.peerAddress, std::move(entry.msgBuf));
// Explicitly free any buffer owned by this handle.
entry.msgBuf = nullptr;
}
}
}
}
CHIP_ERROR MessageCounterManager::SendMsgCounterSyncReq(const SessionHandle & session, Transport::SecureSession * state)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Messaging::ExchangeContext * exchangeContext = nullptr;
System::PacketBufferHandle msgBuf;
Messaging::SendFlags sendFlags;
exchangeContext = mExchangeMgr->NewContext(session, this);
VerifyOrExit(exchangeContext != nullptr, err = CHIP_ERROR_NO_MEMORY);
msgBuf = MessagePacketBuffer::New(kChallengeSize);
VerifyOrExit(!msgBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
// Generate a 64-bit random number to uniquely identify the request.
SuccessOrExit(err = Crypto::DRBG_get_bytes(msgBuf->Start(), kChallengeSize));
msgBuf->SetDataLength(kChallengeSize);
// Store generated Challenge value to message counter context to resolve synchronization response.
state->GetSessionMessageCounter().GetPeerMessageCounter().SyncStarting(FixedByteSpan<kChallengeSize>(msgBuf->Start()));
sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck).Set(Messaging::SendMessageFlags::kExpectResponse);
// Arm a timer to enforce that a MsgCounterSyncRsp is received before kSyncTimeout.
exchangeContext->SetResponseTimeout(kSyncTimeout);
// Send the message counter synchronization request in a Secure Channel Protocol::MsgCounterSyncReq message.
SuccessOrExit(
err = exchangeContext->SendMessage(Protocols::SecureChannel::MsgType::MsgCounterSyncReq, std::move(msgBuf), sendFlags));
exit:
if (err != CHIP_NO_ERROR)
{
if (exchangeContext != nullptr)
{
exchangeContext->Close();
}
state->GetSessionMessageCounter().GetPeerMessageCounter().SyncFailed();
ChipLogError(SecureChannel, "Failed to send message counter synchronization request with error:%" CHIP_ERROR_FORMAT,
err.Format());
}
return err;
}
CHIP_ERROR MessageCounterManager::SendMsgCounterSyncResp(Messaging::ExchangeContext * exchangeContext,
FixedByteSpan<kChallengeSize> challenge)
{
System::PacketBufferHandle msgBuf;
VerifyOrDie(exchangeContext->HasSessionHandle());
VerifyOrReturnError(exchangeContext->GetSessionHandle()->IsGroupSession(), CHIP_ERROR_INVALID_ARGUMENT);
// NOTE: not currently implemented. When implementing, the following should be done:
// - allocate a new buffer: MessagePacketBuffer::New
// - setup payload and place the local message counter + challange in it
// - exchangeContext->SendMessage(Protocols::SecureChannel::MsgType::MsgCounterSyncRsp, ...)
//
// You can view the history of this file for a partial implementation that got
// removed due to it using non-group sessions.
return CHIP_ERROR_NOT_IMPLEMENTED;
}
CHIP_ERROR MessageCounterManager::HandleMsgCounterSyncReq(Messaging::ExchangeContext * exchangeContext,
System::PacketBufferHandle && msgBuf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t * req = msgBuf->Start();
size_t reqlen = msgBuf->DataLength();
ChipLogDetail(SecureChannel, "Received MsgCounterSyncReq request");
VerifyOrExit(req != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(reqlen == kChallengeSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
// Respond with MsgCounterSyncResp
err = SendMsgCounterSyncResp(exchangeContext, FixedByteSpan<kChallengeSize>(req));
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Failed to handle MsgCounterSyncReq message with error:%" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
CHIP_ERROR MessageCounterManager::HandleMsgCounterSyncResp(Messaging::ExchangeContext * exchangeContext,
System::PacketBufferHandle && msgBuf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint32_t syncCounter = 0;
const uint8_t * resp = msgBuf->Start();
size_t resplen = msgBuf->DataLength();
ChipLogDetail(SecureChannel, "Received MsgCounterSyncResp response");
VerifyOrDie(exchangeContext->HasSessionHandle());
VerifyOrExit(msgBuf->DataLength() == kSyncRespMsgSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
VerifyOrExit(resp != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(resplen == kSyncRespMsgSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
syncCounter = chip::Encoding::LittleEndian::Read32(resp);
VerifyOrExit(syncCounter != 0, err = CHIP_ERROR_READ_FAILED);
// Verify that the response field matches the expected Challenge field for the exchange.
err =
exchangeContext->GetSessionHandle()->AsSecureSession()->GetSessionMessageCounter().GetPeerMessageCounter().VerifyChallenge(
syncCounter, FixedByteSpan<kChallengeSize>(resp));
SuccessOrExit(err);
// Process all queued incoming messages after message counter synchronization is completed.
ProcessPendingMessages(exchangeContext->GetSessionHandle()->AsSecureSession()->GetPeerNodeId());
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(SecureChannel, "Failed to handle MsgCounterSyncResp message with error:%" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
} // namespace secure_channel
} // namespace chip