Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[session][test] Added unit test coverage of OnMessageReceive decrypt path. #22736

Merged
merged 5 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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[] = {
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
.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);