From b907990bbd7c4ba1401522f9949921741ec536ae Mon Sep 17 00:00:00 2001 From: Amine Alami <43780877+Alami-Amine@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:48:49 +0100 Subject: [PATCH] Adding a FuzzTest for fuzzing PASE (#36171) --- BUILD.gn | 1 + src/protocols/secure_channel/tests/BUILD.gn | 24 ++ .../secure_channel/tests/FuzzPASE_PW.cpp | 304 ++++++++++++++++++ 3 files changed, 329 insertions(+) create mode 100644 src/protocols/secure_channel/tests/FuzzPASE_PW.cpp diff --git a/BUILD.gn b/BUILD.gn index e05f14312d8a1a..a777fa84b32c99 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -70,6 +70,7 @@ if (current_toolchain != "${dir_pw_toolchain}/default:default") { "${chip_root}/src/lib/core/tests:fuzz-tlv-reader-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", "${chip_root}/src/lib/dnssd/minimal_mdns/tests:fuzz-minmdns-packet-parsing-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", "${chip_root}/src/lib/format/tests:fuzz-payload-decoder-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", + "${chip_root}/src/protocols/secure_channel/tests:fuzz-PASE-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", "${chip_root}/src/setup_payload/tests:fuzz-setup-payload-base38-pw(//build/toolchain/pw_fuzzer:chip_pw_fuzztest)", ] } diff --git a/src/protocols/secure_channel/tests/BUILD.gn b/src/protocols/secure_channel/tests/BUILD.gn index a5e6d73455eaea..415db1bbd76fd0 100644 --- a/src/protocols/secure_channel/tests/BUILD.gn +++ b/src/protocols/secure_channel/tests/BUILD.gn @@ -1,6 +1,8 @@ import("//build_overrides/build.gni") import("//build_overrides/chip.gni") + import("${chip_root}/build/chip/chip_test_suite.gni") +import("${chip_root}/build/chip/fuzz_test.gni") import("${chip_root}/src/app/icd/icd.gni") chip_test_suite("tests") { @@ -45,3 +47,25 @@ chip_test_suite("tests") { public_deps += [ "${chip_root}/src/app/icd/server:configuration-data" ] } } +if (pw_enable_fuzz_test_targets) { + chip_pw_fuzz_target("fuzz-PASE-pw") { + test_source = [ "FuzzPASE_PW.cpp" ] + public_deps = [ + "${chip_root}/src/app/icd/server:icd-server-config", + "${chip_root}/src/credentials/tests:cert_test_vectors", + "${chip_root}/src/crypto/tests:tests.lib", + "${chip_root}/src/lib/core", + "${chip_root}/src/lib/core:string-builder-adapters", + "${chip_root}/src/lib/support", + "${chip_root}/src/lib/support:test_utils", + "${chip_root}/src/lib/support:testing", + "${chip_root}/src/lib/support/tests:pw-test-macros", + "${chip_root}/src/messaging/tests:helpers", + "${chip_root}/src/protocols", + "${chip_root}/src/protocols/secure_channel", + "${chip_root}/src/protocols/secure_channel:check-in-counter", + "${chip_root}/src/transport/raw/tests:helpers", + "${dir_pw_unit_test}", + ] + } +} diff --git a/src/protocols/secure_channel/tests/FuzzPASE_PW.cpp b/src/protocols/secure_channel/tests/FuzzPASE_PW.cpp new file mode 100644 index 00000000000000..d3dc5998112f2a --- /dev/null +++ b/src/protocols/secure_channel/tests/FuzzPASE_PW.cpp @@ -0,0 +1,304 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using namespace chip; +using namespace std; + +using namespace chip::Crypto; +using namespace fuzztest; +using namespace chip::Transport; +using namespace chip::Messaging; +using namespace System::Clock::Literals; + +// TODO: #35369 Refactor the classes below to Fixtures once Errors related to FuzzTest Fixtures are resolved +class FuzzLoopbackMessagingContext : public chip::Test::MessagingContext +{ +public: + ~FuzzLoopbackMessagingContext() {} + + // These functions wrap spLoopbackTransportManager methods + static auto & GetSystemLayer() { return spLoopbackTransportManager->GetSystemLayer(); } + static auto & GetLoopback() { return spLoopbackTransportManager->GetLoopback(); } + static auto & GetTransportMgr() { return spLoopbackTransportManager->GetTransportMgr(); } + static auto & GetIOContext() { return spLoopbackTransportManager->GetIOContext(); } + + template + static void DrainAndServiceIO(Ts... args) + { + return spLoopbackTransportManager->DrainAndServiceIO(args...); + } + + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + // Initialize memory. + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + // Instantiate the LoopbackTransportManager. + ASSERT_EQ(spLoopbackTransportManager, nullptr); + spLoopbackTransportManager = new chip::Test::LoopbackTransportManager(); + ASSERT_NE(spLoopbackTransportManager, nullptr); + // Initialize the LoopbackTransportManager. + ASSERT_EQ(spLoopbackTransportManager->Init(), CHIP_NO_ERROR); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + // Shutdown the LoopbackTransportManager. + spLoopbackTransportManager->Shutdown(); + // Destroy the LoopbackTransportManager. + if (spLoopbackTransportManager != nullptr) + { + delete spLoopbackTransportManager; + spLoopbackTransportManager = nullptr; + } + // Shutdown memory. + chip::Platform::MemoryShutdown(); + } + + // Performs setup for each individual test in the test suite + void SetUp() { ASSERT_EQ(MessagingContext::Init(&GetTransportMgr(), &GetIOContext()), CHIP_NO_ERROR); } + + // Performs teardown for each individual test in the test suite + void TearDown() { MessagingContext::Shutdown(); } + + static chip::Test::LoopbackTransportManager * spLoopbackTransportManager; +}; +chip::Test::LoopbackTransportManager * FuzzLoopbackMessagingContext::spLoopbackTransportManager = nullptr; + +class TestSecurePairingDelegate : public SessionEstablishmentDelegate +{ +public: + void OnSessionEstablishmentError(CHIP_ERROR error) override { mNumPairingErrors++; } + + void OnSessionEstablished(const SessionHandle & session) override { mNumPairingComplete++; } + + uint32_t mNumPairingErrors = 0; + uint32_t mNumPairingComplete = 0; +}; + +class TestPASESession : public FuzzLoopbackMessagingContext +{ +public: + TestPASESession() + { + ConfigInitializeNodes(false); + FuzzLoopbackMessagingContext::SetUpTestSuite(); + FuzzLoopbackMessagingContext::SetUp(); + } + ~TestPASESession() + { + FuzzLoopbackMessagingContext::TearDown(); + FuzzLoopbackMessagingContext::TearDownTestSuite(); + } + + void SecurePairingHandshake(SessionManager & sessionManager, PASESession & pairingCommissioner, + TestSecurePairingDelegate & delegateCommissioner, TestSecurePairingDelegate & delegateAccessory, + const Spake2pVerifier & verifier, uint32_t pbkdf2IterCount, const ByteSpan & salt, + uint32_t SetUpPINCode); +}; + +class TemporarySessionManager +{ +public: + TemporarySessionManager(TestPASESession & ctx) : mCtx(ctx) + { + EXPECT_EQ(CHIP_NO_ERROR, + mSessionManager.Init(&ctx.GetSystemLayer(), &ctx.GetTransportMgr(), &ctx.GetMessageCounterManager(), &mStorage, + &ctx.GetFabricTable(), ctx.GetSessionKeystore())); + // The setup here is really weird: we are using one session manager for + // the actual messages we send (the PASE handshake, so the + // unauthenticated sessions) and a different one for allocating the PASE + // sessions. Since our Init() set us up as the thing to handle messages + // on the transport manager, undo that. + mCtx.GetTransportMgr().SetSessionManager(&mCtx.GetSecureSessionManager()); + } + + ~TemporarySessionManager() + { + mSessionManager.Shutdown(); + // Reset the session manager on the transport again, just in case + // shutdown messed with it. + mCtx.GetTransportMgr().SetSessionManager(&mCtx.GetSecureSessionManager()); + } + + operator SessionManager &() { return mSessionManager; } + +private: + TestPASESession & mCtx; + TestPersistentStorageDelegate mStorage; + SessionManager mSessionManager; +}; + +class PASETestLoopbackTransportDelegate : public Test::LoopbackTransportDelegate +{ +public: + void OnMessageDropped() override { mMessageDropped = true; } + bool mMessageDropped = false; +}; + +void TestPASESession::SecurePairingHandshake(SessionManager & sessionManager, PASESession & pairingCommissioner, + TestSecurePairingDelegate & delegateCommissioner, + TestSecurePairingDelegate & delegateAccessory, const Spake2pVerifier & verifier, + uint32_t pbkdf2IterCount, const ByteSpan & salt, uint32_t SetUpPINCode) +{ + + PASESession pairingAccessory; + + PASETestLoopbackTransportDelegate delegate; + auto & loopback = GetLoopback(); + loopback.SetLoopbackTransportDelegate(&delegate); + loopback.mSentMessageCount = 0; + + ExchangeContext * contextCommissioner = NewUnauthenticatedExchangeToBob(&pairingCommissioner); + + EXPECT_EQ(GetExchangeManager().RegisterUnsolicitedMessageHandlerForType(Protocols::SecureChannel::MsgType::PBKDFParamRequest, + &pairingAccessory), + CHIP_NO_ERROR); + + pairingAccessory.WaitForPairing(sessionManager, verifier, pbkdf2IterCount, salt, + Optional::Missing(), &delegateAccessory); + DrainAndServiceIO(); + + pairingCommissioner.Pair(sessionManager, SetUpPINCode, Optional::Missing(), contextCommissioner, + &delegateCommissioner); + + DrainAndServiceIO(); +} + +//----------------------------------------**********Fuzz Tests*********------------------------------------------------ + +// This Fuzz Test should always result in Successful PASE Pairing, since all fuzzed inputs are within the valid bounds +void PASESession_Bounded(const uint32_t fuzzedSetupPasscode, const vector & fuzzedSalt, uint32_t fuzzedPBKDF2Iter) +{ + + Spake2pVerifier fuzzedSpake2pVerifier; + ByteSpan fuzzedSaltSpan{ fuzzedSalt.data(), fuzzedSalt.size() }; + + // Generating the Spake2+ verifier from the fuzzed inputs + EXPECT_EQ(fuzzedSpake2pVerifier.Generate(fuzzedPBKDF2Iter, fuzzedSaltSpan, fuzzedSetupPasscode), CHIP_NO_ERROR); + + // TODO: #35369 Move this to a Fixture once Errors related to FuzzTest Fixtures are resolved + TestPASESession PASELoopBack; + TemporarySessionManager sessionManager(PASELoopBack); + + PASESession pairingCommissioner; + + TestSecurePairingDelegate delegateCommissioner; + TestSecurePairingDelegate delegateCommissionee; + + PASELoopBack.SecurePairingHandshake(sessionManager, pairingCommissioner, delegateCommissioner, delegateCommissionee, + fuzzedSpake2pVerifier, fuzzedPBKDF2Iter, fuzzedSaltSpan, fuzzedSetupPasscode); + + // Given that the inputs to this Fuzz Test are within the expected boundaries, the Pairing should Always be successful. + EXPECT_EQ(delegateCommissionee.mNumPairingComplete, 1u); + EXPECT_EQ(delegateCommissioner.mNumPairingComplete, 1u); + + EXPECT_EQ(delegateCommissionee.mNumPairingErrors, 0u); + EXPECT_EQ(delegateCommissioner.mNumPairingErrors, 0u); +} + +FUZZ_TEST(FuzzPASE_PW, PASESession_Bounded) + .WithDomains( + InRange(00000000, 99999998), + Arbitrary>().WithMinSize(kSpake2p_Min_PBKDF_Salt_Length).WithMaxSize(kSpake2p_Max_PBKDF_Salt_Length), + InRange(kSpake2p_Min_PBKDF_Iterations, kSpake2p_Max_PBKDF_Iterations)); + +/* -------------------------------------------------------------------------------------------*/ +// This Fuzz Test is the equivalent of the previous one, but with the fuzzed inputs not being within the valid bounds. +void PASESession_Unbounded(const uint32_t fuzzedSetupPasscode, const vector & fuzzedSalt, uint32_t fuzzedPBKDF2Iter) +{ + + Spake2pVerifier fuzzedSpake2pVerifier; + ByteSpan fuzzedSaltSpan{ fuzzedSalt.data(), fuzzedSalt.size() }; + + // Generating the Spake2+ verifier from fuzzed inputs + fuzzedSpake2pVerifier.Generate(fuzzedPBKDF2Iter, fuzzedSaltSpan, fuzzedSetupPasscode); + + TestPASESession PASELoopBack; + TemporarySessionManager sessionManager(PASELoopBack); + + PASESession pairingCommissioner; + + TestSecurePairingDelegate delegateCommissioner; + TestSecurePairingDelegate delegateCommissionee; + + PASELoopBack.SecurePairingHandshake(sessionManager, pairingCommissioner, delegateCommissioner, delegateCommissionee, + fuzzedSpake2pVerifier, fuzzedPBKDF2Iter, fuzzedSaltSpan, fuzzedSetupPasscode); +} + +FUZZ_TEST(FuzzPASE_PW, PASESession_Unbounded) + .WithDomains(Arbitrary(), Arbitrary>(), Arbitrary()); + +/* -------------------------------------------------------------------------------------------*/ +// In This FuzzTest, the Spake2pVerifier is fuzzed. +void FuzzSpake2pVerifier(const vector & aW0, const vector & aL, const vector & aSalt, + const uint32_t fuzzedPBKDF2Iter, const uint32_t fuzzedSetupPasscode) +{ + Spake2pVerifier fuzzedSpake2pVerifier; + + copy_n(aW0.data(), aW0.size(), fuzzedSpake2pVerifier.mW0); + copy_n(aL.data(), aL.size(), fuzzedSpake2pVerifier.mL); + + ByteSpan fuzzedSaltSpan(aSalt.data(), aSalt.size()); + + TestPASESession PASELoopBack; + TemporarySessionManager sessionManager(PASELoopBack); + + PASESession pairingCommissioner; + + TestSecurePairingDelegate delegateCommissioner; + TestSecurePairingDelegate delegateCommissionee; + + PASELoopBack.SecurePairingHandshake(sessionManager, pairingCommissioner, delegateCommissioner, delegateCommissionee, + fuzzedSpake2pVerifier, fuzzedPBKDF2Iter, fuzzedSaltSpan, fuzzedSetupPasscode); +} +FUZZ_TEST(FuzzPASE_PW, FuzzSpake2pVerifier) + .WithDomains(Arbitrary>().WithMaxSize(kP256_FE_Length), + Arbitrary>().WithMaxSize(kP256_Point_Length), Arbitrary>(), + Arbitrary(), Arbitrary()); + +/* -------------------------------------------------------------------------------------------*/ +// In This FuzzTest, Fuzzed Serialized Verifier is deserialized and Serialized Again, comparing the original with RoundTrip result. +void Spake2pVerifier_Serialize_RoundTrip(const vector & FuzzedSerializedVerifier) +{ + + Spake2pVerifierSerialized FuzzedSerializedVerifierArray; + + copy_n(FuzzedSerializedVerifier.data(), FuzzedSerializedVerifier.size(), FuzzedSerializedVerifierArray); + + // Deserialize the fuzzed SPAKE2+ Verifier + Spake2pVerifier verifier; + EXPECT_EQ(verifier.Deserialize(ByteSpan(FuzzedSerializedVerifierArray)), CHIP_NO_ERROR); + + // Serialize the fuzzed SPAKE2+ Verifier again + Spake2pVerifierSerialized reserializedVerifier; + MutableByteSpan reserializedVerifierSpan(reserializedVerifier); + EXPECT_EQ(verifier.Serialize(reserializedVerifierSpan), CHIP_NO_ERROR); + EXPECT_EQ(reserializedVerifierSpan.size(), kSpake2p_VerifierSerialized_Length); + + // The original fuzzed SPAKE2+ verifier should be the same as the deserialized and re-serialized verifier (RoundTrip). + EXPECT_EQ(memcmp(reserializedVerifier, FuzzedSerializedVerifierArray, kSpake2p_VerifierSerialized_Length), 0); +} + +FUZZ_TEST(FuzzPASE_PW, Spake2pVerifier_Serialize_RoundTrip) + .WithDomains(Arbitrary>().WithSize(kSpake2p_VerifierSerialized_Length)); + +} // namespace