blob: a6b9f2ee6bc4675fe92d9746d30556372cc0e9b7 [file] [log] [blame]
/*
*
* Copyright (c) 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 FabricTable implementation.
*/
#include <errno.h>
#include <gtest/gtest.h>
#include <lib/core/CHIPCore.h>
#include <credentials/FabricTable.h>
#include <credentials/PersistentStorageOpCertStore.h>
#include <credentials/TestOnlyLocalCertificateAuthority.h>
#include <credentials/tests/CHIPCert_test_vectors.h>
#include <crypto/CHIPCryptoPAL.h>
#include <crypto/PersistentStorageOperationalKeystore.h>
#include <lib/asn1/ASN1.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/TestPersistentStorageDelegate.h>
#include <platform/ConfigurationManager.h>
#include <lib/support/BytesToHex.h>
#include <stdarg.h>
using namespace chip;
using namespace chip::Credentials;
namespace {
class ScopedFabricTable
{
public:
ScopedFabricTable() {}
~ScopedFabricTable()
{
mFabricTable.Shutdown();
mOpCertStore.Finish();
mOpKeyStore.Finish();
}
CHIP_ERROR Init(chip::TestPersistentStorageDelegate * storage)
{
chip::FabricTable::InitParams initParams;
initParams.storage = storage;
initParams.operationalKeystore = &mOpKeyStore;
initParams.opCertStore = &mOpCertStore;
ReturnErrorOnFailure(mOpKeyStore.Init(storage));
ReturnErrorOnFailure(mOpCertStore.Init(storage));
return mFabricTable.Init(initParams);
}
FabricTable & GetFabricTable() { return mFabricTable; }
private:
chip::FabricTable mFabricTable;
chip::PersistentStorageOperationalKeystore mOpKeyStore;
chip::Credentials::PersistentStorageOpCertStore mOpCertStore;
};
/**
* Load a single test fabric with with the Root01:ICA01:Node01_01 identity.
*/
static CHIP_ERROR LoadTestFabric_Node01_01(FabricTable & fabricTable, bool doCommit)
{
Crypto::P256SerializedKeypair opKeysSerialized;
static Crypto::P256Keypair opKey_Node01_01;
FabricIndex fabricIndex;
memcpy(opKeysSerialized.Bytes(), TestCerts::sTestCert_Node01_01_PublicKey.data(),
TestCerts::sTestCert_Node01_01_PublicKey.size());
memcpy(opKeysSerialized.Bytes() + TestCerts::sTestCert_Node01_01_PublicKey.size(),
TestCerts::sTestCert_Node01_01_PrivateKey.data(), TestCerts::sTestCert_Node01_01_PrivateKey.size());
ByteSpan rcacSpan(TestCerts::sTestCert_Root01_Chip);
ByteSpan icacSpan(TestCerts::sTestCert_ICA01_Chip);
ByteSpan nocSpan(TestCerts::sTestCert_Node01_01_Chip);
ReturnErrorOnFailure(opKeysSerialized.SetLength(TestCerts::sTestCert_Node01_01_PublicKey.size() +
TestCerts::sTestCert_Node01_01_PrivateKey.size()));
ReturnErrorOnFailure(opKey_Node01_01.Deserialize(opKeysSerialized));
ReturnErrorOnFailure(fabricTable.AddNewPendingTrustedRootCert(rcacSpan));
ReturnErrorOnFailure(fabricTable.AddNewPendingFabricWithProvidedOpKey(nocSpan, icacSpan, VendorId::TestVendor1,
&opKey_Node01_01,
/*isExistingOpKeyExternallyOwned =*/true, &fabricIndex));
if (doCommit)
{
ReturnErrorOnFailure(fabricTable.CommitPendingFabricData());
}
return CHIP_NO_ERROR;
}
static CHIP_ERROR LoadTestFabric_Node01_02(FabricTable & fabricTable, bool doCommit)
{
Crypto::P256SerializedKeypair opKeysSerialized;
FabricIndex fabricIndex;
static Crypto::P256Keypair opKey_Node01_02;
memcpy(opKeysSerialized.Bytes(), TestCerts::sTestCert_Node01_02_PublicKey.data(),
TestCerts::sTestCert_Node01_02_PublicKey.size());
memcpy(opKeysSerialized.Bytes() + TestCerts::sTestCert_Node01_02_PublicKey.size(),
TestCerts::sTestCert_Node01_02_PrivateKey.data(), TestCerts::sTestCert_Node01_02_PrivateKey.size());
ByteSpan rcacSpan(TestCerts::sTestCert_Root01_Chip);
ByteSpan nocSpan(TestCerts::sTestCert_Node01_02_Chip);
ReturnErrorOnFailure(opKeysSerialized.SetLength(TestCerts::sTestCert_Node01_02_PublicKey.size() +
TestCerts::sTestCert_Node01_02_PrivateKey.size()));
ReturnErrorOnFailure(opKey_Node01_02.Deserialize(opKeysSerialized));
ReturnErrorOnFailure(fabricTable.AddNewPendingTrustedRootCert(rcacSpan));
ReturnErrorOnFailure(fabricTable.AddNewPendingFabricWithProvidedOpKey(nocSpan, {}, VendorId::TestVendor1, &opKey_Node01_02,
/*isExistingOpKeyExternallyOwned =*/true, &fabricIndex));
if (doCommit)
{
ReturnErrorOnFailure(fabricTable.CommitPendingFabricData());
}
return CHIP_NO_ERROR;
}
/**
* Load a single test fabric with with the Root02:ICA02:Node02_01 identity.
*/
static CHIP_ERROR LoadTestFabric_Node02_01(FabricTable & fabricTable, bool doCommit,
FabricTable::AdvertiseIdentity advertiseIdentity = FabricTable::AdvertiseIdentity::Yes)
{
Crypto::P256SerializedKeypair opKeysSerialized;
FabricIndex fabricIndex;
static Crypto::P256Keypair opKey_Node02_01;
memcpy(opKeysSerialized.Bytes(), TestCerts::sTestCert_Node02_01_PublicKey.data(),
TestCerts::sTestCert_Node02_01_PublicKey.size());
memcpy(opKeysSerialized.Bytes() + TestCerts::sTestCert_Node02_01_PublicKey.size(),
TestCerts::sTestCert_Node02_01_PrivateKey.data(), TestCerts::sTestCert_Node02_01_PrivateKey.size());
ByteSpan rcacSpan(TestCerts::sTestCert_Root02_Chip);
ByteSpan icacSpan(TestCerts::sTestCert_ICA02_Chip);
ByteSpan nocSpan(TestCerts::sTestCert_Node02_01_Chip);
EXPECT_EQ(opKeysSerialized.SetLength(TestCerts::sTestCert_Node02_01_PublicKey.size() +
TestCerts::sTestCert_Node02_01_PrivateKey.size()),
CHIP_NO_ERROR);
EXPECT_EQ(opKey_Node02_01.Deserialize(opKeysSerialized), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcacSpan), CHIP_NO_ERROR);
CHIP_ERROR err =
fabricTable.AddNewPendingFabricWithProvidedOpKey(nocSpan, icacSpan, VendorId::TestVendor1, &opKey_Node02_01,
/*isExistingOpKeyExternallyOwned =*/true, &fabricIndex, advertiseIdentity);
EXPECT_EQ(err, CHIP_NO_ERROR);
if (doCommit)
{
err = fabricTable.CommitPendingFabricData();
EXPECT_EQ(err, CHIP_NO_ERROR);
}
return err;
}
struct TestFabricTable : public ::testing::Test
{
static void SetUpTestSuite()
{
DeviceLayer::SetConfigurationMgr(&DeviceLayer::ConfigurationManagerImpl::GetDefaultInstance());
ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR);
}
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
};
TEST_F(TestFabricTable, TestLastKnownGoodTimeInit)
{
// Fabric table init should init Last Known Good Time to the firmware build time.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
System::Clock::Seconds32 lastKnownGoodChipEpochTime;
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime), CHIP_NO_ERROR);
System::Clock::Seconds32 firmwareBuildTime;
EXPECT_EQ(DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(firmwareBuildTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodChipEpochTime, firmwareBuildTime);
}
TEST_F(TestFabricTable, TestCollidingFabrics)
{
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
//
// Start by loading NOCs for two nodes on the same fabric. The second one should fail since the FabricTable by default
// doesn't permit colliding fabrics.
//
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
EXPECT_NE(LoadTestFabric_Node01_02(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
//
// Revert the partially added NOC from the last call, permit colliding fabrics in the FabricTable and try again.
// This time, it should succeed
//
fabricTable.RevertPendingFabricData();
fabricTable.PermitCollidingFabrics();
EXPECT_EQ(LoadTestFabric_Node01_02(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
ByteSpan rcacSpan(TestCerts::sTestCert_Root01_Chip);
Credentials::P256PublicKeySpan rootPublicKeySpan;
EXPECT_EQ(Credentials::ExtractPublicKeyFromChipCert(rcacSpan, rootPublicKeySpan), CHIP_NO_ERROR);
//
// Ensure we can find both node identities in the FabricTable.
//
{
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
ByteSpan origNocSpan(TestCerts::sTestCert_Node01_01_Chip);
NodeId nodeId;
FabricId fabricId;
EXPECT_EQ(ExtractNodeIdFabricIdFromOpCert(origNocSpan, &nodeId, &fabricId), CHIP_NO_ERROR);
EXPECT_NE(fabricTable.FindIdentity(rootPublicKeySpan, fabricId, nodeId), nullptr);
}
{
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
ByteSpan origNocSpan(TestCerts::sTestCert_Node01_02_Chip);
NodeId nodeId;
FabricId fabricId;
EXPECT_EQ(ExtractNodeIdFabricIdFromOpCert(origNocSpan, &nodeId, &fabricId), CHIP_NO_ERROR);
EXPECT_NE(fabricTable.FindIdentity(rootPublicKeySpan, fabricId, nodeId), nullptr);
}
}
TEST_F(TestFabricTable, TestUpdateLastKnownGoodTime)
{
// Adding a fabric should advance Last Known Good Time if any certificate's
// NotBefore time is later than the build time, and else should leave it
// to the initial build time value.
// Test certs all have this NotBefore: Oct 15 14:23:43 2020 GMT
const ASN1::ASN1UniversalTime asn1Expected = { 2020, 10, 15, 14, 23, 43 };
uint32_t testCertNotBeforeSeconds;
EXPECT_EQ(Credentials::ASN1ToChipEpochTime(asn1Expected, testCertNotBeforeSeconds), CHIP_NO_ERROR);
System::Clock::Seconds32 testCertNotBeforeTime = System::Clock::Seconds32(testCertNotBeforeSeconds);
// Test that certificate NotBefore times that are before the Firmware build time
// do not advance Last Known Good Time.
System::Clock::Seconds32 afterNotBeforeBuildTimes[] = { System::Clock::Seconds32(testCertNotBeforeTime.count() + 1),
System::Clock::Seconds32(testCertNotBeforeTime.count() + 1000),
System::Clock::Seconds32(testCertNotBeforeTime.count() + 1000000) };
for (auto buildTime : afterNotBeforeBuildTimes)
{
// Set build time to the desired value.
EXPECT_EQ(DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime), CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Read back Last Known Good Time, which will have been initialized to firmware build time.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
// Load a test fabric, but do not commit.
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ false), CHIP_NO_ERROR);
// Read Last Known Good Time and verify that it hasn't moved forward.
// This test case was written after the test certs' NotBefore time and we
// are using a configuration manager that should reflect a real build time.
// Therefore, we expect that build time is after NotBefore and so Last
// Known Good Time will be set to the later of these, build time, even
// after installing the new fabric.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
// Verify that calling the fail-safe roll back interface does not change
// last known good time, as it hadn't been updated in the first place.
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
// Now reload the test fabric and commit this time.
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
// Read Last Known Good Time and verify that it hasn't moved forward.
// This test case was written after the test certs' NotBefore time and we
// are using a configuration manager that should reflect a real build time.
// Therefore, we expect that build time is after NotBefore and so Last
// Known Good Time will be set to the later of these, build time, even
// after installing the new fabric.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
// Call revert again. Since we've committed, this is a no-op.
// Last known good time should again be unchanged.
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Verify that last known good time was retained.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
}
}
System::Clock::Seconds32 beforeNotBeforeBuildTimes[] = { testCertNotBeforeTime,
System::Clock::Seconds32(testCertNotBeforeTime.count() - 1),
System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000),
System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000000) };
// Test that certificate NotBefore times that are at or after the Firmware
// build time do result in Last Known Good Times set to these.
// Verify behavior both for fail-safe roll back and commit scenarios.
for (auto buildTime : beforeNotBeforeBuildTimes)
{
// Set build time to the desired value.
EXPECT_EQ(DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime), CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Load a test fabric, but do not commit.
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ false), CHIP_NO_ERROR);
// Read Last Known Good Time and verify that it is now set to the certificate
// NotBefore time, as this should be at or after firmware build time.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, testCertNotBeforeTime);
// Now test revert. Last known good time should change back to the
// previous value.
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, buildTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Verify that the original last known good time was retained, since
// we reverted before.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
}
{
// Now test loading a fabric and committing.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
// Read Last Known Good Time and verify that it is now set to the certificate
// NotBefore time, as this should be at or after firmware build time.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, testCertNotBeforeTime);
// Now test revert, which will be a no-op because we already
// committed. Verify that Last Known Good time is retained.
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, testCertNotBeforeTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Verify that the new last known good time was retained, since
// we committed.
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, testCertNotBeforeTime);
}
}
}
TEST_F(TestFabricTable, TestSetLastKnownGoodTime)
{
// It is desirable for nodes to set Last Known Good Time whenever a good
// time source is available, including cases where this would set the time
// backward. However, it is impermissible to set last known good time to
// any time before the Firmware Build time or the latest NotBefore of any
// installed certificate.
// Test certs all have this NotBefore: Oct 15 14:23:43 2020 GMT
const ASN1::ASN1UniversalTime asn1Expected = { 2020, 10, 15, 14, 23, 43 };
uint32_t testCertNotBeforeSeconds;
EXPECT_EQ(Credentials::ASN1ToChipEpochTime(asn1Expected, testCertNotBeforeSeconds), CHIP_NO_ERROR);
System::Clock::Seconds32 testCertNotBeforeTime = System::Clock::Seconds32(testCertNotBeforeSeconds);
// Iterate over two cases: one with build time prior to our certificates' NotBefore, one with build time after.
System::Clock::Seconds32 testCaseFirmwareBuildTimes[] = { System::Clock::Seconds32(testCertNotBeforeTime.count() - 100000),
System::Clock::Seconds32(testCertNotBeforeTime.count() + 100000) };
for (auto buildTime : testCaseFirmwareBuildTimes)
{
// Set build time to the desired value.
EXPECT_EQ(DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime), CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
System::Clock::Seconds32 newTime;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Load a test fabric
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit= */ true), CHIP_NO_ERROR);
// Verify the Last Known Good Time matches our expected initial value.
System::Clock::Seconds32 initialLastKnownGoodTime =
buildTime > testCertNotBeforeTime ? buildTime : testCertNotBeforeTime;
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, initialLastKnownGoodTime);
// Read Last Known Good Time and verify that it hasn't moved forward, since
// build time is later than the test certs' NotBefore times.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, initialLastKnownGoodTime);
// Attempt to set a Last Known Good Time that is before the firmware build time. This should fail.
newTime = System::Clock::Seconds32(buildTime.count() - 1000);
EXPECT_NE(fabricTable.SetLastKnownGoodChipEpochTime(newTime), CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, initialLastKnownGoodTime);
// Attempt to set a Last Known Good Time that is before our certificates' NotBefore times. This should fail.
newTime = System::Clock::Seconds32(testCertNotBeforeTime.count() - 1000);
EXPECT_NE(fabricTable.SetLastKnownGoodChipEpochTime(newTime), CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, initialLastKnownGoodTime);
// Attempt to set a Last Known Good Time that at our current value.
EXPECT_EQ(fabricTable.SetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, initialLastKnownGoodTime);
// Attempt to set Last Known Good Times that is after our current value.
newTime = System::Clock::Seconds32(initialLastKnownGoodTime.count() + 1000);
EXPECT_EQ(fabricTable.SetLastKnownGoodChipEpochTime(newTime), CHIP_NO_ERROR);
// Verify Last Known Good Time is updated.
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, newTime);
}
{
// Verify that Last Known Good Time was persisted.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
System::Clock::Seconds32 lastKnownGoodTime;
EXPECT_EQ(fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime), CHIP_NO_ERROR);
EXPECT_EQ(lastKnownGoodTime, newTime);
}
}
}
// Test adding 2 fabrics, updating 1, removing 1
TEST_F(TestFabricTable, TestBasicAddNocUpdateNocFlow)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
Credentials::TestOnlyLocalCertificateAuthority fabric44CertAuthority;
chip::TestPersistentStorageDelegate storage;
// Uncomment the next line for superior debugging powers if you blow-up this test
// storage.SetLoggingLevel(chip::TestPersistentStorageDelegate::LoggingLevel::kLogMutation);
// Initialize test CA and a Fabric 11 externally owned key
EXPECT_TRUE(fabric11CertAuthority.Init().IsSuccess());
EXPECT_TRUE(fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
chip::Crypto::P256Keypair fabric11Node55Keypair; // Fabric ID 11,
EXPECT_EQ(fabric11Node55Keypair.Initialize(Crypto::ECPKeyTarget::ECDSA), CHIP_NO_ERROR);
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 1);
}
size_t numFabricsIterated = 0;
size_t numStorageKeysAtStart = storage.GetNumKeys();
// Sequence 1: Add node ID 55 on fabric 11, using externally owned key and no ICAC --> Yield fabricIndex 1
{
FabricId fabricId = 11;
NodeId nodeId = 55;
EXPECT_EQ(fabric11CertAuthority.SetIncludeIcac(false)
.GenerateNocChain(fabricId, nodeId, fabric11Node55Keypair.Pubkey())
.GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric11CertAuthority.GetRcac();
ByteSpan noc = fabric11CertAuthority.GetNoc();
// Validate iterator sees nothing yet
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 0u);
EXPECT_FALSE(saw1);
}
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
{
// No pending root cert yet.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_ERROR_NOT_FOUND);
}
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
{
// Now have a pending root cert.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_NO_ERROR);
EXPECT_TRUE(fetchedSpan.data_equal(rcac));
}
FabricIndex newFabricIndex = kUndefinedFabricIndex;
bool keyIsExternallyOwned = true;
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingFabricWithProvidedOpKey(noc, ByteSpan{}, kVendorId, &fabric11Node55Keypair,
keyIsExternallyOwned, &newFabricIndex),
CHIP_NO_ERROR);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.FabricCount(), 1);
{
// No more pending root cert; it's associated with a fabric now.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_ERROR_NOT_FOUND);
}
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAtStart);
// Next fabric index has not been updated yet.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 1);
}
// Validate iterator sees pending
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), nodeId);
EXPECT_EQ(iterFabricInfo.GetFabricId(), fabricId);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
// Commit, now storage should have keys
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(storage.GetNumKeys(), (numStorageKeysAtStart + 4)); // 2 opcerts + fabric metadata + index
// Next fabric index has been updated.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 2);
}
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), newFabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), nodeId);
EXPECT_EQ(fabricInfo->GetFabricId(), fabricId);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fabric11Node55Keypair.Pubkey().ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
// Validate iterator sees committed
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), nodeId);
EXPECT_EQ(iterFabricInfo.GetFabricId(), fabricId);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
size_t numStorageAfterFirstAdd = storage.GetNumKeys();
// Sequence 2: Add node ID 999 on fabric 44, using operational keystore and ICAC --> Yield fabricIndex 2
{
FabricId fabricId = 44;
NodeId nodeId = 999;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
// Next fabric index should still be the same as before.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 2);
}
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterFirstAdd);
// Next fabric index has not been updated yet.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 2);
}
// Commit, now storage should have keys
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(storage.GetNumKeys(),
(numStorageAfterFirstAdd + 5)); // 3 opcerts + fabric metadata + 1 operational key
// Next fabric index has been updated.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), newFabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), nodeId);
EXPECT_EQ(fabricInfo->GetFabricId(), fabricId);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256PublicKey nocPubKey;
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Verify we can now see 2 fabrics with the iterator
{
numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 11u);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 999u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 2u);
EXPECT_TRUE(saw1);
EXPECT_TRUE(saw2);
}
}
size_t numStorageAfterSecondAdd = storage.GetNumKeys();
// Sequence 3: Update node ID 999 to 1000 on fabric 44, using operational keystore and no ICAC --> Stays fabricIndex 2
{
FabricId fabricId = 44;
NodeId nodeId = 1000;
FabricIndex fabricIndex = 2;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
// Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(2)), csrSpan),
CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(2, noc, ByteSpan{}, FabricTable::AdvertiseIdentity::No),
CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterSecondAdd);
// Validate iterator sees the pending data
{
numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 11u);
EXPECT_TRUE(iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1000u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
EXPECT_FALSE(iterFabricInfo.ShouldAdvertiseIdentity());
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 2u);
EXPECT_TRUE(saw1);
EXPECT_TRUE(saw2);
}
// Commit, now storage should have keys
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(storage.GetNumKeys(), (numStorageAfterSecondAdd - 1)); // ICAC got deleted
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), fabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), nodeId);
EXPECT_EQ(fabricInfo->GetFabricId(), fabricId);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256PublicKey nocPubKey;
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Validate iterator sees the committed update
{
numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 11u);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1000u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 2u);
EXPECT_TRUE(saw1);
EXPECT_TRUE(saw2);
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
}
size_t numStorageAfterUpdate = storage.GetNumKeys();
// Sequence 4: Rename fabric index 2, applies immediately when nothing pending
{
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(fabricTable.SetFabricLabel(2, "roboto"_span), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterUpdate); // Number of keys unchanged
// Validate basic contents
{
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 1000u);
EXPECT_EQ(fabricInfo->GetFabricId(), 44u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_TRUE(fabricInfo->GetFabricLabel().data_equal(CharSpan{ "roboto", strlen("roboto") }));
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
}
// Sequence 5: Remove FabricIndex 1 (FabricId 11, NodeId 55), make sure FabricIndex 2 (FabricId 44, NodeId 1000) still exists
{
// Remove the fabric: no commit needed
{
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(fabricTable.Delete(1), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(), (numStorageAfterUpdate - 3)); // Deleted NOC, RCAC, Metadata
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
// Validate contents of Fabric Index 2 is still OK
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 1000u);
EXPECT_EQ(fabricInfo->GetFabricId(), 44u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_TRUE(fabricInfo->GetFabricLabel().data_equal(CharSpan{ "roboto", strlen("roboto") }));
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(2, rootPublicKeyOfFabric), CHIP_NO_ERROR);
// Validate that fabric has the correct operational key by verifying a signature
{
uint8_t nocBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
EXPECT_EQ(fabricTable.FetchNOCCert(2, nocSpan), CHIP_NO_ERROR);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(nocSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey nocPubKey(certificates.GetCertSet()[0].mPublicKey);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Validate iterator only sees the remaining fabric
{
numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1000u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_FALSE(saw1);
EXPECT_TRUE(saw2);
}
}
}
TEST_F(TestFabricTable, TestAddMultipleSameRootDifferentFabricId)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
uint8_t rcac1Buf[kMaxCHIPCertLength];
MutableByteSpan rcac1Span{ rcac1Buf };
// First scope: add FabricID 1111, node ID 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
// Keep a copy for second scope check
CopySpanToMutableSpan(rcac, rcac1Span);
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterFirstAdd, 7u); // Metadata, index, 3 certs, 1 opkey, last known good time
// Second scope: add FabricID 2222, node ID 66, same root as first
{
FabricId fabricId = 2222;
NodeId nodeId = 66;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac2 = fabricCertAuthority.GetRcac();
EXPECT_TRUE(rcac2.data_equal(rcac1Span));
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac2), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 2222u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterSecondAdd, (numStorageKeysAfterFirstAdd + 5)); // Add 3 certs, 1 metadata, 1 opkey
}
TEST_F(TestFabricTable, TestAddMultipleSameFabricIdDifferentRoot)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority1;
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority2;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority1.Init().IsSuccess());
EXPECT_TRUE(fabricCertAuthority2.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
uint8_t rcac1Buf[kMaxCHIPCertLength];
MutableByteSpan rcac1Span{ rcac1Buf };
// First scope: add FabricID 1111, node ID 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority1.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority1.GetRcac();
// Keep a copy for second scope check
CopySpanToMutableSpan(rcac, rcac1Span);
ByteSpan icac = fabricCertAuthority1.GetIcac();
ByteSpan noc = fabricCertAuthority1.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterFirstAdd, 7u); // Metadata, index, 3 certs, 1 opkey, last known good time
// Second scope: add FabricID 1111, node ID 66, different root from first
{
FabricId fabricId = 1111;
NodeId nodeId = 66;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority2.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac2 = fabricCertAuthority2.GetRcac();
EXPECT_FALSE(rcac2.data_equal(rcac1Span));
ByteSpan icac = fabricCertAuthority2.GetIcac();
ByteSpan noc = fabricCertAuthority2.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac2), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterSecondAdd, (numStorageKeysAfterFirstAdd + 5)); // Add 3 certs, 1 metadata, 1 opkey
}
TEST_F(TestFabricTable, TestPersistence)
{
/**
*
* - Create an outer scope with storage delegate
* - Keep buffer slots for the operational public keys of the 2 fabrics added in the next scope
*
* - Create a new scope with a ScopedFabricTable
* - Add 2 fabrics, fully committed, using OperationalKeystore-based storage (e.g. CSR for opkey)
* - Make sure to save public keys in other scope for next step
*
* - Create a new scope with a ScopedFabricTable
* - Validate that after init, it has 2 fabrics and the 2 fabrics match fabric indices expected,
* and that the fabric tables are usable, and that NOC can be extracted for each, and that
* its public key matches expectation, and that they match the public keys stored in outer scope
* and verify you can sign and verify messages with the opkey
*
*/
Crypto::P256PublicKey fIdx1PublicKey;
Crypto::P256PublicKey fIdx2PublicKey;
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// First scope: add 2 fabrics with same root: (1111, 2222), commit them, keep track of public keys
{
// Initialize a FabricTable
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// Add Fabric 1111 Node Id 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx1PublicKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
}
// Add Fabric 2222 Node Id 66, no ICAC
{
FabricId fabricId = 2222;
NodeId nodeId = 66;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex),
CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 2222u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx2PublicKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx2PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
}
EXPECT_EQ(fabricTable.FabricCount(), 2);
// Verify we can now see 2 fabrics with the iterator
{
size_t numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 1111u);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 66u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 2222u);
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 2u);
EXPECT_TRUE(saw1);
EXPECT_TRUE(saw2);
}
// Next fabric index should now be 3, since we added 1 and 2 above.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
}
// Global: Last known good time + fabric index = 2
// Fabric 1111: Metadata, 1 opkey, RCAC/ICAC/NOC = 5
// Fabric 2222: Metadata, 1 opkey, RCAC/NOC = 4
EXPECT_EQ(storage.GetNumKeys(), (2u + 5u + 4u));
// Second scope: Validate that a fresh FabricTable loads the previously committed fabrics on Init.
{
// Initialize a FabricTable
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 2);
// Verify we can see 2 fabrics with the iterator
{
size_t numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 1111u);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 66u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 2222u);
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 2u);
EXPECT_TRUE(saw1);
EXPECT_TRUE(saw2);
}
// Validate contents of Fabric 2222
{
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchRootCert(2, rcacSpan), CHIP_NO_ERROR);
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcacSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 2222u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(2, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx2PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
}
// Validate contents of Fabric 1111
{
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchRootCert(1, rcacSpan), CHIP_NO_ERROR);
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcacSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(1, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(1, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Validate that signing with Fabric index 2 fails to verify with fabric index 1
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig),
CHIP_ERROR_INVALID_SIGNATURE);
}
}
// Validate that next fabric index is still 3;
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 3);
}
}
}
TEST_F(TestFabricTable, TestAddNocFailSafe)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
Credentials::TestOnlyLocalCertificateAuthority fabric44CertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabric11CertAuthority.Init().IsSuccess());
EXPECT_TRUE(fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
size_t numFabricsIterated = 0;
size_t numStorageKeysAtStart = storage.GetNumKeys();
// Sequence 1: Add node ID 55 on fabric 11, see that pending works, and that revert works
{
FabricId fabricId = 11;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabric11CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric11CertAuthority.GetRcac();
ByteSpan noc = fabric11CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 1);
}
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex),
CHIP_NO_ERROR);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.FabricCount(), 1);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAtStart); // Nothing yet
// Validate iterator sees pending
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), nodeId);
EXPECT_EQ(iterFabricInfo.GetFabricId(), fabricId);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
// Revert, should see nothing yet
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// No started except fabric index metadata
EXPECT_EQ(storage.GetNumKeys(), (numStorageKeysAtStart + 1));
// Validate iterator sees nothing
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 0u);
EXPECT_FALSE(saw1);
}
// Validate next fabric index has not changed.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(nextFabricIndex, 1);
}
}
size_t numStorageAfterRevert = storage.GetNumKeys();
// Sequence 2: Add node ID 999 on fabric 44, using operational keystore and ICAC --> Yield fabricIndex 1
{
FabricId fabricId = 44;
NodeId nodeId = 999;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterRevert);
// Commit, now storage should have keys
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(),
(numStorageAfterRevert + 5)); // 3 opcerts + fabric metadata + 1 operational key
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), newFabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), nodeId);
EXPECT_EQ(fabricInfo->GetFabricId(), fabricId);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256PublicKey nocPubKey;
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Verify we can now see the fabric with the iterator
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 999u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
size_t numStorageAfterAdd = storage.GetNumKeys();
// Sequence 3: Do a RevertPendingFabricData() again, see that it doesn't affect existing fabric
{
// Revert, should should look like a no-op
fabricTable.RevertPendingFabricData();
// No change of storage
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd);
// Verify we can still see the fabric with the iterator
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 999u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
}
TEST_F(TestFabricTable, TestUpdateNocFailSafe)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
Credentials::TestOnlyLocalCertificateAuthority fabric44CertAuthority;
chip::TestPersistentStorageDelegate storage;
storage.SetLoggingLevel(chip::TestPersistentStorageDelegate::LoggingLevel::kLogMutation);
EXPECT_TRUE(fabric11CertAuthority.Init().IsSuccess());
EXPECT_TRUE(fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
size_t numFabricsIterated = 0;
size_t numStorageKeysAtStart = storage.GetNumKeys();
// Sequence 1: Add node ID 999 on fabric 44, using operational keystore and ICAC --> Yield fabricIndex 1
{
FabricId fabricId = 44;
NodeId nodeId = 999;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAtStart);
// Commit, now storage should have keys
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(),
(numStorageKeysAtStart + 6)); // 3 opcerts + fabric metadata + 1 operational key + LKGT + fabric index
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), newFabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), nodeId);
EXPECT_EQ(fabricInfo->GetFabricId(), fabricId);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256PublicKey nocPubKey;
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Verify we can now see the fabric with the iterator
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 999u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
size_t numStorageAfterAdd = storage.GetNumKeys();
// Sequence 2: Do an Update to NodeId 1000, with no ICAC, but revert it
{
FabricId fabricId = 44;
NodeId nodeId = 1000;
FabricIndex fabricIndex = 1;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
// Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan),
CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd);
// Validate iterator sees the pending data
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1000u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
EXPECT_TRUE(iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
// Revert, should see Node ID 999 again
fabricTable.RevertPendingFabricData();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
{
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), fabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), 999u);
EXPECT_EQ(fabricInfo->GetFabricId(), 44u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
uint8_t nocBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
EXPECT_EQ(fabricTable.FetchNOCCert(1, nocSpan), CHIP_NO_ERROR);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(nocSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey nocPubKey(certificates.GetCertSet()[0].mPublicKey);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Validate iterator sees the previous fabric
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 999u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
// Sequence 3: Do an Update to NodeId 1001, with no ICAC, but commit it
{
FabricId fabricId = 44;
NodeId nodeId = 1001;
FabricIndex fabricIndex = 1;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
// Make sure to tag fabric index to pending opkey: otherwise the UpdateNOC fails
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan),
CHIP_NO_ERROR);
EXPECT_EQ(fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
// No storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd);
// Validate iterator sees the pending data
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1001u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
EXPECT_TRUE(iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
// Commit, should see Node ID 1001, and 1 less cert in the storage
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(storage.GetNumKeys(), numStorageAfterAdd - 1);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), fabricIndex);
EXPECT_EQ(fabricInfo->GetNodeId(), 1001u);
EXPECT_EQ(fabricInfo->GetFabricId(), 44u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256PublicKey nocPubKey;
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey), CHIP_NO_ERROR);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
// Validate iterator sees the updated fabric
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 1001u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 44u);
saw1 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
}
}
}
TEST_F(TestFabricTable, TestAddRootCertFailSafe)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabric11CertAuthority.Init().IsSuccess());
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// Add a root cert, see that pending works, and that revert works
{
ByteSpan rcac = fabric11CertAuthority.GetRcac();
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
{
// No pending root cert yet.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_ERROR_NOT_FOUND);
}
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
{
// Now have a pending root cert.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_NO_ERROR);
EXPECT_TRUE(fetchedSpan.data_equal(rcac));
}
// Revert
fabricTable.RevertPendingFabricData();
{
// No pending root cert anymore.
MutableByteSpan fetchedSpan{ rcacBuf };
EXPECT_EQ(fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan), CHIP_ERROR_NOT_FOUND);
}
}
}
TEST_F(TestFabricTable, TestSequenceErrors)
{
// TODO: Write test
}
TEST_F(TestFabricTable, TestFabricLabelChange)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// First scope: add FabricID 1111, node ID 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterFirstAdd, 7u); // Metadata, index, 3 certs, 1 opkey, last known good time
// Second scope: set FabricLabel to "acme fabric", make sure it cannot be reverted
{
// Fabric label starts unset from prior scope
CharSpan fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_EQ(fabricLabel.size(), 0u);
// Set a valid name
EXPECT_EQ(fabricTable.SetFabricLabel(1, "acme fabric"_span), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_TRUE(fabricLabel.data_equal("acme fabric"_span));
// Revert pending fabric data. Should not revert name since nothing pending.
fabricTable.RevertPendingFabricData();
fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_TRUE(fabricLabel.data_equal("acme fabric"_span));
// Verify we fail to set too large a label (> kFabricLabelMaxLengthInBytes)
CharSpan fabricLabelTooBig = "012345678901234567890123456789123456"_span;
EXPECT_GT(fabricLabelTooBig.size(), chip::kFabricLabelMaxLengthInBytes);
EXPECT_EQ(fabricTable.SetFabricLabel(1, fabricLabelTooBig), CHIP_ERROR_INVALID_ARGUMENT);
fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_TRUE(fabricLabel.data_equal("acme fabric"_span));
}
// Third scope: set fabric label after an update, it sticks, but then goes back after revert
{
FabricId fabricId = 1111;
NodeId nodeId = 66; // Update node ID from 55 to 66
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan),
CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, icac), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
// Validate contents prior to change/revert
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
CharSpan fabricLabel = fabricInfo->GetFabricLabel();
EXPECT_TRUE(fabricLabel.data_equal("acme fabric"_span));
// Update fabric label
fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.SetFabricLabel(1, "roboto fabric"_span), CHIP_NO_ERROR);
fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_TRUE(fabricLabel.data_equal("roboto fabric"_span));
// Revert pending fabric data. Should revert name to "acme fabric"
fabricTable.RevertPendingFabricData();
fabricLabel = "placeholder"_span;
EXPECT_EQ(fabricTable.GetFabricLabel(1, fabricLabel), CHIP_NO_ERROR);
EXPECT_TRUE(fabricLabel.data_equal("acme fabric"_span));
}
}
TEST_F(TestFabricTable, TestCompressedFabricId)
{
// TODO: Write test
}
TEST_F(TestFabricTable, TestFabricLookup)
{
// Initialize a fabric table.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
EXPECT_EQ(LoadTestFabric_Node02_01(fabricTable, /* doCommit = */ true, FabricTable::AdvertiseIdentity::No), CHIP_NO_ERROR);
// Attempt lookup of the Root01 fabric.
{
Crypto::P256PublicKey key;
EXPECT_GE(key.Length(), TestCerts::sTestCert_Root01_PublicKey.size());
if (key.Length() < TestCerts::sTestCert_Root01_PublicKey.size())
{
return;
}
memcpy(key.Bytes(), TestCerts::sTestCert_Root01_PublicKey.data(), TestCerts::sTestCert_Root01_PublicKey.size());
auto fabricInfo = fabricTable.FindFabric(key, 0xFAB000000000001D);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_TRUE(fabricInfo->ShouldAdvertiseIdentity());
}
// Attempt lookup of the Root02 fabric.
{
Crypto::P256PublicKey key;
EXPECT_GE(key.Length(), TestCerts::sTestCert_Root02_PublicKey.size());
if (key.Length() < TestCerts::sTestCert_Root02_PublicKey.size())
{
return;
}
memcpy(key.Bytes(), TestCerts::sTestCert_Root02_PublicKey.data(), TestCerts::sTestCert_Root02_PublicKey.size());
auto fabricInfo = fabricTable.FindFabric(key, 0xFAB000000000001D);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_FALSE(fabricInfo->ShouldAdvertiseIdentity());
}
// Attempt lookup of FabricIndex 0 --> should always fail.
{
EXPECT_EQ(fabricTable.FindFabricWithIndex(0), nullptr);
}
}
TEST_F(TestFabricTable, TestFetchCATs)
{
// Initialize a fabric table.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&testStorage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(LoadTestFabric_Node01_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
EXPECT_EQ(LoadTestFabric_Node02_01(fabricTable, /* doCommit = */ true), CHIP_NO_ERROR);
// Attempt Fetching fabric index 1 CATs and verify contents.
{
CATValues cats;
EXPECT_EQ(fabricTable.FetchCATs(1, cats), CHIP_NO_ERROR);
// Test fabric NOCs don't contain any CATs.
EXPECT_EQ(cats, kUndefinedCATs);
}
// Attempt Fetching fabric index 2 CATs and verify contents.
{
CATValues cats;
EXPECT_EQ(fabricTable.FetchCATs(2, cats), CHIP_NO_ERROR);
// Test fabric NOCs don't contain any CATs.
EXPECT_EQ(cats, kUndefinedCATs);
}
// TODO(#20335): Add test cases for NOCs that actually embed CATs
}
// Validate that adding the same fabric twice fails (same root, same FabricId)
TEST_F(TestFabricTable, TestAddNocRootCollision)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// First scope: add FabricID 1111, node ID 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex,
FabricTable::AdvertiseIdentity::No),
CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_FALSE(fabricInfo->ShouldAdvertiseIdentity());
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterFirstAdd, 7u); // Metadata, index, 3 certs, 1 opkey, last known good time
// Second scope: add FabricID 1111, node ID 55 *again* --> Collision of Root/FabricID with existing
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex),
CHIP_ERROR_FABRIC_EXISTS);
EXPECT_EQ(fabricTable.FabricCount(), 1);
CHIP_ERROR err = fabricTable.CommitPendingFabricData();
printf("err = %" CHIP_ERROR_FORMAT "\n", err.Format());
EXPECT_EQ(err, CHIP_ERROR_INCORRECT_STATE);
// Validate contents of Fabric index 1 still valid
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(1, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Ensure no new persisted keys after failed colliding add
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAfterFirstAdd);
// Third scope: add FabricID 2222, node ID 55 --> Not colliding, should work. The failing commit above]
// should have been enough of a revert that this scope succeeds without any additional revert.
{
FabricId fabricId = 2222;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_TRUE(fabricInfo->ShouldAdvertiseIdentity());
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 2222u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterSecondAdd, (numStorageKeysAfterFirstAdd + 5)); // Metadata, 3 certs, 1 opkey
}
TEST_F(TestFabricTable, TestInvalidChaining)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
Credentials::TestOnlyLocalCertificateAuthority differentCertAuthority;
chip::TestPersistentStorageDelegate storage;
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
EXPECT_TRUE(differentCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.FabricCount(), 0);
// Try to add fabric with either the NOC not chaining properly, or ICAC not chaining properly, fail,
// then succeed with proper chaining
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
// Generate same cert chain from two different roots
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
EXPECT_EQ(differentCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
ByteSpan otherIcac = differentCertAuthority.GetIcac();
ByteSpan otherNoc = differentCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
// Add with NOC not chaining to ICAC: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err = fabricTable.AddNewPendingFabricWithOperationalKeystore(otherNoc, icac, kVendorId, &newFabricIndex);
EXPECT_NE(err, CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 0);
}
// Add with ICAC not chaining to root: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err = fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, otherIcac, kVendorId, &newFabricIndex);
EXPECT_NE(err, CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 0);
}
// Add with NOC and ICAC chaining together, but not to root: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err =
fabricTable.AddNewPendingFabricWithOperationalKeystore(otherNoc, otherIcac, kVendorId, &newFabricIndex);
EXPECT_NE(err, CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 0);
}
// Revert state, start tests without ICAC
fabricTable.RevertPendingFabricData();
// Generate same cert chain from two different roots
csrSpan = MutableByteSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(), CHIP_NO_ERROR);
EXPECT_EQ(differentCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
rcac = fabricCertAuthority.GetRcac();
noc = fabricCertAuthority.GetNoc();
otherNoc = differentCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
// Add with NOC not chaining to RCAC: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err =
fabricTable.AddNewPendingFabricWithOperationalKeystore(otherNoc, ByteSpan{}, kVendorId, &newFabricIndex);
EXPECT_NE(err, CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 0);
}
// Add properly now
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex),
CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
}
}
TEST_F(TestFabricTable, TestEphemeralKeys)
{
// Initialize a fabric table with operational keystore
{
chip::TestPersistentStorageDelegate storage;
// Initialize a FabricTable
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256Keypair * ephemeralKeypair = fabricTable.AllocateEphemeralKeypairForCASE();
ASSERT_NE(ephemeralKeypair, nullptr);
EXPECT_EQ(ephemeralKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA), CHIP_NO_ERROR);
EXPECT_EQ(ephemeralKeypair->ECDSA_sign_msg(message, sizeof(message), sig), CHIP_NO_ERROR);
EXPECT_EQ(ephemeralKeypair->Pubkey().ECDSA_validate_msg_signature(message, sizeof(message), sig), CHIP_NO_ERROR);
fabricTable.ReleaseEphemeralKeypair(ephemeralKeypair);
}
// Use a fabric table without an operational keystore: should still work
{
chip::TestPersistentStorageDelegate storage;
chip::Credentials::PersistentStorageOpCertStore opCertStore;
EXPECT_EQ(opCertStore.Init(&storage), CHIP_NO_ERROR);
FabricTable fabricTable;
FabricTable::InitParams initParams;
initParams.storage = &storage;
initParams.opCertStore = &opCertStore;
EXPECT_EQ(fabricTable.Init(initParams), CHIP_NO_ERROR);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256Keypair * ephemeralKeypair = fabricTable.AllocateEphemeralKeypairForCASE();
ASSERT_NE(ephemeralKeypair, nullptr);
EXPECT_EQ(ephemeralKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA), CHIP_NO_ERROR);
EXPECT_EQ(ephemeralKeypair->ECDSA_sign_msg(message, sizeof(message), sig), CHIP_NO_ERROR);
EXPECT_EQ(ephemeralKeypair->Pubkey().ECDSA_validate_msg_signature(message, sizeof(message), sig), CHIP_NO_ERROR);
fabricTable.ReleaseEphemeralKeypair(ephemeralKeypair);
fabricTable.Shutdown();
opCertStore.Finish();
}
}
TEST_F(TestFabricTable, TestCommitMarker)
{
Crypto::P256PublicKey fIdx1PublicKey;
Crypto::P256PublicKey fIdx2PublicKey;
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
// Log verbosity on this test helps debug significantly
storage.SetLoggingLevel(chip::TestPersistentStorageDelegate::LoggingLevel::kLogMutationAndReads);
EXPECT_TRUE(fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
size_t numStorageKeysAfterFirstAdd = 0;
// First scope: add 2 fabrics with same root:
// - FabricID 1111, Node ID 55
// - FabricID 2222, Node ID 66
// - Abort commit on second fabric
{
// Initialize a fabric table
ScopedFabricTable fabricTableHolder;
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
EXPECT_EQ(fabricTable.GetDeletedFabricFromCommitMarker(), kUndefinedFabricIndex);
EXPECT_EQ(fabricTable.FabricCount(), 0);
// Add Fabric 1111 Node Id 55
{
FabricId fabricId = 1111;
NodeId nodeId = 55;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 0);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(newFabricIndex, 1);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_NO_ERROR);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 1);
EXPECT_EQ(fabricInfo->GetNodeId(), 55u);
EXPECT_EQ(fabricInfo->GetFabricId(), 1111u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
EXPECT_EQ(VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx1PublicKey), CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig), CHIP_NO_ERROR);
EXPECT_EQ(fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig), CHIP_NO_ERROR);
}
}
numStorageKeysAfterFirstAdd = storage.GetNumKeys();
EXPECT_EQ(numStorageKeysAfterFirstAdd, 7u); // Metadata, index, 3 certs, 1 opkey, last known good time
// The following test requires test methods not available on all builds.
// TODO: Debug why some CI jobs don't set it properly.
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
// Add Fabric 2222 Node Id 66, no ICAC *** AND ABORT COMMIT ***
{
FabricId fabricId = 2222;
NodeId nodeId = 66;
uint8_t csrBuf[chip::Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csrSpan{ csrBuf };
EXPECT_EQ(fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan), CHIP_NO_ERROR);
EXPECT_EQ(fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus(),
CHIP_NO_ERROR);
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
EXPECT_EQ(fabricTable.FabricCount(), 1);
EXPECT_EQ(fabricTable.AddNewPendingTrustedRootCert(rcac), CHIP_NO_ERROR);
FabricIndex newFabricIndex = kUndefinedFabricIndex;
EXPECT_EQ(fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex),
CHIP_NO_ERROR);
EXPECT_EQ(fabricTable.FabricCount(), 2);
EXPECT_EQ(newFabricIndex, 2);
// Validate contents of pending
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
ASSERT_NE(fabricInfo, nullptr);
Credentials::ChipCertificateSet certificates;
EXPECT_EQ(certificates.Init(1), CHIP_NO_ERROR);
EXPECT_EQ(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)), CHIP_NO_ERROR);
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
EXPECT_EQ(fabricInfo->GetFabricIndex(), 2);
EXPECT_EQ(fabricInfo->GetNodeId(), 66u);
EXPECT_EQ(fabricInfo->GetFabricId(), 2222u);
EXPECT_EQ(fabricInfo->GetVendorId(), kVendorId);
EXPECT_EQ(fabricInfo->GetFabricLabel().size(), 0u);
Crypto::P256PublicKey rootPublicKeyOfFabric;
EXPECT_EQ(fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric), CHIP_NO_ERROR);
EXPECT_TRUE(rootPublicKeyOfFabric.Matches(rcacPublicKey));
// Make sure no additional storage yet
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAfterFirstAdd);
// --> FORCE AN ERROR ON COMMIT that will BYPASS commit clean-up (similar to reboot during commit)
fabricTable.SetForceAbortCommitForTest(true);
EXPECT_EQ(fabricTable.CommitPendingFabricData(), CHIP_ERROR_INTERNAL);
// Check that there are more keys now, partially committed: at least a Commit Marker (+1)
// and some more keys from the aborted process.
EXPECT_GT(storage.GetNumKeys(), (numStorageKeysAfterFirstAdd + 1));
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
{
storage.DumpKeys();
// Initialize a FabricTable again. Make sure it succeeds in initing.
ScopedFabricTable fabricTableHolder;
EXPECT_GT(storage.GetNumKeys(), (numStorageKeysAfterFirstAdd + 1));
EXPECT_EQ(fabricTableHolder.Init(&storage), CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Make sure that after init, the fabricTable has only 1 fabric
EXPECT_EQ(fabricTable.FabricCount(), 1);
// Make sure it caught the last partially committed fabric
EXPECT_EQ(fabricTable.GetDeletedFabricFromCommitMarker(), 2);
// Second read must return kUndefinedFabricIndex
EXPECT_EQ(fabricTable.GetDeletedFabricFromCommitMarker(), kUndefinedFabricIndex);
{
// Here we would do other clean-ups (e.g. see Server.cpp that uses the above) and then
// clear the commit marker after.
fabricTable.ClearCommitMarker();
}
// Make sure that all other pending storage got deleted
EXPECT_EQ(storage.GetNumKeys(), numStorageKeysAfterFirstAdd);
// Verify we can only see 1 fabric with the iterator
{
size_t numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
EXPECT_EQ(iterFabricInfo.GetNodeId(), 55u);
EXPECT_EQ(iterFabricInfo.GetFabricId(), 1111u);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
saw2 = true;
}
}
EXPECT_EQ(numFabricsIterated, 1u);
EXPECT_TRUE(saw1);
EXPECT_FALSE(saw2);
}
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
} // namespace