diff --git a/src/credentials/GroupDataProvider.h b/src/credentials/GroupDataProvider.h index 9b694d82c3a7a6..d714134149598c 100644 --- a/src/credentials/GroupDataProvider.h +++ b/src/credentials/GroupDataProvider.h @@ -113,7 +113,7 @@ class GroupDataProvider GroupId group_id = kUndefinedGroupId; FabricIndex fabric_index; SecurityPolicy security_policy; - Crypto::SymmetricKeyContext * key = nullptr; + Crypto::SymmetricKeyContext * keyContext = nullptr; }; // An EpochKey is a single key usable to determine an operational group key diff --git a/src/credentials/GroupDataProviderImpl.cpp b/src/credentials/GroupDataProviderImpl.cpp index 29e83f87038301..3ebbb6575255c1 100644 --- a/src/credentials/GroupDataProviderImpl.cpp +++ b/src/credentials/GroupDataProviderImpl.cpp @@ -1933,7 +1933,7 @@ bool GroupDataProviderImpl::GroupSessionIteratorImpl::Next(GroupSession & output output.fabric_index = fabric.fabric_index; output.group_id = mapping.group_id; output.security_policy = keyset.policy; - output.key = &mGroupKeyContext; + output.keyContext = &mGroupKeyContext; return true; } } diff --git a/src/credentials/tests/TestGroupDataProvider.cpp b/src/credentials/tests/TestGroupDataProvider.cpp index 0f320cce8a8bbb..deafdf5958de3a 100644 --- a/src/credentials/tests/TestGroupDataProvider.cpp +++ b/src/credentials/tests/TestGroupDataProvider.cpp @@ -1172,13 +1172,13 @@ void TestGroupDecryption(nlTestSuite * apSuite, void * apContext) { std::pair found(session.fabric_index, session.group_id); NL_TEST_ASSERT(apSuite, expected.count(found) > 0); - NL_TEST_ASSERT(apSuite, session.key != nullptr); + NL_TEST_ASSERT(apSuite, session.keyContext != nullptr); // Decrypt the ciphertext NL_TEST_ASSERT(apSuite, CHIP_NO_ERROR == - session.key->MessageDecrypt(ciphertext, ByteSpan(aad, sizeof(aad)), ByteSpan(nonce, sizeof(nonce)), - tag, plaintext)); + session.keyContext->MessageDecrypt(ciphertext, ByteSpan(aad, sizeof(aad)), + ByteSpan(nonce, sizeof(nonce)), tag, plaintext)); // The new plaintext must match the original message NL_TEST_ASSERT(apSuite, 0 == memcmp(plaintext.data(), kMessage, sizeof(kMessage))); diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index d8ed3429d7ce6f..1d2a7f9f3508a3 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -1173,6 +1173,19 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #define CHIP_CONFIG_ENABLE_SERVER_IM_EVENT 1 #endif +/** + * Accepts receipt of invalid privacy flag usage that affected some early SVE2 test event implementations. + * When SVE2 started, group messages would be sent with the privacy flag enabled, but without privacy encrypting the message header. + * The issue was subsequently corrected in master, the 1.0 branch, and the SVE2 branch. + * This is a temporary workaround for interoperability with those erroneous early-SVE2 implementations. + * The cost of this compatibity mode is twice as many decryption steps per received group message. + * + * TODO(#24573): Remove this workaround once interoperability with legacy pre-SVE2 is no longer required. + */ +#ifndef CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 +#define CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 1 +#endif // CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 + /** * @def CHIP_RESUBSCRIBE_MAX_RETRY_WAIT_INTERVAL_MS * @@ -1340,6 +1353,7 @@ extern const char CHIP_NON_PRODUCTION_MARKER[]; #ifndef CHIP_CONFIG_MAX_CLIENT_REG_PER_FABRIC #define CHIP_CONFIG_MAX_CLIENT_REG_PER_FABRIC 1 #endif // CHIP_CONFIG_MAX_CLIENT_REG_PER_FABRIC + /** * @} */ diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp index 45dd822089cbae..7cec122a9bcdeb 100644 --- a/src/transport/SessionManager.cpp +++ b/src/transport/SessionManager.cpp @@ -542,37 +542,48 @@ CHIP_ERROR SessionManager::InjectCaseSessionWithTestKey(SessionHolder & sessionH void SessionManager::OnMessageReceived(const PeerAddress & peerAddress, System::PacketBufferHandle && msg) { CHIP_TRACE_PREPARED_MESSAGE_RECEIVED(&peerAddress, &msg); - PacketHeader packetHeader; + PacketHeader partialPacketHeader; - CHIP_ERROR err = packetHeader.DecodeAndConsume(msg); + CHIP_ERROR err = partialPacketHeader.DecodeFixed(msg); if (err != CHIP_NO_ERROR) { ChipLogError(Inet, "Failed to decode packet header: %" CHIP_ERROR_FORMAT, err.Format()); return; } - if (packetHeader.IsEncrypted()) + if (partialPacketHeader.IsEncrypted()) { - if (packetHeader.IsGroupSession()) + if (partialPacketHeader.IsGroupSession()) { - SecureGroupMessageDispatch(packetHeader, peerAddress, std::move(msg)); + SecureGroupMessageDispatch(partialPacketHeader, peerAddress, std::move(msg)); } else { - SecureUnicastMessageDispatch(packetHeader, peerAddress, std::move(msg)); + SecureUnicastMessageDispatch(partialPacketHeader, peerAddress, std::move(msg)); } } else { - UnauthenticatedMessageDispatch(packetHeader, peerAddress, std::move(msg)); + UnauthenticatedMessageDispatch(partialPacketHeader, peerAddress, std::move(msg)); } } -void SessionManager::UnauthenticatedMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, - System::PacketBufferHandle && msg) +void SessionManager::UnauthenticatedMessageDispatch(const PacketHeader & partialPacketHeader, + const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg) { + // Drop unsecured messages with privacy enabled. + if (partialPacketHeader.HasPrivacyFlag()) + { + ChipLogError(Inet, "Dropping unauthenticated message with privacy flag set"); + return; + } + + PacketHeader packetHeader; + ReturnOnFailure(packetHeader.DecodeAndConsume(msg)); + Optional source = packetHeader.GetSourceNodeId(); Optional destination = packetHeader.GetDestinationNodeId(); + if ((source.HasValue() && destination.HasValue()) || (!source.HasValue() && !destination.HasValue())) { ChipLogProgress(Inet, @@ -639,15 +650,25 @@ void SessionManager::UnauthenticatedMessageDispatch(const PacketHeader & packetH } } -void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, - System::PacketBufferHandle && msg) +void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & partialPacketHeader, + const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg) { CHIP_ERROR err = CHIP_NO_ERROR; - Optional session = mSecureSessions.FindSecureSessionByLocalKey(packetHeader.GetSessionId()); + Optional session = mSecureSessions.FindSecureSessionByLocalKey(partialPacketHeader.GetSessionId()); PayloadHeader payloadHeader; + // Drop secure unicast messages with privacy enabled. + if (partialPacketHeader.HasPrivacyFlag()) + { + ChipLogError(Inet, "Dropping secure unicast message with privacy flag set"); + return; + } + + PacketHeader packetHeader; + ReturnOnFailure(packetHeader.DecodeAndConsume(msg)); + SessionMessageDelegate::DuplicateMessage isDuplicate = SessionMessageDelegate::DuplicateMessage::No; if (msg.IsNull()) @@ -736,29 +757,78 @@ void SessionManager::SecureUnicastMessageDispatch(const PacketHeader & packetHea } } -void SessionManager::SecureGroupMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, - System::PacketBufferHandle && msg) +/** + * Helper function to implement a single attempt to decrypt a groupcast message + * using the given group key and privacy setting. + * + * @param[in] partialPacketHeader The partial packet header with non-obfuscated message fields (result of calling DecodeFixed). + * @param[out] packetHeaderCopy A copy of the packet header, to be filled with privacy decrypted fields + * @param[out] payloadHeader The payload header of the decrypted message + * @param[in] applyPrivacy Whether to apply privacy deobfuscation + * @param[out] msgCopy A copy of the message, to be filled with the decrypted message + * @param[in] mac The MAC of the message + * @param[in] groupContext The group context to use for decryption key material + * + * @return true if the message was decrypted successfully + * @return false if the message could not be decrypted + */ +static bool GroupKeyDecryptAttempt(const PacketHeader & partialPacketHeader, PacketHeader & packetHeaderCopy, + PayloadHeader & payloadHeader, bool applyPrivacy, System::PacketBufferHandle & msgCopy, + const MessageAuthenticationCode & mac, + const Credentials::GroupDataProvider::GroupSession & groupContext) +{ + bool decrypted = false; + CryptoContext context(groupContext.keyContext); + + if (applyPrivacy) + { + // Perform privacy deobfuscation, if applicable. + uint8_t * privacyHeader = partialPacketHeader.PrivacyHeader(msgCopy->Start()); + size_t privacyLength = partialPacketHeader.PrivacyHeaderLength(); + if (CHIP_NO_ERROR != context.PrivacyDecrypt(privacyHeader, privacyLength, privacyHeader, partialPacketHeader, mac)) + { + return false; + } + } + + if (packetHeaderCopy.DecodeAndConsume(msgCopy) != CHIP_NO_ERROR) + { + ChipLogError(Inet, "Failed to decode Groupcast packet header. Discarding."); + return false; + } + + // Optimization to reduce number of decryption attempts + GroupId groupId = packetHeaderCopy.GetDestinationGroupId().Value(); + if (groupId != groupContext.group_id) + { + return false; + } + + CryptoContext::NonceStorage nonce; + CryptoContext::BuildNonce(nonce, packetHeaderCopy.GetSecurityFlags(), packetHeaderCopy.GetMessageCounter(), + packetHeaderCopy.GetSourceNodeId().Value()); + decrypted = (CHIP_NO_ERROR == SecureMessageCodec::Decrypt(context, nonce, payloadHeader, packetHeaderCopy, msgCopy)); + + return decrypted; +} + +void SessionManager::SecureGroupMessageDispatch(const PacketHeader & partialPacketHeader, + const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg) { PayloadHeader payloadHeader; + PacketHeader packetHeaderCopy; /// Packet header decoded per group key, with privacy decrypted fields + System::PacketBufferHandle msgCopy; Credentials::GroupDataProvider * groups = Credentials::GetGroupDataProvider(); VerifyOrReturn(nullptr != groups); CHIP_ERROR err = CHIP_NO_ERROR; - if (!packetHeader.GetDestinationGroupId().HasValue()) + if (!partialPacketHeader.HasDestinationGroupId()) { return; // malformed packet } - GroupId groupId = packetHeader.GetDestinationGroupId().Value(); - - if (msg.IsNull()) - { - ChipLogError(Inet, "Secure transport received Groupcast NULL packet, discarding"); - return; - } - // Check if Message Header is valid first - if (!(packetHeader.IsValidMCSPMsg() || packetHeader.IsValidGroupMsg())) + if (!(partialPacketHeader.IsValidMCSPMsg() || partialPacketHeader.IsValidGroupMsg())) { ChipLogError(Inet, "Invalid condition found in packet header"); return; @@ -766,44 +836,70 @@ void SessionManager::SecureGroupMessageDispatch(const PacketHeader & packetHeade // Trial decryption with GroupDataProvider Credentials::GroupDataProvider::GroupSession groupContext; - auto iter = groups->IterateGroupSessions(packetHeader.GetSessionId()); + auto iter = groups->IterateGroupSessions(partialPacketHeader.GetSessionId()); if (iter == nullptr) { ChipLogError(Inet, "Failed to retrieve Groups iterator. Discarding everything"); return; } - System::PacketBufferHandle msgCopy; + // Extract MIC from the end of the message. + uint8_t * data = msg->Start(); + uint16_t len = msg->DataLength(); + uint16_t footerLen = partialPacketHeader.MICTagLength(); + VerifyOrReturn(footerLen <= len); + + uint16_t taglen = 0; + MessageAuthenticationCode mac; + ReturnOnFailure(mac.Decode(partialPacketHeader, &data[len - footerLen], footerLen, &taglen)); + VerifyOrReturn(taglen == footerLen); + bool decrypted = false; while (!decrypted && iter->Next(groupContext)) { - // Optimization to reduce number of decryption attempts - if (groupId != groupContext.group_id) + CryptoContext context(groupContext.keyContext); + msgCopy = msg.CloneData(); + if (msgCopy.IsNull()) { - continue; + ChipLogError(Inet, "Failed to clone Groupcast message buffer. Discarding."); + return; } - msgCopy = msg.CloneData(); - CryptoContext::NonceStorage nonce; - CryptoContext::BuildNonce(nonce, packetHeader.GetSecurityFlags(), packetHeader.GetMessageCounter(), - packetHeader.GetSourceNodeId().Value()); - decrypted = (CHIP_NO_ERROR == - SecureMessageCodec::Decrypt(CryptoContext(groupContext.key), nonce, payloadHeader, packetHeader, msgCopy)); + + bool privacy = partialPacketHeader.HasPrivacyFlag(); + decrypted = + GroupKeyDecryptAttempt(partialPacketHeader, packetHeaderCopy, payloadHeader, privacy, msgCopy, mac, groupContext); + +#if CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 + if (privacy && !decrypted) + { + // Try processing the P=1 message again without privacy as a work-around for invalid early-SVE2 nodes. + msgCopy = msg.CloneData(); + if (msgCopy.IsNull()) + { + ChipLogError(Inet, "Failed to clone Groupcast message buffer. Discarding."); + return; + } + decrypted = + GroupKeyDecryptAttempt(partialPacketHeader, packetHeaderCopy, payloadHeader, false, msgCopy, mac, groupContext); + } +#endif // CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 } iter->Release(); + if (!decrypted) { - ChipLogError(Inet, "Failed to retrieve Key. Discarding everything"); + ChipLogError(Inet, "Failed to decrypt group message. Discarding everything"); return; } msg = std::move(msgCopy); // MCSP check - if (packetHeader.IsValidMCSPMsg()) + if (packetHeaderCopy.IsValidMCSPMsg()) { // TODO: When MCSP Msg, create Secure Session instead of a Group session // TODO - // if (packetHeader.GetDestinationNodeId().Value() == ThisDeviceNodeID) + // if (packetHeaderCopy.GetDestinationNodeId().Value() == ThisDeviceNodeID) // { // MCSP processing.. // } @@ -823,13 +919,13 @@ void SessionManager::SecureGroupMessageDispatch(const PacketHeader & packetHeade Transport::PeerMessageCounter * counter = nullptr; if (CHIP_NO_ERROR == - mGroupPeerMsgCounter.FindOrAddPeer(groupContext.fabric_index, packetHeader.GetSourceNodeId().Value(), - packetHeader.IsSecureSessionControlMsg(), counter)) + mGroupPeerMsgCounter.FindOrAddPeer(groupContext.fabric_index, packetHeaderCopy.GetSourceNodeId().Value(), + packetHeaderCopy.IsSecureSessionControlMsg(), counter)) { if (Credentials::GroupDataProvider::SecurityPolicy::kTrustFirst == groupContext.security_policy) { - err = counter->VerifyOrTrustFirstGroup(packetHeader.GetMessageCounter()); + err = counter->VerifyOrTrustFirstGroup(packetHeaderCopy.GetMessageCounter()); } else { @@ -839,7 +935,7 @@ void SessionManager::SecureGroupMessageDispatch(const PacketHeader & packetHeade return; // cache and sync - // err = counter->VerifyGroup(packetHeader.GetMessageCounter()); + // err = counter->VerifyGroup(packetHeaderCopy.GetMessageCounter()); } if (err != CHIP_NO_ERROR) @@ -856,15 +952,15 @@ void SessionManager::SecureGroupMessageDispatch(const PacketHeader & packetHeade return; } - counter->CommitGroup(packetHeader.GetMessageCounter()); + counter->CommitGroup(packetHeaderCopy.GetMessageCounter()); if (mCB != nullptr) { // TODO : When MCSP is done, clean up session creation logic Transport::IncomingGroupSession groupSession(groupContext.group_id, groupContext.fabric_index, - packetHeader.GetSourceNodeId().Value()); - CHIP_TRACE_MESSAGE_RECEIVED(payloadHeader, packetHeader, &groupSession, peerAddress, msg->Start(), msg->TotalLength()); - mCB->OnMessageReceived(packetHeader, payloadHeader, SessionHandle(groupSession), + packetHeaderCopy.GetSourceNodeId().Value()); + CHIP_TRACE_MESSAGE_RECEIVED(payloadHeader, packetHeaderCopy, &groupSession, peerAddress, msg->Start(), msg->TotalLength()); + mCB->OnMessageReceived(packetHeaderCopy, payloadHeader, SessionHandle(groupSession), SessionMessageDelegate::DuplicateMessage::No, std::move(msg)); } } diff --git a/src/transport/SessionManager.h b/src/transport/SessionManager.h index 9fac1a18347626..29f72c7a6ac2b7 100644 --- a/src/transport/SessionManager.h +++ b/src/transport/SessionManager.h @@ -476,13 +476,35 @@ class DLL_EXPORT SessionManager : public TransportMgrDelegate, public FabricTabl GlobalUnencryptedMessageCounter mGlobalUnencryptedMessageCounter; - void SecureUnicastMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, + /** + * @brief Parse, decrypt, validate, and dispatch a secure unicast message. + * + * @param[in] partialPacketHeader The partial PacketHeader of the message after processing with DecodeFixed. + * If the message decrypts successfully, this will be filled with a fully decoded PacketHeader. + * @param[in] peerAddress The PeerAddress of the message as provided by the receiving Transport Endpoint. + * @param msg The full message buffer, including header fields. + */ + void SecureUnicastMessageDispatch(const PacketHeader & partialPacketHeader, const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg); - void SecureGroupMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, + /** + * @brief Parse, decrypt, validate, and dispatch a secure group message. + * + * @param partialPacketHeader The partial PacketHeader of the message once processed with DecodeFixed. + * @param peerAddress The PeerAddress of the message as provided by the receiving Transport Endpoint. + * @param msg The full message buffer, including header fields. + */ + void SecureGroupMessageDispatch(const PacketHeader & partialPacketHeader, const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg); - void UnauthenticatedMessageDispatch(const PacketHeader & packetHeader, const Transport::PeerAddress & peerAddress, + /** + * @brief Parse, decrypt, validate, and dispatch an unsecured message. + * + * @param partialPacketHeader The partial PacketHeader of the message after processing with DecodeFixed. + * @param peerAddress The PeerAddress of the message as provided by the receiving Transport Endpoint. + * @param msg The full message buffer, including header fields. + */ + void UnauthenticatedMessageDispatch(const PacketHeader & partialPacketHeader, const Transport::PeerAddress & peerAddress, System::PacketBufferHandle && msg); void OnReceiveError(CHIP_ERROR error, const Transport::PeerAddress & source); diff --git a/src/transport/raw/MessageHeader.cpp b/src/transport/raw/MessageHeader.cpp index 1b8ef89680d75f..2947756b644d1a 100644 --- a/src/transport/raw/MessageHeader.cpp +++ b/src/transport/raw/MessageHeader.cpp @@ -131,13 +131,10 @@ uint16_t PayloadHeader::EncodeSizeBytes() const return static_cast(size); } -CHIP_ERROR PacketHeader::Decode(const uint8_t * const data, uint16_t size, uint16_t * decode_len) +CHIP_ERROR PacketHeader::DecodeFixedCommon(Encoding::LittleEndian::Reader & reader) { CHIP_ERROR err = CHIP_NO_ERROR; - LittleEndian::Reader reader(data, size); int version; - // TODO: De-uint16-ify everything related to this library - uint16_t octets_read; uint8_t msgFlags; SuccessOrExit(err = reader.Read8(&msgFlags).StatusCode()); @@ -151,6 +148,28 @@ CHIP_ERROR PacketHeader::Decode(const uint8_t * const data, uint16_t size, uint1 SuccessOrExit(err = reader.Read8(&securityFlags).StatusCode()); SetSecurityFlags(securityFlags); +exit: + + return err; +} + +CHIP_ERROR PacketHeader::DecodeFixed(const System::PacketBufferHandle & buf) +{ + const uint8_t * const data = buf->Start(); + uint16_t size = buf->DataLength(); + LittleEndian::Reader reader(data, size); + return DecodeFixedCommon(reader); +} + +CHIP_ERROR PacketHeader::Decode(const uint8_t * const data, uint16_t size, uint16_t * decode_len) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + LittleEndian::Reader reader(data, size); + // TODO: De-uint16-ify everything related to this library + uint16_t octets_read; + + SuccessOrExit(err = DecodeFixedCommon(reader)); + SuccessOrExit(err = reader.Read32(&mMessageCounter).StatusCode()); if (mMsgFlags.Has(Header::MsgFlagValues::kSourceNodeIdPresent)) diff --git a/src/transport/raw/MessageHeader.h b/src/transport/raw/MessageHeader.h index b811cb17797b34..4f7856232179c5 100644 --- a/src/transport/raw/MessageHeader.h +++ b/src/transport/raw/MessageHeader.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +135,13 @@ using ExFlags = BitFlags; class PacketHeader { public: + enum + { + kHeaderMinLength = 8, + kPrivacyHeaderMinLength = 4, + kPrivacyHeaderOffset = 4, + }; + /** * Gets the message counter set in the header. * @@ -172,6 +180,10 @@ class PacketHeader bool HasPrivacyFlag() const { return mSecFlags.Has(Header::SecFlagValues::kPrivacyFlag); } + bool HasSourceNodeId() const { return mMsgFlags.Has(Header::MsgFlagValues::kSourceNodeIdPresent); } + bool HasDestinationNodeId() const { return mMsgFlags.Has(Header::MsgFlagValues::kDestinationNodeIdPresent); } + bool HasDestinationGroupId() const { return mMsgFlags.Has(Header::MsgFlagValues::kDestinationGroupIdPresent); } + void SetFlags(Header::SecFlagValues value) { mSecFlags.Set(value); } void SetFlags(Header::MsgFlagValues value) { mMsgFlags.Set(value); } @@ -202,15 +214,13 @@ class PacketHeader bool IsValidGroupMsg() const { // Check is based on spec 4.11.2 - return (IsGroupSession() && GetSourceNodeId().HasValue() && GetDestinationGroupId().HasValue() && - !IsSecureSessionControlMsg()); + return (IsGroupSession() && HasSourceNodeId() && HasDestinationGroupId() && !IsSecureSessionControlMsg()); } bool IsValidMCSPMsg() const { // Check is based on spec 4.9.2.4 - return (IsGroupSession() && GetSourceNodeId().HasValue() && GetDestinationNodeId().HasValue() && - IsSecureSessionControlMsg()); + return (IsGroupSession() && HasSourceNodeId() && HasDestinationNodeId() && IsSecureSessionControlMsg()); } bool IsEncrypted() const { return !((mSessionId == kMsgUnicastSessionIdUnsecured) && IsUnicastSession()); } @@ -316,6 +326,37 @@ class PacketHeader return *this; } + /** + * Returns a pointer to the start of the privacy header + * given a pointer to the start of the message. + */ + uint8_t * PrivacyHeader(uint8_t * msgBuf) const { return msgBuf + PacketHeader::kPrivacyHeaderOffset; } + + size_t PrivacyHeaderLength() const + { + size_t length = kPrivacyHeaderMinLength; + if (mMsgFlags.Has(Header::MsgFlagValues::kSourceNodeIdPresent)) + { + length += sizeof(NodeId); + } + if (mMsgFlags.Has(Header::MsgFlagValues::kDestinationNodeIdPresent)) + { + length += sizeof(NodeId); + } + else if (mMsgFlags.Has(Header::MsgFlagValues::kDestinationGroupIdPresent)) + { + length += sizeof(GroupId); + } + return length; + } + + size_t PayloadOffset() const + { + size_t offset = kPrivacyHeaderMinLength; + offset += PrivacyHeaderLength(); + return offset; + } + /** * A call to `Encode` will require at least this many bytes on the current * object to be successful. @@ -324,6 +365,18 @@ class PacketHeader */ uint16_t EncodeSizeBytes() const; + /** + * Decodes the fixed portion of the header fields from the given buffer. + * The fixed header includes: message flags, session id, and security flags. + * + * @return CHIP_NO_ERROR on success. + * + * Possible failures: + * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size + * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. + */ + CHIP_ERROR DecodeFixed(const System::PacketBufferHandle & buf); + /** * Decodes a header from the given buffer. * @@ -397,6 +450,18 @@ class PacketHeader } private: + /** + * Decodes the fixed portion of the header fields from the stream reader. + * The fixed header includes: message flags, session id, and security flags. + * + * @return CHIP_NO_ERROR on success. + * + * Possible failures: + * CHIP_ERROR_INVALID_ARGUMENT on insufficient buffer size + * CHIP_ERROR_VERSION_MISMATCH if header version is not supported. + */ + CHIP_ERROR DecodeFixedCommon(Encoding::LittleEndian::Reader & reader); + /// Represents the current encode/decode header version (4 bits) static constexpr uint8_t kMsgHeaderVersion = 0x00; diff --git a/src/transport/raw/tests/TestMessageHeader.cpp b/src/transport/raw/tests/TestMessageHeader.cpp index 15a845c8a6be9b..41b1b527adfbca 100644 --- a/src/transport/raw/tests/TestMessageHeader.cpp +++ b/src/transport/raw/tests/TestMessageHeader.cpp @@ -151,6 +151,7 @@ void TestPacketHeaderEncodeDecode(nlTestSuite * inSuite, void * inContext) header.ClearDestinationNodeId(); header.SetSessionType(Header::SessionType::kGroupSession); + header.SetFlags(Header::SecFlagValues::kPrivacyFlag); header.SetSecureSessionControlMsg(false); NL_TEST_ASSERT(inSuite, header.Encode(buffer, &encodeLen) == CHIP_NO_ERROR); @@ -164,7 +165,7 @@ void TestPacketHeaderEncodeDecode(nlTestSuite * inSuite, void * inContext) NL_TEST_ASSERT(inSuite, header.IsValidGroupMsg()); // Verify MCSP state - header.ClearDestinationGroupId().SetDestinationNodeId(42); + header.ClearDestinationGroupId().SetDestinationNodeId(42).SetFlags(Header::SecFlagValues::kPrivacyFlag); header.SetSecureSessionControlMsg(true); NL_TEST_ASSERT(inSuite, header.Encode(buffer, &encodeLen) == CHIP_NO_ERROR); @@ -172,7 +173,8 @@ void TestPacketHeaderEncodeDecode(nlTestSuite * inSuite, void * inContext) header.SetMessageCounter(222).SetSourceNodeId(1).SetDestinationGroupId(2); NL_TEST_ASSERT(inSuite, header.Decode(buffer, &decodeLen) == CHIP_NO_ERROR); NL_TEST_ASSERT(inSuite, header.GetDestinationNodeId() == Optional::Value(42ull)); - NL_TEST_ASSERT(inSuite, !header.GetDestinationGroupId().HasValue()); + NL_TEST_ASSERT(inSuite, !header.HasDestinationGroupId()); + NL_TEST_ASSERT(inSuite, header.HasPrivacyFlag()); NL_TEST_ASSERT(inSuite, header.IsValidMCSPMsg()); } diff --git a/src/transport/tests/TestSessionManagerDispatch.cpp b/src/transport/tests/TestSessionManagerDispatch.cpp index 641c568c9e701b..72b8a2e27a4905 100644 --- a/src/transport/tests/TestSessionManagerDispatch.cpp +++ b/src/transport/tests/TestSessionManagerDispatch.cpp @@ -89,9 +89,14 @@ struct MessageTestEntry NodeId peerNodeId; GroupId groupId; NodeId sourceNodeId; + + uint8_t expectedMessageCount; }; struct MessageTestEntry theMessageTestVector[] = { + // ======================================= + // PASE positive test cases + // ======================================= { .name = "secure pase message (no payload)", .peerAddr = "::1", @@ -115,6 +120,8 @@ struct MessageTestEntry theMessageTestVector[] = { .sessionId = 0x0bb8, // 3000 .peerNodeId = 0x0000000000000000ULL, + + .expectedMessageCount = 1, }, { .name = "secure pase message (short payload)", @@ -139,14 +146,102 @@ struct MessageTestEntry theMessageTestVector[] = { .sessionId = 0x0bb8, // 3000 .peerNodeId = 0x0000000000000000ULL, + + .expectedMessageCount = 1, + }, + // ======================================= + // PASE negative test cases + // ======================================= + { + .name = "secure pase message (no payload / wrong MIC)", + .peerAddr = "::1", + + .payload = "", + .plain = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x05\x64\xee\x0e\x20\x7d", + .encrypted = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x5a\x98\x9a\xe4\x2e\x8d" + "\x84\x7f\x53\x5c\x30\x07\xe6\x15\x0c\xd6\x58\x67\xf2\xb8\x17\xdd", // Includes wrong MIC + .privacy = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x5a\x98\x9a\xe4\x2e\x8d" + "\x84\x7f\x53\x5c\x30\x07\xe6\x15\x0c\xd6\x58\x67\xf2\xb8\x17\xdd", // Includes wrong MIC + + .payloadLength = 0, + .plainLength = 14, + .encryptedLength = 30, + .privacyLength = 30, + + // TODO(#22830): unicast message tests must use test key currently + .encryptKey = "\x5e\xde\xd2\x44\xe5\x53\x2b\x3c\xdc\x23\x40\x9d\xba\xd0\x52\xd2", + + .nonce = "\x00\x39\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + + .sessionId = 0x0bb8, // 3000 + .peerNodeId = 0x0000000000000000ULL, + + .expectedMessageCount = 0, + }, + { + .name = "secure pase message (short payload / wrong MIC)", + .peerAddr = "::1", + + .payload = "\x11\x22\x33\x44\x55", + .plain = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x05\x64\xee\x0e\x20\x7d\x11\x22\x33\x44\x55", + .encrypted = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x5a\x98\x9a\xe4\x2e\x8d\x0f\x7f\x88\x5d\xfb" + "\x2f\xaa\x89\x49\xcf\x73\x0a\x57\x28\xe0\x35\x46\x10\xa0\xc4\xaa", // Includes wrong MIC + .privacy = "\x00\xb8\x0b\x00\x39\x30\x00\x00\x5a\x98\x9a\xe4\x2e\x8d\x0f\x7f\x88\x5d\xfb" + "\x2f\xaa\x89\x49\xcf\x73\x0a\x57\x28\xe0\x35\x46\x10\xa0\xc4\xaa", // Includes wrong MIC + + .payloadLength = 5, + .plainLength = 19, + .encryptedLength = 35, + .privacyLength = 35, + + // TODO(#22830): unicast message tests must use test key currently + .encryptKey = "\x5e\xde\xd2\x44\xe5\x53\x2b\x3c\xdc\x23\x40\x9d\xba\xd0\x52\xd2", + + .nonce = "\x00\x39\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + + .sessionId = 0x0bb8, // 3000 + .peerNodeId = 0x0000000000000000ULL, + + .expectedMessageCount = 0, + }, + { + .name = "secure pase message (short payload / drop when privacy enabled)", + .peerAddr = "::1", + + .payload = "\x11\x22\x33\x44\x55", + .plain = "\x00\xb8\x0b\x80\x39\x30\x00\x00\x05\x64\xee\x0e\x20\x7d\x11\x22\x33\x44\x55", + .encrypted = "\x00\xb8\x0b\x80\x39\x30\x00\x00\xaa\x26\xa0\xf9\x01\xef\xce\x9f\x9a\x67\xc8" + "\x13\x79\x17\xd1\x5b\x81\xd1\x5d\x31\x33\x08\x31\x97\x58\xea\x3f", // Includes MIC + .privacy = "\x00\xb8\x0b\x80\x87\xbe\xef\x06\xaa\x26\xa0\xf9\x01\xef\xce\x9f\x9a\x67\xc8" + "\x13\x79\x17\xd1\x5b\x81\xd1\x5d\x31\x33\x08\x31\x97\x58\xea\x3f", // Includes MIC + + .payloadLength = 5, + .plainLength = 19, + .encryptedLength = 35, + .privacyLength = 35, + + // TODO(#22830): unicast message tests must use test key currently + .encryptKey = "\x5e\xde\xd2\x44\xe5\x53\x2b\x3c\xdc\x23\x40\x9d\xba\xd0\x52\xd2", + + .nonce = "\x80\x39\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + .privacyNonce = "\x0b\xb8\x81\xd1\x5d\x31\x33\x08\x31\x97\x58\xea\x3f", + + .sessionId = 0x0bb8, // 3000 + .peerNodeId = 0x0000000000000000ULL, + + .expectedMessageCount = 0, }, #if !CHIP_CONFIG_SECURITY_TEST_MODE + // ======================================= + // GROUP positive test cases + // ======================================= { .name = "secure group message (no privacy)", .peerAddr = "::1", .payload = "", + // messageCounter = 0x12345678 // each group use case must increment this to pass replay. .plain = "\06\x7d\xdb\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x64\xee\x0e\x20\x7d", .encrypted = "\x06\x7d\xdb\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x65\xc7\x67\xbc\x6c\xda" "\x01\x06\xc9\x80\x13\x23\x90\x0e\x9b\x3c\xe6\xd4\xbb\x03\x27\xd6", // Includes MIC @@ -169,7 +264,146 @@ struct MessageTestEntry theMessageTestVector[] = { .peerNodeId = 0x0000000000000000ULL, .groupId = 2, .sourceNodeId = 0x0000000000000002ULL, + + .expectedMessageCount = 1, }, + { + .name = "secure group message (no privacy, drop replay)", + .peerAddr = "::1", + + .payload = "", + + // messageCounter = 0x12345678 // each group use case must increment this to pass replay. + .plain = "\06\x7d\xdb\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x64\xee\x0e\x20\x7d", + .encrypted = "\x06\x7d\xdb\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x65\xc7\x67\xbc\x6c\xda" + "\x01\x06\xc9\x80\x13\x23\x90\x0e\x9b\x3c\xe6\xd4\xbb\x03\x27\xd6", // Includes MIC + .privacy = "\x06\x7d\xdb\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x65\xc7\x67\xbc\x6c\xda" + "\x01\x06\xc9\x80\x13\x23\x90\x0e\x9b\x3c\xe6\xd4\xbb\x03\x27\xd6", // Includes MIC + + .payloadLength = 0, + .plainLength = 24, + .encryptedLength = 40, + .privacyLength = 40, + + .encryptKey = "\xca\x92\xd7\xa0\x94\x2d\x1a\x51\x1a\x0e\x26\xad\x07\x4f\x4c\x2f", + .privacyKey = "\xbf\xe9\xda\x01\x6a\x76\x53\x65\xf2\xdd\x97\xa9\xf9\x39\xe4\x25", + .epochKey = "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + + .nonce = "\x01\x78\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00", + .privacyNonce = "\xdb\x7d\x23\x90\x0e\x9b\x3c\xe6\xd4\xbb\x03\x27\xd6", + + .sessionId = 0xdb7d, // 56189 + .peerNodeId = 0x0000000000000000ULL, + .groupId = 2, + .sourceNodeId = 0x0000000000000002ULL, + + .expectedMessageCount = 0, ///< same test vector as above, but drops due to replay protection + }, + { + .name = "private group message", + .peerAddr = "::1", + + .payload = "", + + // messageCounter = 0x12345679 + .plain = "\x06\x7d\xdb\x81\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x64\xee\x0e\x20\x7d", + .encrypted = "\x06\x7d\xdb\x81\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2b\x2f\x91\x5a\x66\xc9" + "\x59\x62\x90\xeb\xe4\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xe1", + .privacy = "\x06\x7d\xdb\x81\xd9\x26\xaf\xce\x24\xc8\xa0\x98\x1b\xdd\x44\xf4\xe7\x30\x2b\x2f\x91\x5a\x66\xc9" + "\x59\x62\x90\xeb\xe4\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xe1", + + .payloadLength = 0, + .plainLength = 24, + .encryptedLength = 40, + .privacyLength = 40, + + .encryptKey = "\xca\x92\xd7\xa0\x94\x2d\x1a\x51\x1a\x0e\x26\xad\x07\x4f\x4c\x2f", + .privacyKey = "\xbf\xe9\xda\x01\x6a\x76\x53\x65\xf2\xdd\x97\xa9\xf9\x39\xe4\x25", + .epochKey = "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + + .nonce = "\x01\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00", + .privacyNonce = "\xdb\x7d\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xe1", + + .sessionId = 0xdb7d, // 56189 + .peerNodeId = 0x0000000000000000ULL, + .groupId = 2, + .sourceNodeId = 0x0000000000000002ULL, + + .expectedMessageCount = 1, + }, + // ======================================= + // GROUP negative test cases + // ======================================= + { + .name = "private group message (wrong MIC)", + .peerAddr = "::1", + + .payload = "", + + // messageCounter = 0x12345679 + .plain = "\x06\x7d\xdb\x81\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x64\xee\x0e\x20\x7d", + .encrypted = "\x06\x7d\xdb\x81\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2b\x2f\x91\x5a\x66\xc9" + "\x59\x62\x90\xeb\xe4\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xee", + .privacy = "\x06\x7d\xdb\x81\xd9\x26\xaf\xce\x24\xc8\xa0\x98\x1b\xdd\x44\xf4\xe7\x30\x2b\x2f\x91\x5a\x66\xc9" + "\x59\x62\x90\xeb\xe4\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xee", + + .payloadLength = 0, + .plainLength = 24, + .encryptedLength = 40, + .privacyLength = 40, + + .encryptKey = "\xca\x92\xd7\xa0\x94\x2d\x1a\x51\x1a\x0e\x26\xad\x07\x4f\x4c\x2f", + .privacyKey = "\xbf\xe9\xda\x01\x6a\x76\x53\x65\xf2\xdd\x97\xa9\xf9\x39\xe4\x25", + .epochKey = "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + + .nonce = "\x01\x79\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00", + .privacyNonce = "\xdb\x7d\x40\x82\x17\xb3\xc0\xc9\x21\xa2\xfc\xa4\xe1", + + .sessionId = 0xdb7d, // 56189 + .peerNodeId = 0x0000000000000000ULL, + .groupId = 2, + .sourceNodeId = 0x0000000000000002ULL, + + .expectedMessageCount = 0, + }, +#if CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 + // ======================================= + // Test early-SVE2 workaround + // ======================================= + { + .name = "secure group message (no privacy, but invalid P=1 flag)", + .peerAddr = "::1", + + .payload = "", + + // messageCounter = 0x12345691 + .plain = "\06\x7d\xdb\x81\x91\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01\x64\xee\x0e\x20\x7d", + .encrypted = "\x06\x7d\xdb\x81\x91\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x91\xe0\x22\x85\xe0\x59\x07\xe0" + "\xd8\x68\x0c\x79\xac\x6d\x64\x46\x90\x65\xb2\x6f\x90\x26", // Includes MIC + .privacy = "\x06\x7d\xdb\x81\x91\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x91\xe0\x22\x85\xe0\x59\x07\xe0" + "\xd8\x68\x0c\x79\xac\x6d\x64\x46\x90\x65\xb2\x6f\x90\x26", // Includes MIC + + .payloadLength = 0, + .plainLength = 24, + .encryptedLength = 40, + .privacyLength = 40, + + .encryptKey = "\xca\x92\xd7\xa0\x94\x2d\x1a\x51\x1a\x0e\x26\xad\x07\x4f\x4c\x2f", + .privacyKey = "\xbf\xe9\xda\x01\x6a\x76\x53\x65\xf2\xdd\x97\xa9\xf9\x39\xe4\x25", + .epochKey = "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + + .nonce = "\x01\x91\x56\x34\x12\x01\x00\x00\x00\x00\x00\x00\x00", + .privacyNonce = "\xdb\x7d\x79\xac\x6d\x64\x46\x90\x65\xb2\x6f\x90\x26", + + .sessionId = 0xdb7d, // 56189 + .peerNodeId = 0x0000000000000000ULL, + .groupId = 2, + .sourceNodeId = 0x0000000000000002ULL, + + .expectedMessageCount = 1, + }, +#endif // CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2 + #endif // !CHIP_CONFIG_SECURITY_TEST_MODE }; @@ -360,7 +594,12 @@ void TestSessionManagerDispatch(nlTestSuite * inSuite, void * inContext) const PeerAddress peerAddress = AddressFromString(testEntry.peerAddr); sessionManager.OnMessageReceived(peerAddress, std::move(msg)); - NL_TEST_ASSERT(inSuite, callback.NumMessagesReceived() > 0); + NL_TEST_ASSERT(inSuite, callback.NumMessagesReceived() == testEntry.expectedMessageCount); + + if ((testEntry.expectedMessageCount == 0) && (callback.NumMessagesReceived() == 0)) + { + ChipLogProgress(Test, "::: TestSessionManagerDispatch[%d] PASS (negative test case)", i); + } } sessionManager.Shutdown();