From 109637270bfc74eed69b69ed338e22a4c08f3f3a Mon Sep 17 00:00:00 2001 From: Martin Turon Date: Tue, 20 Sep 2022 09:01:23 -0700 Subject: [PATCH] [session][test] Added unit test coverage of OnMessageReceive decrypt path. (#22736) * [session][test] Added unit test for coverage of OnMessageReceive decrypt path. --- src/lib/core/CHIPConfig.h | 14 + src/transport/CryptoContext.cpp | 9 +- src/transport/SessionManager.cpp | 4 +- src/transport/tests/BUILD.gn | 1 + .../tests/TestSessionManagerDispatch.cpp | 305 ++++++++++++++++++ 5 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 src/transport/tests/TestSessionManagerDispatch.cpp diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h index 3d13480627e9e3..b84fa02e091904 100644 --- a/src/lib/core/CHIPConfig.h +++ b/src/lib/core/CHIPConfig.h @@ -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 * diff --git a/src/transport/CryptoContext.cpp b/src/transport/CryptoContext.cpp index 98c448ad6e600e..ac92cb4750e3e8 100644 --- a/src/transport/CryptoContext.cpp +++ b/src/transport/CryptoContext.cpp @@ -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; @@ -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( diff --git a/src/transport/SessionManager.cpp b/src/transport/SessionManager.cpp index 12f76f4b5073a9..e7514d45b9b50d 100644 --- a/src/transport/SessionManager.cpp +++ b/src/transport/SessionManager.cpp @@ -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(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE), secretLen); ReturnErrorOnFailure(secureSession->GetCryptoContext().InitFromSecret( secret, ByteSpan(nullptr, 0), CryptoContext::SessionInfoType::kSessionEstablishment, role)); @@ -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(CHIP_CONFIG_TEST_SHARED_SECRET_VALUE), secretLen); ReturnErrorOnFailure(secureSession->GetCryptoContext().InitFromSecret( secret, ByteSpan(nullptr, 0), CryptoContext::SessionInfoType::kSessionEstablishment, role)); diff --git a/src/transport/tests/BUILD.gn b/src/transport/tests/BUILD.gn index ce7579b62db928..b939287a2febdb 100644 --- a/src/transport/tests/BUILD.gn +++ b/src/transport/tests/BUILD.gn @@ -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" && diff --git a/src/transport/tests/TestSessionManagerDispatch.cpp b/src/transport/tests/TestSessionManagerDispatch.cpp new file mode 100644 index 00000000000000..6ebbbfbfb651ff --- /dev/null +++ b/src/transport/tests/TestSessionManagerDispatch.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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(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(plain), testEntry.plainLength); + const char * privacy = testEntry.privacy; + chip::System::PacketBufferHandle msg = + chip::MessagePacketBuffer::NewWithData(reinterpret_cast(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(aContext)->Init(); + return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE; +} + +/** + * Finalize the test suite. + */ +int Finalize(void * aContext) +{ + reinterpret_cast(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(&sSuite); +} + +CHIP_REGISTER_TEST_SUITE(TestSessionManagerDispatchSuite);