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

[pw_fuzzer] Adding a FuzzTest for fuzzing PASE #36171

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
]
}
Expand Down
24 changes: 24 additions & 0 deletions src/protocols/secure_channel/tests/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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") {
Expand Down Expand Up @@ -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}",
]
}
}
304 changes: 304 additions & 0 deletions src/protocols/secure_channel/tests/FuzzPASE_PW.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
#include <cstddef>
#include <cstdint>
#include <stdarg.h>
#include <string.h>

#include <pw_fuzzer/fuzztest.h>
#include <pw_unit_test/framework.h>

#include <app/icd/server/ICDServerConfig.h>
#include <lib/core/CHIPCore.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/UnitTestUtils.h>
#include <messaging/tests/MessagingContext.h>
#include <protocols/secure_channel/PASESession.h>

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 <typename... Ts>
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<ReliableMessageProtocolConfig>::Missing(), &delegateAccessory);
DrainAndServiceIO();

pairingCommissioner.Pair(sessionManager, SetUpPINCode, Optional<ReliableMessageProtocolConfig>::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<uint8_t> & 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<vector<uint8_t>>().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<uint8_t> & 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<uint32_t>(), Arbitrary<vector<uint8_t>>(), Arbitrary<uint32_t>());

/* -------------------------------------------------------------------------------------------*/
// In This FuzzTest, the Spake2pVerifier is fuzzed.
void FuzzSpake2pVerifier(const vector<uint8_t> & aW0, const vector<uint8_t> & aL, const vector<uint8_t> & 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<std::vector<uint8_t>>().WithMaxSize(kP256_FE_Length),
Arbitrary<std::vector<uint8_t>>().WithMaxSize(kP256_Point_Length), Arbitrary<vector<uint8_t>>(),
Arbitrary<uint32_t>(), Arbitrary<uint32_t>());

/* -------------------------------------------------------------------------------------------*/
// In This FuzzTest, Fuzzed Serialized Verifier is deserialized and Serialized Again, comparing the original with RoundTrip result.
void Spake2pVerifier_Serialize_RoundTrip(const vector<uint8_t> & 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<vector<uint8_t>>().WithSize(kSpake2p_VerifierSerialized_Length));

} // namespace
Loading