blob: a4ca0ef13abf2e0002ed364ba4ef7748be890639 [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 Secure Channel protocol.
*
*/
#include <core/CHIPCore.h>
#include <core/CHIPEncoding.h>
#include <core/CHIPKeyIds.h>
#include <messaging/ExchangeContext.h>
#include <messaging/ExchangeMgr.h>
#include <messaging/Flags.h>
#include <messaging/MessageCounterSync.h>
#include <protocols/Protocols.h>
#include <support/BufferWriter.h>
#include <support/ErrorMacros.h>
#include <support/ErrorStr.h>
#include <support/ReturnMacros.h>
#include <support/logging/CHIPLogging.h>
namespace chip {
namespace Messaging {
CHIP_ERROR MessageCounterSyncMgr::Init(Messaging::ExchangeManager * exchangeMgr)
{
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrReturnError(exchangeMgr != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
mExchangeMgr = exchangeMgr;
// Register to receive unsolicited Secure Channel Request messages from the exchange manager.
err = mExchangeMgr->RegisterUnsolicitedMessageHandlerForProtocol(Protocols::SecureChannel::Id, this);
ReturnErrorOnFailure(err);
return err;
}
void MessageCounterSyncMgr::Shutdown()
{
if (mExchangeMgr != nullptr)
{
mExchangeMgr->UnregisterUnsolicitedMessageHandlerForProtocol(Protocols::SecureChannel::Id);
mExchangeMgr = nullptr;
}
}
void MessageCounterSyncMgr::OnMessageReceived(Messaging::ExchangeContext * exchangeContext, const PacketHeader & packetHeader,
const PayloadHeader & payloadHeader, System::PacketBufferHandle msgBuf)
{
if (payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncReq))
{
HandleMsgCounterSyncReq(exchangeContext, packetHeader, std::move(msgBuf));
}
else if (payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncRsp))
{
HandleMsgCounterSyncResp(exchangeContext, packetHeader, std::move(msgBuf));
}
}
void MessageCounterSyncMgr::OnResponseTimeout(Messaging::ExchangeContext * exchangeContext)
{
Transport::PeerConnectionState * state =
mExchangeMgr->GetSessionMgr()->GetPeerConnectionState(exchangeContext->GetSecureSessionHandle());
if (state != nullptr)
{
state->SetMsgCounterSyncInProgress(false);
}
else
{
ChipLogError(ExchangeManager, "Timed out! Failed to clear message counter synchronization status.");
}
// Close the exchange if MsgCounterSyncRsp is not received before kMsgCounterSyncTimeout.
if (exchangeContext != nullptr)
exchangeContext->Close();
}
CHIP_ERROR MessageCounterSyncMgr::AddToRetransmissionTable(Protocols::Id protocolId, uint8_t msgType, const SendFlags & sendFlags,
System::PacketBufferHandle msgBuf,
Messaging::ExchangeContext * exchangeContext)
{
bool added = false;
CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrReturnError(exchangeContext != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
for (RetransTableEntry & entry : mRetransTable)
{
// Entries are in use if they have an exchangeContext.
if (entry.exchangeContext == nullptr)
{
entry.protocolId = protocolId;
entry.msgType = msgType;
entry.msgBuf = std::move(msgBuf);
entry.exchangeContext = exchangeContext;
entry.exchangeContext->Retain();
added = true;
break;
}
}
if (!added)
{
ChipLogError(ExchangeManager, "MCSP RetransTable Already Full");
err = CHIP_ERROR_NO_MEMORY;
}
return err;
}
/**
* Retransmit all pending messages that were encrypted with application
* group key and were addressed to the specified node.
*
* @param[in] peerNodeId Node ID of the destination node.
*
*/
void MessageCounterSyncMgr::RetransPendingGroupMsgs(NodeId peerNodeId)
{
// Find all retransmit entries matching peerNodeId. Note that everything in
// this table was using an application group key; that's why it was added.
for (RetransTableEntry & entry : mRetransTable)
{
if (entry.exchangeContext != nullptr && entry.exchangeContext->GetSecureSession().GetPeerNodeId() == peerNodeId)
{
// Retramsmit message.
CHIP_ERROR err =
entry.exchangeContext->SendMessage(entry.protocolId, entry.msgType, std::move(entry.msgBuf), entry.sendFlags);
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "Failed to resend cached group message to node: %d with error:%s", peerNodeId,
ErrorStr(err));
}
entry.exchangeContext->Release();
entry.exchangeContext = nullptr;
}
}
}
CHIP_ERROR MessageCounterSyncMgr::AddToReceiveTable(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader,
const SecureSessionHandle & session, System::PacketBufferHandle msgBuf)
{
bool added = false;
CHIP_ERROR err = CHIP_NO_ERROR;
for (ReceiveTableEntry & entry : mReceiveTable)
{
// Entries are in use if they have a message buffer.
if (entry.msgBuf.IsNull())
{
entry.packetHeader = packetHeader;
entry.payloadHeader = payloadHeader;
entry.session = session;
entry.msgBuf = std::move(msgBuf);
added = true;
break;
}
}
if (!added)
{
ChipLogError(ExchangeManager, "MCSP ReceiveTable Already Full");
err = CHIP_ERROR_NO_MEMORY;
}
return err;
}
/**
* 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 MessageCounterSyncMgr::ProcessPendingGroupMsgs(NodeId peerNodeId)
{
// 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() && entry.session.GetPeerNodeId() == peerNodeId)
{
// Reprocess message.
mExchangeMgr->HandleGroupMessageReceived(entry.packetHeader, entry.payloadHeader, entry.session,
std::move(entry.msgBuf));
// Explicitly free any buffer owned by this handle. The
// HandleGroupMessageReceived() call should really handle this, but
// just in case it messes up we don't want to get confused about
// wheter the entry is in use.
entry.msgBuf = nullptr;
}
}
}
// Create and initialize new exchange for the message counter synchronization request/response messages.
CHIP_ERROR MessageCounterSyncMgr::NewMsgCounterSyncExchange(SecureSessionHandle session,
Messaging::ExchangeContext *& exchangeContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// Message counter synchronization protocol is only applicable for application group keys.
VerifyOrReturnError(ChipKeyId::IsAppGroupKey(session.GetPeerKeyId()), err = CHIP_ERROR_INVALID_ARGUMENT);
// Create new exchange context.
exchangeContext = mExchangeMgr->NewContext(session, this);
VerifyOrReturnError(exchangeContext != nullptr, err = CHIP_ERROR_NO_MEMORY);
return err;
}
CHIP_ERROR MessageCounterSyncMgr::SendMsgCounterSyncReq(SecureSessionHandle session)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Messaging::ExchangeContext * exchangeContext = nullptr;
Transport::PeerConnectionState * state = nullptr;
System::PacketBufferHandle msgBuf;
Messaging::SendFlags sendFlags;
uint8_t challenge[kMsgCounterChallengeSize];
state = mExchangeMgr->GetSessionMgr()->GetPeerConnectionState(session);
VerifyOrExit(state != nullptr, err = CHIP_ERROR_NOT_CONNECTED);
// Create and initialize new exchange.
err = NewMsgCounterSyncExchange(session, exchangeContext);
SuccessOrExit(err);
// Allocate a buffer for the null message.
msgBuf = MessagePacketBuffer::New(kMsgCounterChallengeSize);
VerifyOrExit(!msgBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
// Generate a 64-bit random number to uniquely identify the request.
err = DRBG_get_bytes(challenge, kMsgCounterChallengeSize);
SuccessOrExit(err);
// Store generated Challenge value to ExchangeContext to resolve synchronization response.
exchangeContext->SetChallenge(challenge);
memcpy(msgBuf->Start(), challenge, kMsgCounterChallengeSize);
msgBuf->SetDataLength(kMsgCounterChallengeSize);
sendFlags.Set(Messaging::SendMessageFlags::kNoAutoRequestAck, true).Set(Messaging::SendMessageFlags::kExpectResponse, true);
// Arm a timer to enforce that a MsgCounterSyncRsp is received before kMsgCounterSyncTimeout.
exchangeContext->SetResponseTimeout(kMsgCounterSyncTimeout);
// Send the message counter synchronization request in a Secure Channel Protocol::MsgCounterSyncReq message.
err = exchangeContext->SendMessageImpl(Protocols::SecureChannel::Id,
static_cast<uint8_t>(Protocols::SecureChannel::MsgType::MsgCounterSyncReq),
std::move(msgBuf), sendFlags);
SuccessOrExit(err);
state->SetMsgCounterSyncInProgress(true);
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "Failed to send message counter synchronization request with error:%s", ErrorStr(err));
}
return err;
}
CHIP_ERROR MessageCounterSyncMgr::SendMsgCounterSyncResp(Messaging::ExchangeContext * exchangeContext, SecureSessionHandle session)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Transport::PeerConnectionState * state = nullptr;
System::PacketBufferHandle msgBuf;
uint8_t * msg = nullptr;
state = mExchangeMgr->GetSessionMgr()->GetPeerConnectionState(session);
VerifyOrExit(state != nullptr, err = CHIP_ERROR_NOT_CONNECTED);
// Allocate new buffer.
msgBuf = System::PacketBufferHandle::New(kMsgCounterSyncRespMsgSize);
VerifyOrExit(!msgBuf.IsNull(), err = CHIP_ERROR_NO_MEMORY);
msg = msgBuf->Start();
// Let's construct the message using BufBound
{
Encoding::LittleEndian::BufferWriter bbuf(msg, kMsgCounterSyncRespMsgSize);
// Write the message id (counter) field.
bbuf.Put32(state->GetSendMessageIndex());
// Fill in the random value
bbuf.Put(exchangeContext->GetChallenge(), kMsgCounterChallengeSize);
VerifyOrExit(bbuf.Fit(), err = CHIP_ERROR_NO_MEMORY);
}
// Set message length.
msgBuf->SetDataLength(kMsgCounterSyncRespMsgSize);
// Send message counter synchronization response message.
err = exchangeContext->SendMessageImpl(Protocols::SecureChannel::Id,
static_cast<uint8_t>(Protocols::SecureChannel::MsgType::MsgCounterSyncRsp),
std::move(msgBuf), Messaging::SendFlags(Messaging::SendMessageFlags::kNoAutoRequestAck));
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "Failed to send message counter synchronization response with error:%s", ErrorStr(err));
}
return err;
}
void MessageCounterSyncMgr::HandleMsgCounterSyncReq(Messaging::ExchangeContext * exchangeContext, const PacketHeader & packetHeader,
System::PacketBufferHandle msgBuf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
const uint8_t * req = msgBuf->Start();
size_t reqlen = msgBuf->DataLength();
ChipLogDetail(ExchangeManager, "Received MsgCounterSyncReq request");
VerifyOrExit(packetHeader.GetSourceNodeId().HasValue(), err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(ChipKeyId::IsAppGroupKey(packetHeader.GetEncryptionKeyID()), err = CHIP_ERROR_WRONG_KEY_TYPE);
VerifyOrExit(req != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(reqlen == kMsgCounterChallengeSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
// Store the 64-bit value sent in the Challenge filed of the MsgCounterSyncReq.
exchangeContext->SetChallenge(req);
// Respond with MsgCounterSyncResp
err = SendMsgCounterSyncResp(exchangeContext, { packetHeader.GetSourceNodeId().Value(), packetHeader.GetEncryptionKeyID(), 0 });
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "Failed to handle MsgCounterSyncReq message with error:%s", ErrorStr(err));
}
if (exchangeContext != nullptr)
exchangeContext->Close();
return;
}
void MessageCounterSyncMgr::HandleMsgCounterSyncResp(Messaging::ExchangeContext * exchangeContext,
const PacketHeader & packetHeader, System::PacketBufferHandle msgBuf)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Transport::PeerConnectionState * state = nullptr;
NodeId peerNodeId = 0;
uint32_t syncCounter = 0;
uint8_t challenge[kMsgCounterChallengeSize];
const uint8_t * resp = msgBuf->Start();
size_t resplen = msgBuf->DataLength();
ChipLogDetail(ExchangeManager, "Received MsgCounterSyncResp response");
// Find an active connection to the specified peer node
state = mExchangeMgr->GetSessionMgr()->GetPeerConnectionState(exchangeContext->GetSecureSessionHandle());
VerifyOrExit(state != nullptr, err = CHIP_ERROR_NOT_CONNECTED);
state->SetMsgCounterSyncInProgress(false);
VerifyOrExit(msgBuf->DataLength() == kMsgCounterSyncRespMsgSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
VerifyOrExit(ChipKeyId::IsAppGroupKey(packetHeader.GetEncryptionKeyID()), err = CHIP_ERROR_WRONG_KEY_TYPE);
// Store the 64-bit value sent in the Challenge filed of the MsgCounterSyncReq.
VerifyOrExit(resp != nullptr, err = CHIP_ERROR_MESSAGE_INCOMPLETE);
VerifyOrExit(resplen == kMsgCounterSyncRespMsgSize, err = CHIP_ERROR_INVALID_MESSAGE_LENGTH);
syncCounter = chip::Encoding::LittleEndian::Read32(resp);
VerifyOrExit(syncCounter != 0, err = CHIP_ERROR_READ_FAILED);
memcpy(challenge, resp, kMsgCounterChallengeSize);
// Verify that the response field matches the expected Challenge field for the exchange.
VerifyOrExit(memcmp(exchangeContext->GetChallenge(), challenge, kMsgCounterChallengeSize) == 0,
err = CHIP_ERROR_INVALID_SIGNATURE);
VerifyOrExit(packetHeader.GetSourceNodeId().HasValue(), err = CHIP_ERROR_INVALID_ARGUMENT);
peerNodeId = packetHeader.GetSourceNodeId().Value();
// Process all queued ougoing and incomming group messages after message counter synchronization is completed.
RetransPendingGroupMsgs(peerNodeId);
ProcessPendingGroupMsgs(peerNodeId);
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(ExchangeManager, "Failed to handle MsgCounterSyncResp message with error:%s", ErrorStr(err));
}
if (exchangeContext != nullptr)
exchangeContext->Close();
return;
}
} // namespace Messaging
} // namespace chip