| /* |
| * |
| * Copyright (c) 2020-2021 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 defines a secure transport layer which adds encryption to data |
| * sent over a transport. |
| * |
| */ |
| |
| #pragma once |
| |
| #include <utility> |
| |
| #include <inet/IPAddress.h> |
| #include <lib/core/CHIPCore.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/DLLUtil.h> |
| #include <messaging/ReliableMessageProtocolConfig.h> |
| #include <protocols/secure_channel/Constants.h> |
| #include <transport/CryptoContext.h> |
| #include <transport/MessageCounterManagerInterface.h> |
| #include <transport/SecureSessionTable.h> |
| #include <transport/SessionDelegate.h> |
| #include <transport/SessionHandle.h> |
| #include <transport/SessionHolder.h> |
| #include <transport/SessionMessageDelegate.h> |
| #include <transport/TransportMgr.h> |
| #include <transport/UnauthenticatedSessionTable.h> |
| #include <transport/raw/Base.h> |
| #include <transport/raw/PeerAddress.h> |
| #include <transport/raw/Tuple.h> |
| |
| namespace chip { |
| |
| class PairingSession; |
| |
| /** |
| * @brief |
| * Tracks ownership of a encrypted packet buffer. |
| * |
| * EncryptedPacketBufferHandle is a kind of PacketBufferHandle class and used to hold a packet buffer |
| * object whose payload has already been encrypted. |
| */ |
| class EncryptedPacketBufferHandle final : private System::PacketBufferHandle |
| { |
| public: |
| EncryptedPacketBufferHandle() {} |
| EncryptedPacketBufferHandle(EncryptedPacketBufferHandle && aBuffer) : PacketBufferHandle(std::move(aBuffer)) {} |
| |
| void operator=(EncryptedPacketBufferHandle && aBuffer) { PacketBufferHandle::operator=(std::move(aBuffer)); } |
| |
| using System::PacketBufferHandle::IsNull; |
| // Pass-through to HasChainedBuffer on our underlying buffer without |
| // exposing operator-> |
| bool HasChainedBuffer() const { return (*this)->HasChainedBuffer(); } |
| |
| uint32_t GetMessageCounter() const; |
| |
| /** |
| * Creates a copy of the data in this packet. |
| * |
| * Does NOT support chained buffers. |
| * |
| * @returns empty handle on allocation failure. |
| */ |
| EncryptedPacketBufferHandle CloneData() { return EncryptedPacketBufferHandle(PacketBufferHandle::CloneData()); } |
| |
| #ifdef CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API |
| /** |
| * Extracts the (unencrypted) packet header from this encrypted packet |
| * buffer. Returns error if a packet header cannot be extracted (e.g. if |
| * there are not enough bytes in this packet buffer). After this call the |
| * buffer does not have a packet header. This API is meant for |
| * unit tests only. The CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API define |
| * should not be defined normally. |
| */ |
| CHIP_ERROR ExtractPacketHeader(PacketHeader & aPacketHeader) { return aPacketHeader.DecodeAndConsume(*this); } |
| |
| /** |
| * Inserts a new (unencrypted) packet header in the encrypted packet buffer |
| * based on the given PacketHeader. This API is meant for |
| * unit tests only. The CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API define |
| * should not be defined normally. |
| */ |
| CHIP_ERROR InsertPacketHeader(const PacketHeader & aPacketHeader) { return aPacketHeader.EncodeBeforeData(*this); } |
| #endif // CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API |
| |
| static EncryptedPacketBufferHandle MarkEncrypted(PacketBufferHandle && aBuffer) |
| { |
| return EncryptedPacketBufferHandle(std::move(aBuffer)); |
| } |
| |
| /** |
| * Get a handle to the data that allows mutating the bytes. This should |
| * only be used if absolutely necessary, because EncryptedPacketBufferHandle |
| * represents a buffer that we want to resend as-is. |
| */ |
| PacketBufferHandle CastToWritable() const { return PacketBufferHandle::Retain(); } |
| |
| private: |
| EncryptedPacketBufferHandle(PacketBufferHandle && aBuffer) : PacketBufferHandle(std::move(aBuffer)) {} |
| }; |
| |
| class DLL_EXPORT SessionManager : public TransportMgrDelegate |
| { |
| public: |
| SessionManager(); |
| ~SessionManager() override; |
| |
| /** |
| * @brief |
| * This function takes the payload and returns an encrypted message which can be sent multiple times. |
| * |
| * @details |
| * It does the following: |
| * 1. Encrypt the msgBuf |
| * 2. construct the packet header |
| * 3. Encode the packet header and prepend it to message. |
| * Returns a encrypted message in encryptedMessage. |
| */ |
| CHIP_ERROR PrepareMessage(const SessionHandle & session, PayloadHeader & payloadHeader, System::PacketBufferHandle && msgBuf, |
| EncryptedPacketBufferHandle & encryptedMessage); |
| |
| /** |
| * @brief |
| * Send a prepared message to a currently connected peer. |
| */ |
| CHIP_ERROR SendPreparedMessage(const SessionHandle & session, const EncryptedPacketBufferHandle & preparedMessage); |
| |
| Transport::SecureSession * GetSecureSession(const SessionHandle & session); |
| |
| /// @brief Set the delegate for handling incoming messages. There can be only one message delegate (probably the |
| /// ExchangeManager) |
| void SetMessageDelegate(SessionMessageDelegate * cb) { mCB = cb; } |
| |
| /// @brief Set the delegate for handling session release. |
| void RegisterReleaseDelegate(SessionReleaseDelegate & cb) |
| { |
| #ifndef NDEBUG |
| mSessionReleaseDelegates.ForEachActiveObject([&](std::reference_wrapper<SessionReleaseDelegate> * i) { |
| VerifyOrDie(std::addressof(cb) != std::addressof(i->get())); |
| return Loop::Continue; |
| }); |
| #endif |
| std::reference_wrapper<SessionReleaseDelegate> * slot = mSessionReleaseDelegates.CreateObject(cb); |
| VerifyOrDie(slot != nullptr); |
| } |
| |
| void UnregisterReleaseDelegate(SessionReleaseDelegate & cb) |
| { |
| mSessionReleaseDelegates.ForEachActiveObject([&](std::reference_wrapper<SessionReleaseDelegate> * i) { |
| if (std::addressof(cb) == std::addressof(i->get())) |
| { |
| mSessionReleaseDelegates.ReleaseObject(i); |
| return Loop::Break; |
| } |
| return Loop::Continue; |
| }); |
| } |
| |
| void RegisterRecoveryDelegate(SessionRecoveryDelegate & cb); |
| void UnregisterRecoveryDelegate(SessionRecoveryDelegate & cb); |
| void RefreshSessionOperationalData(const SessionHandle & sessionHandle); |
| |
| /** |
| * @brief |
| * Establish a new pairing with a peer node |
| * |
| * @details |
| * This method sets up a new pairing with the peer node. It also |
| * establishes the security keys for secure communication with the |
| * peer node. |
| */ |
| CHIP_ERROR NewPairing(SessionHolder & sessionHolder, const Optional<Transport::PeerAddress> & peerAddr, NodeId peerNodeId, |
| PairingSession * pairing, CryptoContext::SessionRole direction, FabricIndex fabric); |
| |
| void ExpirePairing(const SessionHandle & session); |
| void ExpireAllPairings(NodeId peerNodeId, FabricIndex fabric); |
| void ExpireAllPairingsForFabric(FabricIndex fabric); |
| |
| /** |
| * @brief |
| * Return the System Layer pointer used by current SessionManager. |
| */ |
| System::Layer * SystemLayer() { return mSystemLayer; } |
| |
| /** |
| * @brief |
| * Initialize a Secure Session Manager |
| * |
| * @param systemLayer System, layer to use |
| * @param transportMgr Transport to use |
| * @param messageCounterManager The message counter manager |
| */ |
| CHIP_ERROR Init(System::Layer * systemLayer, TransportMgrBase * transportMgr, |
| Transport::MessageCounterManagerInterface * messageCounterManager); |
| |
| /** |
| * @brief |
| * Shutdown the Secure Session Manager. This terminates this instance |
| * of the object and reset it's state. |
| */ |
| void Shutdown(); |
| |
| TransportMgrBase * GetTransportManager() const { return mTransportMgr; } |
| |
| /** |
| * @brief |
| * Handle received secure message. Implements TransportMgrDelegate |
| * |
| * @param source the source address of the package |
| * @param msgBuf the buffer containing a full CHIP message (except for the optional length field). |
| */ |
| void OnMessageReceived(const Transport::PeerAddress & source, System::PacketBufferHandle && msgBuf) override; |
| |
| Optional<SessionHandle> CreateUnauthenticatedSession(const Transport::PeerAddress & peerAddress, |
| const ReliableMessageProtocolConfig & config) |
| { |
| Optional<Transport::UnauthenticatedSessionHandle> session = |
| mUnauthenticatedSessions.FindOrAllocateEntry(peerAddress, config); |
| return session.HasValue() ? MakeOptional<SessionHandle>(session.Value()) : NullOptional; |
| } |
| |
| // TODO: placeholder function for creating GroupSession. Implements a GroupSession class in the future |
| Optional<SessionHandle> CreateGroupSession(NodeId peerNodeId, GroupId groupId, FabricIndex fabricIndex) |
| { |
| return MakeOptional(SessionHandle(peerNodeId, groupId, fabricIndex)); |
| } |
| |
| // TODO: this is a temporary solution for legacy tests which use nodeId to send packets |
| // and tv-casting-app that uses the TV's node ID to find the associated secure session |
| SessionHandle FindSecureSessionForNode(NodeId peerNodeId); |
| |
| private: |
| /** |
| * The State of a secure transport object. |
| */ |
| enum class State |
| { |
| kNotReady, /**< State before initialization. */ |
| kInitialized, /**< State when the object is ready connect to other peers. */ |
| }; |
| |
| enum class EncryptionState |
| { |
| kPayloadIsEncrypted, |
| kPayloadIsUnencrypted, |
| }; |
| |
| System::Layer * mSystemLayer = nullptr; |
| Transport::UnauthenticatedSessionTable<CHIP_CONFIG_UNAUTHENTICATED_CONNECTION_POOL_SIZE> mUnauthenticatedSessions; |
| Transport::SecureSessionTable<CHIP_CONFIG_PEER_CONNECTION_POOL_SIZE> mSecureSessions; // < Active connections to other peers |
| State mState; // < Initialization state of the object |
| |
| SessionMessageDelegate * mCB = nullptr; |
| |
| // TODO: This is a temporary solution to release sessions, in the near future, SessionReleaseDelegate will be |
| // directly associated with the every SessionHolder. Then the callback function is called on over the handle |
| // delegate directly, in order to prevent dangling handles. |
| BitMapObjectPool<std::reference_wrapper<SessionReleaseDelegate>, CHIP_CONFIG_MAX_SESSION_RELEASE_DELEGATES> |
| mSessionReleaseDelegates; |
| |
| BitMapObjectPool<std::reference_wrapper<SessionRecoveryDelegate>, CHIP_CONFIG_MAX_SESSION_RECOVERY_DELEGATES> |
| mSessionRecoveryDelegates; |
| |
| TransportMgrBase * mTransportMgr = nullptr; |
| Transport::MessageCounterManagerInterface * mMessageCounterManager = nullptr; |
| |
| GlobalUnencryptedMessageCounter mGlobalUnencryptedMessageCounter; |
| GlobalEncryptedMessageCounter mGlobalEncryptedMessageCounter; |
| |
| /** Schedules a new oneshot timer for checking connection expiry. */ |
| void ScheduleExpiryTimer(); |
| |
| /** Cancels any active timers for connection expiry checks. */ |
| void CancelExpiryTimer(); |
| |
| /** |
| * Called when a specific connection expires. |
| */ |
| void HandleConnectionExpired(const Transport::SecureSession & state); |
| |
| /** |
| * Callback for timer expiry check |
| */ |
| static void ExpiryTimerCallback(System::Layer * layer, void * param); |
| |
| void SecureUnicastMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, |
| System::PacketBufferHandle && msg); |
| |
| void SecureGroupMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, |
| System::PacketBufferHandle && msg); |
| |
| void MessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, |
| System::PacketBufferHandle && msg); |
| |
| void OnReceiveError(CHIP_ERROR error, const Transport::PeerAddress & source); |
| |
| static bool IsControlMessage(PayloadHeader & payloadHeader) |
| { |
| return payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncReq) || |
| payloadHeader.HasMessageType(Protocols::SecureChannel::MsgType::MsgCounterSyncRsp); |
| } |
| |
| MessageCounter & GetSendCounterForPacket(PayloadHeader & payloadHeader, Transport::SecureSession & state) |
| { |
| if (IsControlMessage(payloadHeader)) |
| { |
| return mGlobalEncryptedMessageCounter; |
| } |
| else |
| { |
| return state.GetSessionMessageCounter().GetLocalMessageCounter(); |
| } |
| } |
| }; |
| |
| namespace MessagePacketBuffer { |
| /** |
| * Maximum size of a message footer, in bytes. |
| */ |
| constexpr uint16_t kMaxFooterSize = kMaxTagLen; |
| |
| /** |
| * Allocates a packet buffer with space for message headers and footers. |
| * |
| * Fails and returns \c nullptr if no memory is available, or if the size requested is too large. |
| * |
| * @param[in] aAvailableSize Minimum number of octets to for application data. |
| * |
| * @return On success, a PacketBufferHandle to the allocated buffer. On fail, \c nullptr. |
| */ |
| inline System::PacketBufferHandle New(size_t aAvailableSize) |
| { |
| static_assert(System::PacketBuffer::kMaxSize > kMaxFooterSize, "inadequate capacity"); |
| if (aAvailableSize > System::PacketBuffer::kMaxSize - kMaxFooterSize) |
| { |
| return System::PacketBufferHandle(); |
| } |
| return System::PacketBufferHandle::New(aAvailableSize + kMaxFooterSize); |
| } |
| |
| /** |
| * Allocates a packet buffer with initial contents. |
| * |
| * @param[in] aData Initial buffer contents. |
| * @param[in] aDataSize Size of initial buffer contents. |
| * |
| * @return On success, a PacketBufferHandle to the allocated buffer. On fail, \c nullptr. |
| */ |
| inline System::PacketBufferHandle NewWithData(const void * aData, size_t aDataSize) |
| { |
| return System::PacketBufferHandle::NewWithData(aData, aDataSize, kMaxFooterSize); |
| } |
| |
| /** |
| * Check whether a packet buffer has enough space for a message footer. |
| * |
| * @returns true if there is space, false otherwise. |
| */ |
| inline bool HasFooterSpace(const System::PacketBufferHandle & aBuffer) |
| { |
| return aBuffer->AvailableDataLength() >= kMaxFooterSize; |
| } |
| |
| } // namespace MessagePacketBuffer |
| |
| } // namespace chip |