blob: 8dd65192441aea98845adabf777d02d093af0780 [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 <nlunit-test.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 <lib/support/UnitTestExtendedAssertions.h>
#include <lib/support/UnitTestRegistration.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(nlTestSuite * inSuite, 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(nlTestSuite * inSuite, 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(nlTestSuite * inSuite, 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);
NL_TEST_ASSERT(inSuite,
opKeysSerialized.SetLength(TestCerts::sTestCert_Node02_01_PublicKey.size() +
TestCerts::sTestCert_Node02_01_PrivateKey.size()) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, opKey_Node02_01.Deserialize(opKeysSerialized) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcacSpan) == CHIP_NO_ERROR);
CHIP_ERROR err =
fabricTable.AddNewPendingFabricWithProvidedOpKey(nocSpan, icacSpan, VendorId::TestVendor1, &opKey_Node02_01,
/*isExistingOpKeyExternallyOwned =*/true, &fabricIndex, advertiseIdentity);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
if (doCommit)
{
err = fabricTable.CommitPendingFabricData();
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
}
return err;
}
void TestLastKnownGoodTimeInit(nlTestSuite * inSuite, void * inContext)
{
// Fabric table init should init Last Known Good Time to the firmware build time.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
System::Clock::Seconds32 lastKnownGoodChipEpochTime;
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime) == CHIP_NO_ERROR);
System::Clock::Seconds32 firmwareBuildTime;
NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(firmwareBuildTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodChipEpochTime == firmwareBuildTime);
}
void TestCollidingFabrics(nlTestSuite * inSuite, void * inContext)
{
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, 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.
//
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, fabricTable, /* doCommit = */ true) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_02(inSuite, 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();
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_02(inSuite, fabricTable, /* doCommit = */ true) == CHIP_NO_ERROR);
ByteSpan rcacSpan(TestCerts::sTestCert_Root01_Chip);
Credentials::P256PublicKeySpan rootPublicKeySpan;
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, ExtractNodeIdFabricIdFromOpCert(origNocSpan, &nodeId, &fabricId) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, fabricTable.FindIdentity(rootPublicKeySpan, fabricId, nodeId) != nullptr);
}
{
chip::Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
ByteSpan origNocSpan(TestCerts::sTestCert_Node01_02_Chip);
NodeId nodeId;
FabricId fabricId;
NL_TEST_ASSERT(inSuite, ExtractNodeIdFabricIdFromOpCert(origNocSpan, &nodeId, &fabricId) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, fabricTable.FindIdentity(rootPublicKeySpan, fabricId, nodeId) != nullptr);
}
}
void TestUpdateLastKnownGoodTime(nlTestSuite * inSuite, void * inContext)
{
// 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;
NL_TEST_ASSERT(inSuite, 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.
NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime);
// Load a test fabric, but do not commit.
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, 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.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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();
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime);
// Now reload the test fabric and commit this time.
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, 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.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime);
// Call revert again. Since we've committed, this is a no-op.
// Last known good time should again be unchanged.
fabricTable.RevertPendingFabricData();
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Verify that last known good time was retained.
System::Clock::Seconds32 lastKnownGoodTime;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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.
NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Load a test fabric, but do not commit.
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime);
// Now test revert. Last known good time should change back to the
// previous value.
fabricTable.RevertPendingFabricData();
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == buildTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
}
{
// Now test loading a fabric and committing.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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();
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime);
}
{
// Test reloading last known good time from persistence.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == testCertNotBeforeTime);
}
}
}
void TestSetLastKnownGoodTime(nlTestSuite * inSuite, void * inContext)
{
// 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;
NL_TEST_ASSERT(inSuite, 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.
NL_TEST_ASSERT(inSuite, DeviceLayer::ConfigurationMgr().SetFirmwareBuildChipEpochTime(buildTime) == CHIP_NO_ERROR);
chip::TestPersistentStorageDelegate testStorage;
System::Clock::Seconds32 newTime;
{
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Load a test fabric
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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);
NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) != CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, 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);
NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) != CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime);
// Attempt to set a Last Known Good Time that at our current value.
NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
// Verify Last Known Good Time is unchanged.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == initialLastKnownGoodTime);
// Attempt to set Last Known Good Times that is after our current value.
newTime = System::Clock::Seconds32(initialLastKnownGoodTime.count() + 1000);
NL_TEST_ASSERT(inSuite, fabricTable.SetLastKnownGoodChipEpochTime(newTime) == CHIP_NO_ERROR);
// Verify Last Known Good Time is updated.
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == newTime);
}
{
// Verify that Last Known Good Time was persisted.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
System::Clock::Seconds32 lastKnownGoodTime;
NL_TEST_ASSERT(inSuite, fabricTable.GetLastKnownGoodChipEpochTime(lastKnownGoodTime) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, lastKnownGoodTime == newTime);
}
}
}
// Test adding 2 fabrics, updating 1, removing 1
void TestBasicAddNocUpdateNocFlow(nlTestSuite * inSuite, void * inContext)
{
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
NL_TEST_ASSERT(inSuite, fabric11CertAuthority.Init().IsSuccess());
NL_TEST_ASSERT(inSuite, fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
chip::Crypto::P256Keypair fabric11Node55Keypair; // Fabric ID 11,
NL_TEST_ASSERT(inSuite, fabric11Node55Keypair.Initialize(Crypto::ECPKeyTarget::ECDSA) == CHIP_NO_ERROR);
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, 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;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric11CertAuthority.SetIncludeIcac(false)
.GenerateNocChain(fabricId, nodeId, fabric11Node55Keypair.Pubkey())
.GetStatus());
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;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 0);
NL_TEST_ASSERT(inSuite, saw1 == false);
}
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
{
// No pending root cert yet.
MutableByteSpan fetchedSpan{ rcacBuf };
NL_TEST_ASSERT(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan) == CHIP_ERROR_NOT_FOUND);
}
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
{
// Now have a pending root cert.
MutableByteSpan fetchedSpan{ rcacBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan));
NL_TEST_ASSERT(inSuite, fetchedSpan.data_equal(rcac));
}
FabricIndex newFabricIndex = kUndefinedFabricIndex;
bool keyIsExternallyOwned = true;
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithProvidedOpKey(noc, ByteSpan{}, kVendorId, &fabric11Node55Keypair,
keyIsExternallyOwned, &newFabricIndex));
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
{
// No more pending root cert; it's associated with a fabric now.
MutableByteSpan fetchedSpan{ rcacBuf };
NL_TEST_ASSERT(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan) == CHIP_ERROR_NOT_FOUND);
}
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageKeysAtStart);
// Next fabric index has not been updated yet.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 1);
}
// Validate iterator sees pending
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == fabricId);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
// Commit, now storage should have keys
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == (numStorageKeysAtStart + 4)); // 2 opcerts + fabric metadata + index
// Next fabric index has been updated.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 2);
}
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == newFabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == fabricId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric11Node55Keypair.Pubkey().ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
// Validate iterator sees committed
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == fabricId);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// Next fabric index should still be the same as before.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 2);
}
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageAfterFirstAdd);
// Next fabric index has not been updated yet.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 2);
}
// Commit, now storage should have keys
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(),
(numStorageAfterFirstAdd + 5)); // 3 opcerts + fabric metadata + 1 operational key
// Next fabric index has been updated.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 3);
}
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == newFabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == fabricId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 11);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 2);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
}
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
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(2)), csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_SUCCESS(
inSuite,
fabricTable.UpdatePendingFabricWithOperationalKeystore(2, noc, ByteSpan{}, FabricTable::AdvertiseIdentity::No));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
// No storage yet
NL_TEST_ASSERT(inSuite, 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 11);
NL_TEST_ASSERT(inSuite, iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, !iterFabricInfo.ShouldAdvertiseIdentity());
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 2);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
// Commit, now storage should have keys
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(), (numStorageAfterSecondAdd - 1)); // ICAC got deleted
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == fabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == fabricId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// Validate iterator sees the committed update
{
numFabricsIterated = 0;
bool saw1 = false;
bool saw2 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 11);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 2);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 3);
}
}
size_t numStorageAfterUpdate = storage.GetNumKeys();
// Sequence 4: Rename fabric index 2, applies immediately when nothing pending
{
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(2, "roboto"_span));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(), numStorageAfterUpdate); // Number of keys unchanged
// Validate basic contents
{
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().data_equal(CharSpan{ "roboto", strlen("roboto") }));
}
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, 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
{
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.Delete(1));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(), (numStorageAfterUpdate - 3)); // Deleted NOC, RCAC, Metadata
}
// Next fabric index has stayed the same.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 3);
}
// Validate contents of Fabric Index 2 is still OK
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().data_equal(CharSpan{ "roboto", strlen("roboto") }));
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(2, rootPublicKeyOfFabric));
}
// Validate that fabric has the correct operational key by verifying a signature
{
uint8_t nocBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchNOCCert(2, nocSpan));
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(nocSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey nocPubKey(certificates.GetCertSet()[0].mPublicKey);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == false);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
}
}
void TestAddMultipleSameRootDifferentFabricId(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
// Keep a copy for second scope check
CopySpanToMutableSpan(rcac, rcac1Span);
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterFirstAdd == 7); // 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac2 = fabricCertAuthority.GetRcac();
NL_TEST_ASSERT(inSuite, rcac2.data_equal(rcac1Span));
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac2));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 2222);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterSecondAdd == (numStorageKeysAfterFirstAdd + 5)); // Add 3 certs, 1 metadata, 1 opkey
}
void TestAddMultipleSameFabricIdDifferentRoot(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority1;
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority2;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabricCertAuthority1.Init().IsSuccess());
NL_TEST_ASSERT(inSuite, fabricCertAuthority2.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority1.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority1.GetRcac();
// Keep a copy for second scope check
CopySpanToMutableSpan(rcac, rcac1Span);
ByteSpan icac = fabricCertAuthority1.GetIcac();
ByteSpan noc = fabricCertAuthority1.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterFirstAdd == 7); // 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority2.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac2 = fabricCertAuthority2.GetRcac();
NL_TEST_ASSERT(inSuite, !rcac2.data_equal(rcac1Span));
ByteSpan icac = fabricCertAuthority2.GetIcac();
ByteSpan noc = fabricCertAuthority2.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac2));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterSecondAdd == (numStorageKeysAfterFirstAdd + 5)); // Add 3 certs, 1 metadata, 1 opkey
}
void TestPersistence(nlTestSuite * inSuite, void * inContext)
{
/**
*
* - 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;
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx1PublicKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
}
// 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 2222);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx2PublicKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, fIdx2PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
}
NL_TEST_ASSERT(inSuite, 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 1111);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 2222);
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 2);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
// Next fabric index should now be 3, since we added 1 and 2 above.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, 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
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == (2 + 5 + 4));
// Second scope: Validate that a fresh FabricTable loads the previously committed fabrics on Init.
{
// Initialize a FabricTable
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 1111);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 2222);
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 2);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == true);
}
// Validate contents of Fabric 2222
{
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootCert(2, rcacSpan));
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcacSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 2222);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(2, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, fIdx2PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
}
// Validate contents of Fabric 1111
{
uint8_t rcacBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootCert(1, rcacSpan));
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcacSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(1, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(1, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// Validate that signing with Fabric index 2 fails to verify with fabric index 1
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(2, ByteSpan{ message }, sig));
NL_TEST_ASSERT(inSuite,
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;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 3);
}
}
}
void TestAddNocFailSafe(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
Credentials::TestOnlyLocalCertificateAuthority fabric44CertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabric11CertAuthority.Init().IsSuccess());
NL_TEST_ASSERT(inSuite, fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric11CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric11CertAuthority.GetRcac();
ByteSpan noc = fabric11CertAuthority.GetNoc();
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, nextFabricIndex, 1);
}
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex));
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageKeysAtStart); // Nothing yet
// Validate iterator sees pending
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == fabricId);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
// Revert, should see nothing yet
fabricTable.RevertPendingFabricData();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
// No started except fabric index metadata
NL_TEST_ASSERT(inSuite, 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;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 0);
NL_TEST_ASSERT(inSuite, saw1 == false);
}
// Validate next fabric index has not changed.
{
FabricIndex nextFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite, fabricTable.PeekFabricIndexForNextAddition(nextFabricIndex) == CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageAfterRevert);
// Commit, now storage should have keys
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(),
(numStorageAfterRevert + 5)); // 3 opcerts + fabric metadata + 1 operational key
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == newFabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == fabricId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
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
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
}
void TestUpdateNocFailSafe(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
Credentials::TestOnlyLocalCertificateAuthority fabric44CertAuthority;
chip::TestPersistentStorageDelegate storage;
storage.SetLoggingLevel(chip::TestPersistentStorageDelegate::LoggingLevel::kLogMutation);
NL_TEST_ASSERT(inSuite, fabric11CertAuthority.Init().IsSuccess());
NL_TEST_ASSERT(inSuite, fabric44CertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan icac = fabric44CertAuthority.GetIcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageKeysAtStart);
// Commit, now storage should have keys
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(),
(numStorageKeysAtStart + 6)); // 3 opcerts + fabric metadata + 1 operational key + LKGT + fabric index
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == newFabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == nodeId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == fabricId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
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
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageAfterAdd);
// Validate iterator sees the pending data
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1000);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
// Revert, should see Node ID 999 again
fabricTable.RevertPendingFabricData();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(), numStorageAfterAdd);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == fabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
uint8_t nocBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchNOCCert(1, nocSpan));
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(nocSpan, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey nocPubKey(certificates.GetCertSet()[0].mPublicKey);
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// Validate iterator sees the previous fabric
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 999);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
// 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
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabric44CertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabric44CertAuthority.GetRcac();
ByteSpan noc = fabric44CertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, ByteSpan{}));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// No storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageAfterAdd);
// Validate iterator sees the pending data
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1001);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, iterFabricInfo.ShouldAdvertiseIdentity());
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
// Commit, should see Node ID 1001, and 1 less cert in the storage
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_EQUALS(inSuite, storage.GetNumKeys(), numStorageAfterAdd - 1);
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == fabricIndex);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 1001);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 44);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(fabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256PublicKey nocPubKey;
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), nocPubKey));
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(fabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, nocPubKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
// Validate iterator sees the updated fabric
{
numFabricsIterated = 0;
bool saw1 = false;
for (const auto & iterFabricInfo : fabricTable)
{
++numFabricsIterated;
if (iterFabricInfo.GetFabricIndex() == 1)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 1001);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 44);
saw1 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
}
}
}
void TestAddRootCertFailSafe(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabric11CertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabric11CertAuthority.Init().IsSuccess());
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan) == CHIP_ERROR_NOT_FOUND);
}
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
{
// Now have a pending root cert.
MutableByteSpan fetchedSpan{ rcacBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan));
NL_TEST_ASSERT(inSuite, fetchedSpan.data_equal(rcac));
}
// Revert
fabricTable.RevertPendingFabricData();
{
// No pending root cert anymore.
MutableByteSpan fetchedSpan{ rcacBuf };
NL_TEST_ASSERT(inSuite, fabricTable.FetchPendingNonFabricAssociatedRootCert(fetchedSpan) == CHIP_ERROR_NOT_FOUND);
}
}
}
void TestSequenceErrors(nlTestSuite * inSuite, void * inContext)
{
// TODO: Write test
}
void TestFabricLabelChange(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterFirstAdd == 7); // 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;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.size() == 0);
// Set a valid name
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, "acme fabric"_span));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
// Revert pending fabric data. Should not revert name since nothing pending.
fabricTable.RevertPendingFabricData();
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
// Verify we fail to set too large a label (> kFabricLabelMaxLengthInBytes)
CharSpan fabricLabelTooBig = "012345678901234567890123456789123456"_span;
NL_TEST_ASSERT(inSuite, fabricLabelTooBig.size() > chip::kFabricLabelMaxLengthInBytes);
NL_TEST_ASSERT(inSuite, fabricTable.SetFabricLabel(1, fabricLabelTooBig) == CHIP_ERROR_INVALID_ARGUMENT);
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}
// 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 };
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AllocatePendingOperationalKey(chip::MakeOptional(static_cast<FabricIndex>(1)), csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.UpdatePendingFabricWithOperationalKeystore(1, noc, icac));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// Validate contents prior to change/revert
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
CharSpan fabricLabel = fabricInfo->GetFabricLabel();
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}
// Update fabric label
CharSpan fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SetFabricLabel(1, "roboto fabric"_span));
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("roboto fabric"_span) == true);
// Revert pending fabric data. Should revert name to "acme fabric"
fabricTable.RevertPendingFabricData();
fabricLabel = "placeholder"_span;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.GetFabricLabel(1, fabricLabel));
NL_TEST_ASSERT(inSuite, fabricLabel.data_equal("acme fabric"_span) == true);
}
}
void TestCompressedFabricId(nlTestSuite * inSuite, void * inContext)
{
// TODO: Write test
}
void TestFabricLookup(nlTestSuite * inSuite, void * inContext)
{
// Initialize a fabric table.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, fabricTable, /* doCommit = */ true) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
LoadTestFabric_Node02_01(inSuite, fabricTable, /* doCommit = */ true, FabricTable::AdvertiseIdentity::No) ==
CHIP_NO_ERROR);
// Attempt lookup of the Root01 fabric.
{
Crypto::P256PublicKey key;
NL_TEST_ASSERT(inSuite, 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);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo == nullptr)
{
return;
}
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->ShouldAdvertiseIdentity());
}
// Attempt lookup of the Root02 fabric.
{
Crypto::P256PublicKey key;
NL_TEST_ASSERT(inSuite, 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);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo == nullptr)
{
return;
}
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, !fabricInfo->ShouldAdvertiseIdentity());
}
// Attempt lookup of FabricIndex 0 --> should always fail.
{
NL_TEST_ASSERT(inSuite, fabricTable.FindFabricWithIndex(0) == nullptr);
}
}
void TestFetchCATs(nlTestSuite * inSuite, void * inContext)
{
// Initialize a fabric table.
chip::TestPersistentStorageDelegate testStorage;
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&testStorage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node01_01(inSuite, fabricTable, /* doCommit = */ true) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, LoadTestFabric_Node02_01(inSuite, fabricTable, /* doCommit = */ true) == CHIP_NO_ERROR);
// Attempt Fetching fabric index 1 CATs and verify contents.
{
CATValues cats;
NL_TEST_ASSERT(inSuite, fabricTable.FetchCATs(1, cats) == CHIP_NO_ERROR);
// Test fabric NOCs don't contain any CATs.
NL_TEST_ASSERT(inSuite, cats == kUndefinedCATs);
}
// Attempt Fetching fabric index 2 CATs and verify contents.
{
CATValues cats;
NL_TEST_ASSERT(inSuite, fabricTable.FetchCATs(2, cats) == CHIP_NO_ERROR);
// Test fabric NOCs don't contain any CATs.
NL_TEST_ASSERT(inSuite, 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)
void TestAddNocRootCollision(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabricCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex,
FabricTable::AdvertiseIdentity::No));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, !fabricInfo->ShouldAdvertiseIdentity());
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
}
size_t numStorageKeysAfterFirstAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterFirstAdd == 7); // 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex) ==
CHIP_ERROR_FABRIC_EXISTS);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
CHIP_ERROR err = fabricTable.CommitPendingFabricData();
printf("err = %" CHIP_ERROR_FORMAT "\n", err.Format());
NL_TEST_ASSERT(inSuite, err == CHIP_ERROR_INCORRECT_STATE);
// Validate contents of Fabric index 1 still valid
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(1, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
}
// Ensure no new persisted keys after failed colliding add
NL_TEST_ASSERT(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->ShouldAdvertiseIdentity());
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 2222);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
}
size_t numStorageKeysAfterSecondAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterSecondAdd == (numStorageKeysAfterFirstAdd + 5)); // Metadata, 3 certs, 1 opkey
}
void TestInvalidChaining(nlTestSuite * inSuite, void * inContext)
{
Credentials::TestOnlyLocalCertificateAuthority fabricCertAuthority;
Credentials::TestOnlyLocalCertificateAuthority differentCertAuthority;
chip::TestPersistentStorageDelegate storage;
NL_TEST_ASSERT(inSuite, fabricCertAuthority.Init().IsSuccess());
NL_TEST_ASSERT(inSuite, differentCertAuthority.Init().IsSuccess());
constexpr uint16_t kVendorId = 0xFFF1u;
// Initialize a fabric table.
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
// Generate same cert chain from two different roots
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
NL_TEST_ASSERT_SUCCESS(inSuite,
differentCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
ByteSpan otherIcac = differentCertAuthority.GetIcac();
ByteSpan otherNoc = differentCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
// Add with NOC not chaining to ICAC: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err = fabricTable.AddNewPendingFabricWithOperationalKeystore(otherNoc, icac, kVendorId, &newFabricIndex);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
}
// Add with ICAC not chaining to root: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err = fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, otherIcac, kVendorId, &newFabricIndex);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, 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);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
}
// Revert state, start tests without ICAC
fabricTable.RevertPendingFabricData();
// Generate same cert chain from two different roots
csrSpan = MutableByteSpan{ csrBuf };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
NL_TEST_ASSERT_SUCCESS(
inSuite, differentCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
rcac = fabricCertAuthority.GetRcac();
noc = fabricCertAuthority.GetNoc();
otherNoc = differentCertAuthority.GetNoc();
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
// Add with NOC not chaining to RCAC: fail
{
FabricIndex newFabricIndex = kUndefinedFabricIndex;
CHIP_ERROR err =
fabricTable.AddNewPendingFabricWithOperationalKeystore(otherNoc, ByteSpan{}, kVendorId, &newFabricIndex);
NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
}
// Add properly now
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
}
}
}
void TestEphemeralKeys(nlTestSuite * inSuite, void * inContext)
{
// Initialize a fabric table with operational keystore
{
chip::TestPersistentStorageDelegate storage;
// Initialize a FabricTable
ScopedFabricTable fabricTableHolder;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256Keypair * ephemeralKeypair = fabricTable.AllocateEphemeralKeypairForCASE();
NL_TEST_ASSERT(inSuite, ephemeralKeypair != nullptr);
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA));
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->ECDSA_sign_msg(message, sizeof(message), sig));
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->Pubkey().ECDSA_validate_msg_signature(message, sizeof(message), sig));
fabricTable.ReleaseEphemeralKeypair(ephemeralKeypair);
}
// Use a fabric table without an operational keystore: should still work
{
chip::TestPersistentStorageDelegate storage;
chip::Credentials::PersistentStorageOpCertStore opCertStore;
NL_TEST_ASSERT_SUCCESS(inSuite, opCertStore.Init(&storage));
FabricTable fabricTable;
FabricTable::InitParams initParams;
initParams.storage = &storage;
initParams.opCertStore = &opCertStore;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.Init(initParams));
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
Crypto::P256Keypair * ephemeralKeypair = fabricTable.AllocateEphemeralKeypairForCASE();
NL_TEST_ASSERT(inSuite, ephemeralKeypair != nullptr);
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->Initialize(Crypto::ECPKeyTarget::ECDSA));
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->ECDSA_sign_msg(message, sizeof(message), sig));
NL_TEST_ASSERT_SUCCESS(inSuite, ephemeralKeypair->Pubkey().ECDSA_validate_msg_signature(message, sizeof(message), sig));
fabricTable.ReleaseEphemeralKeypair(ephemeralKeypair);
fabricTable.Shutdown();
opCertStore.Finish();
}
}
void TestCommitMarker(nlTestSuite * inSuite, void * inContext)
{
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);
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
NL_TEST_ASSERT(inSuite, fabricTable.GetDeletedFabricFromCommitMarker() == kUndefinedFabricIndex);
NL_TEST_ASSERT_EQUALS(inSuite, 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricCertAuthority.SetIncludeIcac(true).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan icac = fabricCertAuthority.GetIcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 0);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(inSuite,
fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, icac, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT(inSuite, newFabricIndex == 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.CommitPendingFabricData());
// Validate contents
const auto * fabricInfo = fabricTable.FindFabricWithIndex(1);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 1);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 1111);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Validate that fabric has the correct operational key by verifying a signature
{
Crypto::P256ECDSASignature sig;
uint8_t message[] = { 'm', 's', 'g' };
NL_TEST_ASSERT_SUCCESS(inSuite, VerifyCertificateSigningRequest(csrSpan.data(), csrSpan.size(), fIdx1PublicKey));
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.SignWithOpKeypair(newFabricIndex, ByteSpan{ message }, sig));
NL_TEST_ASSERT_SUCCESS(inSuite, fIdx1PublicKey.ECDSA_validate_msg_signature(&message[0], sizeof(message), sig));
}
}
numStorageKeysAfterFirstAdd = storage.GetNumKeys();
NL_TEST_ASSERT(inSuite, numStorageKeysAfterFirstAdd == 7); // 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 };
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AllocatePendingOperationalKey(chip::NullOptional, csrSpan));
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricCertAuthority.SetIncludeIcac(false).GenerateNocChain(fabricId, nodeId, csrSpan).GetStatus());
ByteSpan rcac = fabricCertAuthority.GetRcac();
ByteSpan noc = fabricCertAuthority.GetNoc();
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.AddNewPendingTrustedRootCert(rcac));
FabricIndex newFabricIndex = kUndefinedFabricIndex;
NL_TEST_ASSERT_SUCCESS(
inSuite, fabricTable.AddNewPendingFabricWithOperationalKeystore(noc, ByteSpan{}, kVendorId, &newFabricIndex));
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 2);
NL_TEST_ASSERT(inSuite, newFabricIndex == 2);
// Validate contents of pending
const auto * fabricInfo = fabricTable.FindFabricWithIndex(2);
NL_TEST_ASSERT(inSuite, fabricInfo != nullptr);
if (fabricInfo != nullptr)
{
Credentials::ChipCertificateSet certificates;
NL_TEST_ASSERT_SUCCESS(inSuite, certificates.Init(1));
NL_TEST_ASSERT_SUCCESS(inSuite,
certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
Crypto::P256PublicKey rcacPublicKey(certificates.GetCertSet()[0].mPublicKey);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricIndex() == 2);
NL_TEST_ASSERT(inSuite, fabricInfo->GetNodeId() == 66);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricId() == 2222);
NL_TEST_ASSERT(inSuite, fabricInfo->GetVendorId() == kVendorId);
NL_TEST_ASSERT(inSuite, fabricInfo->GetFabricLabel().size() == 0);
Crypto::P256PublicKey rootPublicKeyOfFabric;
NL_TEST_ASSERT_SUCCESS(inSuite, fabricTable.FetchRootPubkey(newFabricIndex, rootPublicKeyOfFabric));
NL_TEST_ASSERT(inSuite, rootPublicKeyOfFabric.Matches(rcacPublicKey));
}
// Make sure no additional storage yet
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() == numStorageKeysAfterFirstAdd);
// --> FORCE AN ERROR ON COMMIT that will BYPASS commit clean-up (similar to reboot during commit)
fabricTable.SetForceAbortCommitForTest(true);
NL_TEST_ASSERT(inSuite, 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.
NL_TEST_ASSERT(inSuite, 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;
NL_TEST_ASSERT(inSuite, storage.GetNumKeys() > (numStorageKeysAfterFirstAdd + 1));
NL_TEST_ASSERT(inSuite, fabricTableHolder.Init(&storage) == CHIP_NO_ERROR);
FabricTable & fabricTable = fabricTableHolder.GetFabricTable();
// Make sure that after init, the fabricTable has only 1 fabric
NL_TEST_ASSERT_EQUALS(inSuite, fabricTable.FabricCount(), 1);
// Make sure it caught the last partially committed fabric
NL_TEST_ASSERT(inSuite, fabricTable.GetDeletedFabricFromCommitMarker() == 2);
// Second read must return kUndefinedFabricIndex
NL_TEST_ASSERT(inSuite, 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
NL_TEST_ASSERT(inSuite, 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)
{
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetNodeId() == 55);
NL_TEST_ASSERT(inSuite, iterFabricInfo.GetFabricId() == 1111);
saw1 = true;
}
if (iterFabricInfo.GetFabricIndex() == 2)
{
saw2 = true;
}
}
NL_TEST_ASSERT(inSuite, numFabricsIterated == 1);
NL_TEST_ASSERT(inSuite, saw1 == true);
NL_TEST_ASSERT(inSuite, saw2 == false);
}
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
}
// Test Suite
/**
* Test Suite that lists all the test functions.
*/
// clang-format off
static const nlTest sTests[] =
{
NL_TEST_DEF("Last Known Good Time Init", TestLastKnownGoodTimeInit),
NL_TEST_DEF("Update Last Known Good Time", TestUpdateLastKnownGoodTime),
NL_TEST_DEF("Set Last Known Good Time", TestSetLastKnownGoodTime),
NL_TEST_DEF("Test basic AddNOC flow", TestBasicAddNocUpdateNocFlow),
NL_TEST_DEF("Test adding multiple fabrics that chain to same root, different fabric ID", TestAddMultipleSameRootDifferentFabricId),
NL_TEST_DEF("Test adding multiple fabrics that chain to different roots, same fabric ID", TestAddMultipleSameFabricIdDifferentRoot),
NL_TEST_DEF("Validate fabrics are loaded from persistence at FabricTable::init", TestPersistence),
NL_TEST_DEF("Test fail-safe handling during AddNOC", TestAddNocFailSafe),
NL_TEST_DEF("Test fail-safe handling during UpdateNoc", TestUpdateNocFailSafe),
NL_TEST_DEF("Test fail-safe handling for root cert", TestAddRootCertFailSafe),
NL_TEST_DEF("Test interlock sequencing errors", TestSequenceErrors),
NL_TEST_DEF("Test fabric label changes", TestFabricLabelChange),
NL_TEST_DEF("Test compressed fabric ID is properly generated", TestCompressedFabricId),
NL_TEST_DEF("Test fabric lookup by <root public key, fabric ID>", TestFabricLookup),
NL_TEST_DEF("Test Fetching CATs", TestFetchCATs),
NL_TEST_DEF("Test AddNOC root collision", TestAddNocRootCollision),
NL_TEST_DEF("Test invalid chaining in AddNOC and UpdateNOC", TestInvalidChaining),
NL_TEST_DEF("Test ephemeral keys allocation", TestEphemeralKeys),
NL_TEST_DEF("Test proper detection of Commit Marker on init", TestCommitMarker),
NL_TEST_DEF("Test colliding fabrics in the fabric table", TestCollidingFabrics),
NL_TEST_SENTINEL()
};
// clang-format on
int TestFabricTable_Setup(void * inContext);
int TestFabricTable_Teardown(void * inContext);
// clang-format off
static nlTestSuite sSuite =
{
"Test-CHIP-FabricTable",
&sTests[0],
TestFabricTable_Setup,
TestFabricTable_Teardown,
};
// clang-format on
/**
* Set up the test suite.
*/
int TestFabricTable_Setup(void * inContext)
{
DeviceLayer::SetConfigurationMgr(&DeviceLayer::ConfigurationManagerImpl::GetDefaultInstance());
return chip::Platform::MemoryInit() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
}
/**
* Tear down the test suite.
*/
int TestFabricTable_Teardown(void * inContext)
{
chip::Platform::MemoryShutdown();
return SUCCESS;
}
} // namespace
/**
* Main
*/
int TestFabricTable()
{
// Run test suit against one context
nlTestRunner(&sSuite, nullptr);
return (nlTestRunnerStats(&sSuite));
}
CHIP_REGISTER_TEST_SUITE(TestFabricTable)