| /* |
| * |
| * 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/GroupDataProviderImpl.h> |
| #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 namespace chip::Credentials; |
| |
| using GroupInfo = GroupDataProvider::GroupInfo; |
| using GroupKey = GroupDataProvider::GroupKey; |
| using KeySet = GroupDataProvider::KeySet; |
| using SecurityPolicy = GroupDataProvider::SecurityPolicy; |
| |
| 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; |
| GroupId groupId; |
| NodeId sourceNodeId; |
| }; |
| |
| struct MessageTestEntry theMessageTestVector[] = { |
| { |
| .name = "secure pase message (no payload)", |
| .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, |
| |
| // 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, |
| }, |
| { |
| .name = "secure pase message (short payload)", |
| .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\xa7", // Includes 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\xa7", // 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 = "\x00\x39\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", |
| |
| .sessionId = 0x0bb8, // 3000 |
| .peerNodeId = 0x0000000000000000ULL, |
| }, |
| #if !CHIP_CONFIG_SECURITY_TEST_MODE |
| { |
| .name = "secure group message (no privacy)", |
| .peerAddr = "::1", |
| |
| .payload = "", |
| |
| .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, |
| }, |
| #endif // !CHIP_CONFIG_SECURITY_TEST_MODE |
| }; |
| |
| const uint16_t theMessageTestVectorLength = sizeof(theMessageTestVector) / sizeof(theMessageTestVector[0]); |
| |
| // Just enough init to replace a ton of boilerplate |
| constexpr FabricIndex kFabricIndex = kMinValidFabricIndex; |
| constexpr size_t kGroupIndex = 0; |
| |
| constexpr uint16_t kMaxGroupsPerFabric = 5; |
| constexpr uint16_t kMaxGroupKeysPerFabric = 8; |
| |
| static chip::TestPersistentStorageDelegate sStorageDelegate; |
| static GroupDataProviderImpl sProvider(kMaxGroupsPerFabric, kMaxGroupKeysPerFabric); |
| class FabricTableHolder |
| { |
| public: |
| FabricTableHolder() {} |
| ~FabricTableHolder() |
| { |
| mFabricTable.Shutdown(); |
| mOpKeyStore.Finish(); |
| mOpCertStore.Finish(); |
| } |
| |
| CHIP_ERROR Init() |
| { |
| ReturnErrorOnFailure(mOpKeyStore.Init(&mStorage)); |
| ReturnErrorOnFailure(mOpCertStore.Init(&mStorage)); |
| |
| // Initialize Group Data Provider |
| sProvider.SetStorageDelegate(&sStorageDelegate); |
| // sProvider.SetListener(&chip::app::TestGroups::sListener); |
| ReturnErrorOnFailure(sProvider.Init()); |
| Credentials::SetGroupDataProvider(&sProvider); |
| |
| // Initialize Fabric Table |
| 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())); |
| } |
| |
| // constexpr chip::FabricId kFabricId1 = 0x2906C908D115D362; |
| static const uint8_t kCompressedFabricIdBuffer1[] = { 0x87, 0xe1, 0xb0, 0x04, 0xe2, 0x35, 0xa1, 0x30 }; |
| constexpr ByteSpan kCompressedFabricId1(kCompressedFabricIdBuffer1); |
| |
| CHIP_ERROR InjectGroupSessionWithTestKey(SessionHolder & sessionHolder, MessageTestEntry & testEntry) |
| { |
| constexpr uint16_t kKeySetIndex = 0x0; |
| |
| GroupId groupId = testEntry.groupId; |
| GroupDataProvider * provider = GetGroupDataProvider(); |
| |
| static KeySet sKeySet(kKeySetIndex, SecurityPolicy::kTrustFirst, 1); |
| static GroupKey sGroupKeySet(groupId, kKeySetIndex); |
| static GroupInfo sGroupInfo(groupId, "Name Matter Not"); |
| static Transport::IncomingGroupSession sSessionBobToFriends(groupId, kFabricIndex, testEntry.sourceNodeId); |
| |
| if (testEntry.epochKey) |
| { |
| memcpy(sKeySet.epoch_keys[0].key, testEntry.epochKey, 16); |
| sKeySet.epoch_keys[0].start_time = 0; |
| sGroupInfo.group_id = groupId; |
| sGroupKeySet.group_id = groupId; |
| |
| ReturnErrorOnFailure(provider->SetKeySet(kFabricIndex, kCompressedFabricId1, sKeySet)); |
| ReturnErrorOnFailure(provider->SetGroupKeyAt(kFabricIndex, kGroupIndex, sGroupKeySet)); |
| ReturnErrorOnFailure(provider->SetGroupInfoAt(kFabricIndex, kGroupIndex, sGroupInfo)); |
| } |
| |
| sessionHolder = SessionHandle(sSessionBobToFriends); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| 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; |
| SessionHolder testGroupSession; |
| |
| 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); |
| |
| // TODO(#22830): inject raw keys rather than always defaulting to test key |
| // TODO: switch on session type |
| |
| // Inject Sessions |
| err = sessionManager.InjectPaseSessionWithTestKey(aliceToBobSession, testEntry.sessionId, testEntry.peerNodeId, |
| testEntry.sessionId, kFabricIndex, peer, |
| CryptoContext::SessionRole::kResponder); |
| NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); |
| |
| err = InjectGroupSessionWithTestKey(testGroupSession, testEntry); |
| NL_TEST_ASSERT(inSuite, CHIP_NO_ERROR == err); |
| |
| 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); |
| |
| 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); |