Skip to content

Commit

Permalink
[session][test] Added unit test coverage of OnMessageReceive decrypt …
Browse files Browse the repository at this point in the history
…path. (#22736)

* [session][test] Added unit test for coverage of OnMessageReceive decrypt path.
  • Loading branch information
turon authored and pull[bot] committed Jul 20, 2023
1 parent b49efb5 commit 1096372
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 7 deletions.
14 changes: 14 additions & 0 deletions src/lib/core/CHIPConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,20 @@
#define CHIP_CONFIG_TEST_SHARED_SECRET_VALUE "Test secret for key derivation."
#endif // CHIP_CONFIG_TEST_SHARED_SECRET_VALUE

/**
* @def CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH
*
* @brief
* Length of the shared secret to use for unit tests or when CHIP_CONFIG_SECURITY_TEST_MODE is enabled.
*
* Note that the default value of 32 includes the null terminator.
* WARNING: `strlen(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE)` will result in different keys
* than expected and give unexpected results for shared secrets that contain '\x00'.
*/
#ifndef CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH
#define CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH 32
#endif // CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH

/**
* @def CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES
*
Expand Down
9 changes: 4 additions & 5 deletions src/transport/CryptoContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,8 @@ CHIP_ERROR CryptoContext::InitFromSecret(const ByteSpan & secret, const ByteSpan
// If enabled, override the generated session key with a known key pair
// to allow man-in-the-middle session key recovery for testing purposes.

#define TEST_SECRET_SIZE 32
constexpr uint8_t kTestSharedSecret[TEST_SECRET_SIZE] = CHIP_CONFIG_TEST_SHARED_SECRET_VALUE;
static_assert(sizeof(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE) == TEST_SECRET_SIZE,
constexpr uint8_t kTestSharedSecret[CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH] = CHIP_CONFIG_TEST_SHARED_SECRET_VALUE;
static_assert(sizeof(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE) == CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH,
"CHIP_CONFIG_TEST_SHARED_SECRET_VALUE must be 32 bytes");
const ByteSpan & testSalt = ByteSpan(nullptr, 0);
(void) info;
Expand All @@ -104,8 +103,8 @@ CHIP_ERROR CryptoContext::InitFromSecret(const ByteSpan & secret, const ByteSpan
"and NodeID=0 in NONCE. "
"Node can only communicate with other nodes built with this flag set.");

ReturnErrorOnFailure(mHKDF.HKDF_SHA256(kTestSharedSecret, TEST_SECRET_SIZE, testSalt.data(), testSalt.size(), SEKeysInfo,
sizeof(SEKeysInfo), &mKeys[0][0], sizeof(mKeys)));
ReturnErrorOnFailure(mHKDF.HKDF_SHA256(kTestSharedSecret, CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH, testSalt.data(),
testSalt.size(), SEKeysInfo, sizeof(SEKeysInfo), &mKeys[0][0], sizeof(mKeys)));
#else

ReturnErrorOnFailure(
Expand Down
4 changes: 2 additions & 2 deletions src/transport/SessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ CHIP_ERROR SessionManager::InjectPaseSessionWithTestKey(SessionHolder & sessionH
SecureSession * secureSession = session.Value()->AsSecureSession();
secureSession->SetPeerAddress(peerAddress);

size_t secretLen = strlen(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE);
size_t secretLen = CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH;
ByteSpan secret(reinterpret_cast<const uint8_t *>(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE), secretLen);
ReturnErrorOnFailure(secureSession->GetCryptoContext().InitFromSecret(
secret, ByteSpan(nullptr, 0), CryptoContext::SessionInfoType::kSessionEstablishment, role));
Expand All @@ -530,7 +530,7 @@ CHIP_ERROR SessionManager::InjectCaseSessionWithTestKey(SessionHolder & sessionH
SecureSession * secureSession = session.Value()->AsSecureSession();
secureSession->SetPeerAddress(peerAddress);

size_t secretLen = strlen(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE);
size_t secretLen = CHIP_CONFIG_TEST_SHARED_SECRET_LENGTH;
ByteSpan secret(reinterpret_cast<const uint8_t *>(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE), secretLen);
ReturnErrorOnFailure(secureSession->GetCryptoContext().InitFromSecret(
secret, ByteSpan(nullptr, 0), CryptoContext::SessionInfoType::kSessionEstablishment, role));
Expand Down
1 change: 1 addition & 0 deletions src/transport/tests/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ chip_test_suite("tests") {
"TestPeerMessageCounter.cpp",
"TestSecureSession.cpp",
"TestSessionManager.cpp",
"TestSessionManagerDispatch.cpp",
]

if (chip_device_platform != "mbed" && chip_device_platform != "efr32" &&
Expand Down
305 changes: 305 additions & 0 deletions src/transport/tests/TestSessionManagerDispatch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
/*
*
* 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 implements unit tests for the SessionManager implementation.
*/

#define CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API // Up here in case some other header
// includes SessionManager.h indirectly

#include <credentials/PersistentStorageOpCertStore.h>
#include <crypto/PersistentStorageOperationalKeystore.h>
#include <lib/core/CHIPCore.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <lib/support/UnitTestContext.h>
#include <lib/support/UnitTestRegistration.h>
#include <protocols/secure_channel/MessageCounterManager.h>
#include <transport/SessionManager.h>
#include <transport/TransportMgr.h>
#include <transport/tests/LoopbackTransportManager.h>

#include <nlbyteorder.h>
#include <nlunit-test.h>

#include <errno.h>

#undef CHIP_ENABLE_TEST_ENCRYPTED_BUFFER_API

namespace {

using namespace chip;
using namespace chip::Inet;
using namespace chip::Transport;
using namespace chip::Test;

using TestContext = chip::Test::LoopbackTransportManager;

struct MessageTestEntry
{
const char * name;

const char * peerAddr;

const char * payload;
const char * plain;
const char * encrypted;
const char * privacy;

size_t payloadLength;
size_t plainLength;
size_t encryptedLength;
size_t privacyLength;

const char * encryptKey;
const char * privacyKey;
const char * epochKey;

const char * nonce;
const char * privacyNonce;
const char * compressedFabricId;

const char * mic;

uint16_t sessionId;
NodeId peerNodeId;
FabricIndex fabricIndex;
};

struct MessageTestEntry theMessageTestVector[] = {
{
.name = "secure pase message",
.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\xdb", // Includes 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\xdb", // Includes MIC

.payloadLength = 0,
.plainLength = 14,
.encryptedLength = 30,
.privacyLength = 30,

.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,
.fabricIndex = 1,
},
};

const uint16_t theMessageTestVectorLength = sizeof(theMessageTestVector) / sizeof(theMessageTestVector[0]);

// Just enough init to replace a ton of boilerplate
class FabricTableHolder
{
public:
FabricTableHolder() {}
~FabricTableHolder()
{
mFabricTable.Shutdown();
mOpKeyStore.Finish();
mOpCertStore.Finish();
}

CHIP_ERROR Init()
{
ReturnErrorOnFailure(mOpKeyStore.Init(&mStorage));
ReturnErrorOnFailure(mOpCertStore.Init(&mStorage));

chip::FabricTable::InitParams initParams;
initParams.storage = &mStorage;
initParams.operationalKeystore = &mOpKeyStore;
initParams.opCertStore = &mOpCertStore;

return mFabricTable.Init(initParams);
}

FabricTable & GetFabricTable() { return mFabricTable; }

private:
chip::FabricTable mFabricTable;
chip::TestPersistentStorageDelegate mStorage;
chip::PersistentStorageOperationalKeystore mOpKeyStore;
chip::Credentials::PersistentStorageOpCertStore mOpCertStore;
};

class TestSessionManagerCallback : public SessionMessageDelegate
{
public:
void OnMessageReceived(const PacketHeader & header, const PayloadHeader & payloadHeader, const SessionHandle & session,
DuplicateMessage isDuplicate, System::PacketBufferHandle && msgBuf) override
{
mReceivedCount++;

MessageTestEntry & testEntry = theMessageTestVector[mTestVectorIndex];

ChipLogProgress(Test, "OnMessageReceived: sessionId=0x%04x", testEntry.sessionId);
NL_TEST_ASSERT(mSuite, header.GetSessionId() == testEntry.sessionId);

size_t dataLength = msgBuf->DataLength();
size_t expectLength = testEntry.payloadLength;

NL_TEST_ASSERT(mSuite, dataLength == expectLength);
NL_TEST_ASSERT(mSuite, memcmp(msgBuf->Start(), testEntry.payload, dataLength) == 0);

ChipLogProgress(Test, "TestSessionManagerDispatch[%d] PASS", mTestVectorIndex);
}

void ResetTest(unsigned testVectorIndex)
{
mTestVectorIndex = testVectorIndex;
mReceivedCount = 0;
}

unsigned NumMessagesReceived() { return mReceivedCount; }

nlTestSuite * mSuite = nullptr;
unsigned mTestVectorIndex = 0;
unsigned mReceivedCount = 0;
};

PeerAddress AddressFromString(const char * str)
{
Inet::IPAddress addr;

VerifyOrDie(Inet::IPAddress::FromString(str, addr));

return PeerAddress::UDP(addr);
}

void TestSessionManagerInit(nlTestSuite * inSuite, TestContext & ctx, SessionManager & sessionManager)
{
static FabricTableHolder fabricTableHolder;
static secure_channel::MessageCounterManager gMessageCounterManager;
static chip::TestPersistentStorageDelegate deviceStorage;

NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == fabricTableHolder.Init());
NL_TEST_ASSERT(inSuite,
CHIP_NO_ERROR ==
sessionManager.Init(&ctx.GetSystemLayer(), &ctx.GetTransportMgr(), &gMessageCounterManager, &deviceStorage,
&fabricTableHolder.GetFabricTable()));
}

void TestSessionManagerDispatch(nlTestSuite * inSuite, void * inContext)
{
CHIP_ERROR err = CHIP_NO_ERROR;

TestContext & ctx = *reinterpret_cast<TestContext *>(inContext);
SessionManager sessionManager;
TestSessionManagerCallback callback;

TestSessionManagerInit(inSuite, ctx, sessionManager);
sessionManager.SetMessageDelegate(&callback);

IPAddress addr;
IPAddress::FromString("::1", addr);
Transport::PeerAddress peer(Transport::PeerAddress::UDP(addr, CHIP_PORT));

SessionHolder aliceToBobSession;

callback.mSuite = inSuite;
for (unsigned i = 0; i < theMessageTestVectorLength; i++)
{
MessageTestEntry & testEntry = theMessageTestVector[i];
callback.ResetTest(i);

ChipLogProgress(Test, "===> TestSessionManagerDispatch[%d] '%s': sessionId=0x%04x", i, testEntry.name, testEntry.sessionId);

// Inject Sessions
err = sessionManager.InjectPaseSessionWithTestKey(aliceToBobSession, testEntry.sessionId, testEntry.peerNodeId,
testEntry.sessionId, testEntry.fabricIndex, peer,
CryptoContext::SessionRole::kResponder);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);

const char * plain = testEntry.plain;
const ByteSpan expectedPlain(reinterpret_cast<const uint8_t *>(plain), testEntry.plainLength);
const char * privacy = testEntry.privacy;
chip::System::PacketBufferHandle msg =
chip::MessagePacketBuffer::NewWithData(reinterpret_cast<const uint8_t *>(privacy), testEntry.privacyLength);

// TODO: inject raw keys rather than always defaulting to test key

const PeerAddress peerAddress = AddressFromString(testEntry.peerAddr);
sessionManager.OnMessageReceived(peerAddress, std::move(msg));
NL_TEST_ASSERT(inSuite, callback.NumMessagesReceived() > 0);
}

sessionManager.Shutdown();
}

// ============================================================================
// Test Suite Instrumenation
// ============================================================================

/**
* Initialize the test suite.
*/
int Initialize(void * aContext)
{
CHIP_ERROR err = reinterpret_cast<TestContext *>(aContext)->Init();
return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE;
}

/**
* Finalize the test suite.
*/
int Finalize(void * aContext)
{
reinterpret_cast<TestContext *>(aContext)->Shutdown();
return SUCCESS;
}

/**
* Test Suite that lists all the test functions.
*/
// clang-format off
const nlTest sTests[] =
{
NL_TEST_DEF("Test Session Manager Dispatch", TestSessionManagerDispatch),

NL_TEST_SENTINEL()
};

nlTestSuite sSuite =
{
"TestSessionManagerDispatch",
&sTests[0],
Initialize,
Finalize
};
// clang-format on

} // namespace

/**
* Main
*/
int TestSessionManagerDispatchSuite()
{
return chip::ExecuteTestsWithContext<TestContext>(&sSuite);
}

CHIP_REGISTER_TEST_SUITE(TestSessionManagerDispatchSuite);

0 comments on commit 1096372

Please sign in to comment.