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