| /* |
| * |
| * Copyright (c) 2020 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 PASESession implementation. |
| */ |
| |
| #include <errno.h> |
| #include <nlunit-test.h> |
| |
| #include <lib/core/CHIPCore.h> |
| #include <lib/core/CHIPSafeCasts.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/UnitTestRegistration.h> |
| #include <lib/support/UnitTestUtils.h> |
| #include <messaging/tests/MessagingContext.h> |
| #include <protocols/secure_channel/PASESession.h> |
| #include <stdarg.h> |
| #include <transport/raw/tests/NetworkTestHelpers.h> |
| |
| using namespace chip; |
| using namespace chip::Inet; |
| using namespace chip::Transport; |
| using namespace chip::Messaging; |
| using namespace chip::Protocols; |
| |
| using TestContext = chip::Test::MessagingContext; |
| |
| class PASETestLoopbackTransport : public Test::LoopbackTransport |
| { |
| void MessageDropped() override |
| { |
| // Trigger a retransmit. |
| if (mContext != nullptr) |
| { |
| chip::test_utils::SleepMillis(65); |
| ReliableMessageMgr * rm = mContext->GetExchangeManager().GetReliableMessageMgr(); |
| ReliableMessageMgr::Timeout(&mContext->GetSystemLayer(), rm); |
| } |
| } |
| |
| public: |
| bool CanSendToPeer(const PeerAddress & address) override { return true; } |
| |
| void Reset() |
| { |
| Test::LoopbackTransport::Reset(); |
| mContext = nullptr; |
| } |
| |
| TestContext * mContext = nullptr; |
| }; |
| |
| TransportMgrBase gTransportMgr; |
| PASETestLoopbackTransport gLoopback; |
| chip::Test::IOContext gIOContext; |
| |
| class TestSecurePairingDelegate : public SessionEstablishmentDelegate |
| { |
| public: |
| void OnSessionEstablishmentError(CHIP_ERROR error) override { mNumPairingErrors++; } |
| |
| void OnSessionEstablished() override { mNumPairingComplete++; } |
| |
| uint32_t mNumPairingErrors = 0; |
| uint32_t mNumPairingComplete = 0; |
| }; |
| |
| class MockAppDelegate : public ExchangeDelegate |
| { |
| public: |
| CHIP_ERROR OnMessageReceived(ExchangeContext * ec, const PayloadHeader & payloadHeader, |
| System::PacketBufferHandle && buffer) override |
| { |
| return CHIP_NO_ERROR; |
| } |
| |
| void OnResponseTimeout(ExchangeContext * ec) override {} |
| }; |
| |
| void SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext) |
| { |
| // Test all combinations of invalid parameters |
| TestSecurePairingDelegate delegate; |
| PASESession pairing; |
| |
| gLoopback.Reset(); |
| |
| NL_TEST_ASSERT(inSuite, pairing.WaitForPairing(1234, 500, ByteSpan(nullptr, 0), 0, &delegate) == CHIP_ERROR_INVALID_ARGUMENT); |
| NL_TEST_ASSERT(inSuite, |
| pairing.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSalt", 8), 0, nullptr) == |
| CHIP_ERROR_INVALID_ARGUMENT); |
| NL_TEST_ASSERT(inSuite, |
| pairing.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSalt", 8), 0, &delegate) == CHIP_NO_ERROR); |
| } |
| |
| void SecurePairingStartTest(nlTestSuite * inSuite, void * inContext) |
| { |
| TestContext & ctx = *reinterpret_cast<TestContext *>(inContext); |
| |
| // Test all combinations of invalid parameters |
| TestSecurePairingDelegate delegate; |
| |
| PASESession pairing; |
| |
| gLoopback.Reset(); |
| |
| NL_TEST_ASSERT(inSuite, pairing.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| ExchangeContext * context = ctx.NewUnauthenticatedExchangeToBob(&pairing); |
| |
| NL_TEST_ASSERT(inSuite, |
| pairing.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, nullptr, nullptr) != CHIP_NO_ERROR); |
| |
| gLoopback.Reset(); |
| NL_TEST_ASSERT(inSuite, |
| pairing.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, context, &delegate) == CHIP_NO_ERROR); |
| |
| NL_TEST_ASSERT(inSuite, gLoopback.mSentMessageCount == 1); |
| |
| // Clear pending packet in CRMP |
| ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); |
| ReliableMessageContext * rc = context->GetReliableMessageContext(); |
| rm->ClearRetransTable(rc); |
| |
| gLoopback.Reset(); |
| gLoopback.mSentMessageCount = 0; |
| gLoopback.mMessageSendError = CHIP_ERROR_BAD_REQUEST; |
| |
| PASESession pairing1; |
| NL_TEST_ASSERT(inSuite, pairing1.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| ExchangeContext * context1 = ctx.NewUnauthenticatedExchangeToBob(&pairing1); |
| NL_TEST_ASSERT(inSuite, |
| pairing1.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, context1, &delegate) == |
| CHIP_ERROR_BAD_REQUEST); |
| gLoopback.mMessageSendError = CHIP_NO_ERROR; |
| } |
| |
| void SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inContext, PASESession & pairingCommissioner, |
| TestSecurePairingDelegate & delegateCommissioner) |
| { |
| TestContext & ctx = *reinterpret_cast<TestContext *>(inContext); |
| |
| TestSecurePairingDelegate delegateAccessory; |
| PASESession pairingAccessory; |
| |
| gLoopback.mSentMessageCount = 0; |
| |
| NL_TEST_ASSERT(inSuite, pairingCommissioner.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, pairingAccessory.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| |
| ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(&pairingCommissioner); |
| |
| if (gLoopback.mNumMessagesToDrop != 0) |
| { |
| ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); |
| ReliableMessageContext * rc = contextCommissioner->GetReliableMessageContext(); |
| NL_TEST_ASSERT(inSuite, rm != nullptr); |
| NL_TEST_ASSERT(inSuite, rc != nullptr); |
| |
| rc->SetConfig({ |
| 1, // CHIP_CONFIG_MRP_DEFAULT_INITIAL_RETRY_INTERVAL |
| 1, // CHIP_CONFIG_MRP_DEFAULT_ACTIVE_RETRY_INTERVAL |
| }); |
| gLoopback.mContext = &ctx; |
| } |
| |
| NL_TEST_ASSERT(inSuite, |
| ctx.GetExchangeManager().RegisterUnsolicitedMessageHandlerForType( |
| Protocols::SecureChannel::MsgType::PBKDFParamRequest, &pairingAccessory) == CHIP_NO_ERROR); |
| |
| NL_TEST_ASSERT(inSuite, |
| pairingAccessory.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSALT", 8), 0, &delegateAccessory) == |
| CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, |
| pairingCommissioner.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, contextCommissioner, |
| &delegateCommissioner) == CHIP_NO_ERROR); |
| |
| // Standalone acks also increment the mSentMessageCount. But some messages could be acked |
| // via piggybacked acks. So we cannot check for a specific value of mSentMessageCount. |
| // Let's make sure atleast number is >= than the minimum messages required to complete the |
| // handshake. |
| NL_TEST_ASSERT(inSuite, gLoopback.mSentMessageCount >= 5); |
| NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 1); |
| NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 1); |
| gLoopback.mContext = nullptr; |
| } |
| |
| void SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext) |
| { |
| TestSecurePairingDelegate delegateCommissioner; |
| PASESession pairingCommissioner; |
| gLoopback.Reset(); |
| SecurePairingHandshakeTestCommon(inSuite, inContext, pairingCommissioner, delegateCommissioner); |
| } |
| |
| void SecurePairingHandshakeWithPacketLossTest(nlTestSuite * inSuite, void * inContext) |
| { |
| TestSecurePairingDelegate delegateCommissioner; |
| PASESession pairingCommissioner; |
| gLoopback.Reset(); |
| gLoopback.mNumMessagesToDrop = 2; |
| SecurePairingHandshakeTestCommon(inSuite, inContext, pairingCommissioner, delegateCommissioner); |
| NL_TEST_ASSERT(inSuite, gLoopback.mDroppedMessageCount == 2); |
| NL_TEST_ASSERT(inSuite, gLoopback.mNumMessagesToDrop == 0); |
| } |
| |
| void SecurePairingFailedHandshake(nlTestSuite * inSuite, void * inContext) |
| { |
| TestContext & ctx = *reinterpret_cast<TestContext *>(inContext); |
| |
| TestSecurePairingDelegate delegateCommissioner; |
| PASESession pairingCommissioner; |
| |
| TestSecurePairingDelegate delegateAccessory; |
| PASESession pairingAccessory; |
| |
| gLoopback.Reset(); |
| gLoopback.mSentMessageCount = 0; |
| |
| NL_TEST_ASSERT(inSuite, pairingCommissioner.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, pairingAccessory.MessageDispatch().Init(&ctx.GetSecureSessionManager()) == CHIP_NO_ERROR); |
| |
| ExchangeContext * contextCommissioner = ctx.NewUnauthenticatedExchangeToBob(&pairingCommissioner); |
| |
| ReliableMessageMgr * rm = ctx.GetExchangeManager().GetReliableMessageMgr(); |
| ReliableMessageContext * rc = contextCommissioner->GetReliableMessageContext(); |
| NL_TEST_ASSERT(inSuite, rm != nullptr); |
| NL_TEST_ASSERT(inSuite, rc != nullptr); |
| |
| rc->SetConfig({ |
| 1, // CHIP_CONFIG_MRP_DEFAULT_INITIAL_RETRY_INTERVAL |
| 1, // CHIP_CONFIG_MRP_DEFAULT_ACTIVE_RETRY_INTERVAL |
| }); |
| gLoopback.mContext = &ctx; |
| |
| NL_TEST_ASSERT(inSuite, |
| ctx.GetExchangeManager().RegisterUnsolicitedMessageHandlerForType( |
| Protocols::SecureChannel::MsgType::PBKDFParamRequest, &pairingAccessory) == CHIP_NO_ERROR); |
| |
| NL_TEST_ASSERT(inSuite, |
| pairingAccessory.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSALT", 8), 0, &delegateAccessory) == |
| CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, |
| pairingCommissioner.Pair(Transport::PeerAddress(Transport::Type::kBle), 4321, 0, contextCommissioner, |
| &delegateCommissioner) == CHIP_NO_ERROR); |
| |
| gLoopback.mContext = nullptr; |
| NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 0); |
| NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 1); |
| NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 0); |
| NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingErrors == 1); |
| } |
| |
| void SecurePairingDeserialize(nlTestSuite * inSuite, void * inContext, PASESession & pairingCommissioner, |
| PASESession & deserialized) |
| { |
| PASESessionSerialized serialized; |
| gLoopback.Reset(); |
| NL_TEST_ASSERT(inSuite, pairingCommissioner.Serialize(serialized) == CHIP_NO_ERROR); |
| |
| NL_TEST_ASSERT(inSuite, deserialized.Deserialize(serialized) == CHIP_NO_ERROR); |
| |
| // Serialize from the deserialized session, and check we get the same string back |
| PASESessionSerialized serialized2; |
| NL_TEST_ASSERT(inSuite, deserialized.Serialize(serialized2) == CHIP_NO_ERROR); |
| |
| NL_TEST_ASSERT(inSuite, strncmp(Uint8::to_char(serialized.inner), Uint8::to_char(serialized2.inner), sizeof(serialized)) == 0); |
| } |
| |
| void SecurePairingSerializeTest(nlTestSuite * inSuite, void * inContext) |
| { |
| TestSecurePairingDelegate delegateCommissioner; |
| |
| // Allocate on the heap to avoid stack overflow in some restricted test scenarios (e.g. QEMU) |
| auto * testPairingSession1 = chip::Platform::New<PASESession>(); |
| auto * testPairingSession2 = chip::Platform::New<PASESession>(); |
| |
| gLoopback.Reset(); |
| |
| SecurePairingHandshakeTestCommon(inSuite, inContext, *testPairingSession1, delegateCommissioner); |
| SecurePairingDeserialize(inSuite, inContext, *testPairingSession1, *testPairingSession2); |
| |
| const uint8_t plain_text[] = { 0x86, 0x74, 0x64, 0xe5, 0x0b, 0xd4, 0x0d, 0x90, 0xe1, 0x17, 0xa3, 0x2d, 0x4b, 0xd4, 0xe1, 0xe6 }; |
| uint8_t encrypted[64]; |
| PacketHeader header; |
| MessageAuthenticationCode mac; |
| |
| // Let's try encrypting using original session, and decrypting using deserialized |
| { |
| SecureSession session1; |
| |
| CHIP_ERROR err = testPairingSession1->DeriveSecureSession(session1, SecureSession::SessionRole::kInitiator); |
| |
| NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); |
| |
| err = session1.Encrypt(plain_text, sizeof(plain_text), encrypted, header, mac); |
| NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); |
| } |
| |
| { |
| SecureSession session2; |
| NL_TEST_ASSERT(inSuite, |
| testPairingSession2->DeriveSecureSession(session2, SecureSession::SessionRole::kResponder) == CHIP_NO_ERROR); |
| |
| uint8_t decrypted[64]; |
| NL_TEST_ASSERT(inSuite, session2.Decrypt(encrypted, sizeof(plain_text), decrypted, header, mac) == CHIP_NO_ERROR); |
| NL_TEST_ASSERT(inSuite, memcmp(plain_text, decrypted, sizeof(plain_text)) == 0); |
| } |
| |
| chip::Platform::Delete(testPairingSession1); |
| chip::Platform::Delete(testPairingSession2); |
| } |
| |
| // Test Suite |
| |
| /** |
| * Test Suite that lists all the test functions. |
| */ |
| // clang-format off |
| static const nlTest sTests[] = |
| { |
| NL_TEST_DEF("WaitInit", SecurePairingWaitTest), |
| NL_TEST_DEF("Start", SecurePairingStartTest), |
| NL_TEST_DEF("Handshake", SecurePairingHandshakeTest), |
| NL_TEST_DEF("Handshake with packet loss", SecurePairingHandshakeWithPacketLossTest), |
| NL_TEST_DEF("Failed Handshake", SecurePairingFailedHandshake), |
| NL_TEST_DEF("Serialize", SecurePairingSerializeTest), |
| |
| NL_TEST_SENTINEL() |
| }; |
| |
| int TestSecurePairing_Setup(void * inContext); |
| int TestSecurePairing_Teardown(void * inContext); |
| |
| // clang-format off |
| static nlTestSuite sSuite = |
| { |
| "Test-CHIP-SecurePairing-PASE", |
| &sTests[0], |
| TestSecurePairing_Setup, |
| TestSecurePairing_Teardown, |
| }; |
| // clang-format on |
| |
| static TestContext sContext; |
| |
| // clang-format on |
| // |
| /** |
| * Set up the test suite. |
| */ |
| int TestSecurePairing_Setup(void * inContext) |
| { |
| // Initialize System memory and resources |
| VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE); |
| VerifyOrReturnError(gIOContext.Init(&sSuite) == CHIP_NO_ERROR, FAILURE); |
| VerifyOrReturnError(gTransportMgr.Init(&gLoopback) == CHIP_NO_ERROR, FAILURE); |
| |
| auto & ctx = *static_cast<TestContext *>(inContext); |
| VerifyOrReturnError(ctx.Init(&sSuite, &gTransportMgr, &gIOContext) == CHIP_NO_ERROR, FAILURE); |
| |
| ctx.SetBobNodeId(kPlaceholderNodeId); |
| ctx.SetAliceNodeId(kPlaceholderNodeId); |
| ctx.SetBobKeyId(0); |
| ctx.SetAliceKeyId(0); |
| ctx.SetFabricIndex(kUndefinedFabricIndex); |
| |
| gTransportMgr.SetSecureSessionMgr(&ctx.GetSecureSessionManager()); |
| |
| return SUCCESS; |
| } |
| |
| /** |
| * Tear down the test suite. |
| */ |
| int TestSecurePairing_Teardown(void * inContext) |
| { |
| CHIP_ERROR err = reinterpret_cast<TestContext *>(inContext)->Shutdown(); |
| gIOContext.Shutdown(); |
| chip::Platform::MemoryShutdown(); |
| return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE; |
| } |
| |
| // TODO: TestPASESession is currently disabled due to lacking convenient way of creating exchange context |
| |
| /** |
| * Main |
| */ |
| int TestPASESession() |
| { |
| // Run test suit against one context |
| nlTestRunner(&sSuite, &sContext); |
| |
| return (nlTestRunnerStats(&sSuite)); |
| } |
| |
| CHIP_REGISTER_TEST_SUITE(TestPASESession) |