/* * * 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 #include #include #include #include #include #include #include #include #include #include #include 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(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 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(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(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