Skip to content

Commit

Permalink
[privacy] Implement privacy decrypt support in message layer (#22783)
Browse files Browse the repository at this point in the history
* [session][test] Add short payload secure unicast message test vector.

* [privacy] Add privacy parsing to message layer.

[test] Add test of private group.

* [privacy] test vector passes decrypt, but fails message counter.

* [privacy] Remove negative PrivacyDecrypt testVectors.

* [restyle]

* [privacy] Fix type conversion issue in CI.

* [privacy] rebase

* [privacy] Update message header codec tests to test P-flag.

* [privacy] Remove superfluous comments.

* [test] Add negative test capability and tests for wrong MIC.

* [test] Add test for dropping pase / secure unicast when privacy enabled.

* [test] Add test for dropping private group message with wrong MIC.

* [privacy] Resolve review comments.

* [privacy] Split per-key processing into dedicated static function.

* [privacy] Add test case for CHIP_CONFIG_PRIVACY_ACCEPT_NONSPEC_SVE2.

* [privacy][test] Update name of unit test for group replay protection.

* [privacy] Make PacketHeader::PrivacyHeader a utility function.

* [privacy] Resolve comments by bzbarsky-apple.

* [privacy] Fixed const and partialPacketHeader naming.

* [privacy] Fixed doc error - mismatched param name.

* [privacy] Add issue ref to TODO.
  • Loading branch information
turon authored and pull[bot] committed Oct 5, 2023
1 parent 43b433b commit 3e61858
Show file tree
Hide file tree
Showing 10 changed files with 522 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/credentials/GroupDataProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/credentials/GroupDataProviderImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/credentials/tests/TestGroupDataProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1172,13 +1172,13 @@ void TestGroupDecryption(nlTestSuite * apSuite, void * apContext)
{
std::pair<FabricIndex, GroupId> 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)));
Expand Down
14 changes: 14 additions & 0 deletions src/lib/core/CHIPConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -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

/**
* @}
*/
Expand Down
188 changes: 142 additions & 46 deletions src/transport/SessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeId> source = packetHeader.GetSourceNodeId();
Optional<NodeId> destination = packetHeader.GetDestinationNodeId();

if ((source.HasValue() && destination.HasValue()) || (!source.HasValue() && !destination.HasValue()))
{
ChipLogProgress(Inet,
Expand Down Expand Up @@ -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<SessionHandle> session = mSecureSessions.FindSecureSessionByLocalKey(packetHeader.GetSessionId());
Optional<SessionHandle> 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())
Expand Down Expand Up @@ -736,74 +757,149 @@ 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;
}

// 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..
// }
Expand All @@ -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
{
Expand All @@ -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)
Expand All @@ -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));
}
}
Expand Down
Loading

0 comments on commit 3e61858

Please sign in to comment.