blob: 6d9f4781fff576eda0091f09b00aef307b584a14 [file] [log] [blame]
/*
*
* Copyright (c) 2021-2022 Project CHIP Authors
*
* 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.
*/
/**
* @brief Defines a table of fabrics that have provisioned the device.
*/
#include "FabricTable.h"
#include <lib/core/CHIPEncoding.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/SafeInt.h>
#include <lib/support/ScopedBuffer.h>
#include <platform/LockTracker.h>
namespace chip {
using namespace Credentials;
using namespace Crypto;
using CertChainElement = chip::Credentials::OperationalCertificateStore::CertChainElement;
namespace {
static_assert(kMinValidFabricIndex <= CHIP_CONFIG_MAX_FABRICS, "Must support some fabrics.");
static_assert(CHIP_CONFIG_MAX_FABRICS <= kMaxValidFabricIndex, "Max fabric count out of range.");
// Tags for our metadata storage.
constexpr TLV::Tag kVendorIdTag = TLV::ContextTag(0);
constexpr TLV::Tag kFabricLabelTag = TLV::ContextTag(1);
// Tags for our index list storage.
constexpr TLV::Tag kNextAvailableFabricIndexTag = TLV::ContextTag(0);
constexpr TLV::Tag kFabricIndicesTag = TLV::ContextTag(1);
// Tags for commit marker storage
constexpr TLV::Tag kMarkerFabricIndexTag = TLV::ContextTag(0);
constexpr TLV::Tag kMarkerIsAdditionTag = TLV::ContextTag(1);
constexpr size_t CommitMarkerContextTLVMaxSize()
{
// Add 2x uncommitted uint64_t to leave space for backwards/forwards
// versioning for this critical feature that runs at boot.
return TLV::EstimateStructOverhead(sizeof(FabricIndex), sizeof(bool), sizeof(uint64_t), sizeof(uint64_t));
}
constexpr size_t IndexInfoTLVMaxSize()
{
// We have a single next-available index and an array of anonymous-tagged
// fabric indices.
//
// The max size of the list is (1 byte control + bytes for actual value)
// times max number of list items, plus one byte for the list terminator.
return TLV::EstimateStructOverhead(sizeof(FabricIndex), CHIP_CONFIG_MAX_FABRICS * (1 + sizeof(FabricIndex)) + 1);
}
} // anonymous namespace
CHIP_ERROR FabricInfo::Init(const FabricInfo::InitParams & initParams)
{
ReturnErrorOnFailure(initParams.AreValid());
Reset();
mNodeId = initParams.nodeId;
mFabricId = initParams.fabricId;
mFabricIndex = initParams.fabricIndex;
mCompressedFabricId = initParams.compressedFabricId;
mRootPublicKey = initParams.rootPublicKey;
mVendorId = static_cast<VendorId>(initParams.vendorId);
// Deal with externally injected keys
if (initParams.operationalKeypair != nullptr)
{
if (initParams.hasExternallyOwnedKeypair)
{
ReturnErrorOnFailure(SetExternallyOwnedOperationalKeypair(initParams.operationalKeypair));
}
else
{
ReturnErrorOnFailure(SetOperationalKeypair(initParams.operationalKeypair));
}
}
return CHIP_NO_ERROR;
}
void FabricInfo::operator=(FabricInfo && other)
{
Reset();
mNodeId = other.mNodeId;
mFabricId = other.mFabricId;
mFabricIndex = other.mFabricIndex;
mCompressedFabricId = other.mCompressedFabricId;
mRootPublicKey = other.mRootPublicKey;
mVendorId = other.mVendorId;
SetFabricLabel(other.GetFabricLabel());
// Transfer ownership of operational keypair (if it was nullptr, it stays that way).
mOperationalKey = other.mOperationalKey;
mHasExternallyOwnedOperationalKey = other.mHasExternallyOwnedOperationalKey;
other.mOperationalKey = nullptr;
other.mHasExternallyOwnedOperationalKey = false;
other.Reset();
}
CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage) const
{
{
uint8_t buf[MetadataTLVMaxSize()];
TLV::TLVWriter writer;
writer.Init(buf);
TLV::TLVType outerType;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
ReturnErrorOnFailure(writer.Put(kVendorIdTag, mVendorId));
ReturnErrorOnFailure(writer.PutString(kFabricLabelTag, CharSpan::fromCharString(mFabricLabel)));
ReturnErrorOnFailure(writer.EndContainer(outerType));
const auto metadataLength = writer.GetLengthWritten();
VerifyOrReturnError(CanCastTo<uint16_t>(metadataLength), CHIP_ERROR_BUFFER_TOO_SMALL);
ReturnErrorOnFailure(storage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf,
static_cast<uint16_t>(metadataLength)));
}
// NOTE: Operational Key is never saved to storage here. See OperationalKeystore interface for how it is accessed
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage, FabricIndex newFabricIndex, const ByteSpan & rcac,
const ByteSpan & noc)
{
mFabricIndex = newFabricIndex;
// Regenerate operational metadata from NOC/RCAC
{
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &mNodeId, &mFabricId));
P256PublicKeySpan rootPubKeySpan;
ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(rcac, rootPubKeySpan));
mRootPublicKey = rootPubKeySpan;
uint8_t compressedFabricIdBuf[sizeof(uint64_t)];
MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf);
ReturnErrorOnFailure(GenerateCompressedFabricId(mRootPublicKey, mFabricId, compressedFabricIdSpan));
// Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId()
// returns a binary buffer and is agnostic of usage of the output as an integer type.
mCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf);
}
// Load other storable metadata (label, vendorId, etc)
{
uint8_t buf[MetadataTLVMaxSize()];
uint16_t size = sizeof(buf);
ReturnErrorOnFailure(
storage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricMetadata(mFabricIndex).KeyName(), buf, size));
TLV::ContiguousBufferTLVReader reader;
reader.Init(buf, size);
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType containerType;
ReturnErrorOnFailure(reader.EnterContainer(containerType));
ReturnErrorOnFailure(reader.Next(kVendorIdTag));
ReturnErrorOnFailure(reader.Get(mVendorId));
ReturnErrorOnFailure(reader.Next(kFabricLabelTag));
CharSpan label;
ReturnErrorOnFailure(reader.Get(label));
VerifyOrReturnError(label.size() <= kFabricLabelMaxLengthInBytes, CHIP_ERROR_BUFFER_TOO_SMALL);
Platform::CopyString(mFabricLabel, label);
ReturnErrorOnFailure(reader.ExitContainer(containerType));
ReturnErrorOnFailure(reader.VerifyEndOfContainer());
}
// NOTE: Operational Key is never loaded here. See OperationalKeystore interface for how it is accessed
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::SetFabricLabel(const CharSpan & fabricLabel)
{
Platform::CopyString(mFabricLabel, fabricLabel);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::DeleteMetadataFromStorage(FabricIndex fabricIndex)
{
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR deleteErr = mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FabricMetadata(fabricIndex).KeyName());
if (deleteErr != CHIP_NO_ERROR)
{
if (deleteErr == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
ChipLogError(FabricProvisioning, "Warning: metadata not found during delete of fabric 0x%x",
static_cast<unsigned>(fabricIndex));
}
else
{
ChipLogError(FabricProvisioning, "Error deleting metadata for fabric fabric 0x%x: %" CHIP_ERROR_FORMAT,
static_cast<unsigned>(fabricIndex), deleteErr.Format());
}
}
return deleteErr;
}
CHIP_ERROR FabricInfo::SetOperationalKeypair(const P256Keypair * keyPair)
{
VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
P256SerializedKeypair serialized;
ReturnErrorOnFailure(keyPair->Serialize(serialized));
if (mHasExternallyOwnedOperationalKey)
{
// Drop it, so we will allocate an internally owned one.
mOperationalKey = nullptr;
mHasExternallyOwnedOperationalKey = false;
}
if (mOperationalKey == nullptr)
{
mOperationalKey = chip::Platform::New<P256Keypair>();
}
VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY);
return mOperationalKey->Deserialize(serialized);
}
CHIP_ERROR FabricInfo::SetExternallyOwnedOperationalKeypair(P256Keypair * keyPair)
{
VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
if (!mHasExternallyOwnedOperationalKey && mOperationalKey != nullptr)
{
chip::Platform::Delete(mOperationalKey);
mOperationalKey = nullptr;
}
mHasExternallyOwnedOperationalKey = true;
mOperationalKey = keyPair;
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::ValidateIncomingNOCChain(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
FabricId existingFabricId, Credentials::CertificateValidityPolicy * policy,
CompressedFabricId & outCompressedFabricId, FabricId & outFabricId,
NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
Crypto::P256PublicKey & outRootPubkey)
{
Credentials::ValidationContext validContext;
// Note that we do NOT set a time in the validation context. This will
// cause the certificate chain NotBefore / NotAfter time validation logic
// to report CertificateValidityResult::kTimeUnknown.
//
// The default CHIPCert policy passes NotBefore / NotAfter validation for
// this case where time is unknown. If an override policy is passed, it
// will be up to the passed policy to decide how to handle this.
//
// In the FabricTable::AddNewFabric and FabricTable::UpdateFabric calls,
// the passed policy always passes for all questions of time validity. The
// rationale is that installed certificates should be valid at the time of
// installation by definition. If they are not and the commissionee and
// commissioner disagree enough on current time, CASE will fail and our
// fail-safe timer will expire.
//
// This then is ultimately how we validate that NotBefore / NotAfter in
// newly installed certificates is workable.
validContext.Reset();
validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature);
validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth);
validContext.mValidityPolicy = policy;
ChipLogProgress(FabricProvisioning, "Validating NOC chain");
CHIP_ERROR err = FabricTable::VerifyCredentials(noc, icac, rcac, validContext, outCompressedFabricId, outFabricId, outNodeId,
outNocPubkey, &outRootPubkey);
if (err != CHIP_NO_ERROR && err != CHIP_ERROR_WRONG_NODE_ID)
{
err = CHIP_ERROR_UNSUPPORTED_CERT_FORMAT;
}
if (err != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed NOC chain validation: %" CHIP_ERROR_FORMAT, err.Format());
}
ReturnErrorOnFailure(err);
// Validate fabric ID match for cases like UpdateNOC.
if (existingFabricId != kUndefinedFabricId)
{
VerifyOrReturnError(existingFabricId == outFabricId, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT);
}
ChipLogProgress(FabricProvisioning, "NOC chain validation successful");
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::SignWithOpKeypair(ByteSpan message, P256ECDSASignature & outSignature) const
{
VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
return mOperationalKey->ECDSA_sign_msg(message.data(), message.size(), outSignature);
}
CHIP_ERROR FabricInfo::FetchRootPubkey(Crypto::P256PublicKey & outPublicKey) const
{
VerifyOrReturnError(IsInitialized(), CHIP_ERROR_KEY_NOT_FOUND);
outPublicKey = mRootPublicKey;
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::VerifyCredentials(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
ValidationContext & context, CompressedFabricId & outCompressedFabricId,
FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
Crypto::P256PublicKey * outRootPublicKey) const
{
assertChipStackLockedByCurrentThread();
uint8_t rootCertBuf[kMaxCHIPCertLength];
MutableByteSpan rootCertSpan{ rootCertBuf };
ReturnErrorOnFailure(FetchRootCert(fabricIndex, rootCertSpan));
return VerifyCredentials(noc, icac, rootCertSpan, context, outCompressedFabricId, outFabricId, outNodeId, outNocPubkey,
outRootPublicKey);
}
CHIP_ERROR FabricTable::VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac,
ValidationContext & context, CompressedFabricId & outCompressedFabricId,
FabricId & outFabricId, NodeId & outNodeId, Crypto::P256PublicKey & outNocPubkey,
Crypto::P256PublicKey * outRootPublicKey)
{
// TODO - Optimize credentials verification logic
// The certificate chain construction and verification is a compute and memory intensive operation.
// It can be optimized by not loading certificate (i.e. rcac) that's local and implicitly trusted.
// The FindValidCert() algorithm will need updates to achieve this refactor.
constexpr uint8_t kMaxNumCertsInOpCreds = 3;
ChipCertificateSet certificates;
ReturnErrorOnFailure(certificates.Init(kMaxNumCertsInOpCreds));
ReturnErrorOnFailure(certificates.LoadCert(rcac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kIsTrustAnchor)));
if (!icac.empty())
{
ReturnErrorOnFailure(certificates.LoadCert(icac, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)));
}
ReturnErrorOnFailure(certificates.LoadCert(noc, BitFlags<CertDecodeFlags>(CertDecodeFlags::kGenerateTBSHash)));
const ChipDN & nocSubjectDN = certificates.GetLastCert()[0].mSubjectDN;
const CertificateKeyId & nocSubjectKeyId = certificates.GetLastCert()[0].mSubjectKeyId;
const ChipCertificateData * resultCert = nullptr;
// FindValidCert() checks the certificate set constructed by loading noc, icac and rcac.
// It confirms that the certs link correctly (noc -> icac -> rcac), and have been correctly signed.
ReturnErrorOnFailure(certificates.FindValidCert(nocSubjectDN, nocSubjectKeyId, context, &resultCert));
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(certificates.GetLastCert()[0], &outNodeId, &outFabricId));
CHIP_ERROR err;
FabricId icacFabricId = kUndefinedFabricId;
if (!icac.empty())
{
err = ExtractFabricIdFromCert(certificates.GetCertSet()[1], &icacFabricId);
if (err == CHIP_NO_ERROR)
{
ReturnErrorCodeIf(icacFabricId != outFabricId, CHIP_ERROR_FABRIC_MISMATCH_ON_ICA);
}
// FabricId is optional field in ICAC and "not found" code is not treated as error.
else if (err != CHIP_ERROR_NOT_FOUND)
{
return err;
}
}
FabricId rcacFabricId = kUndefinedFabricId;
err = ExtractFabricIdFromCert(certificates.GetCertSet()[0], &rcacFabricId);
if (err == CHIP_NO_ERROR)
{
ReturnErrorCodeIf(rcacFabricId != outFabricId, CHIP_ERROR_WRONG_CERT_DN);
}
// FabricId is optional field in RCAC and "not found" code is not treated as error.
else if (err != CHIP_ERROR_NOT_FOUND)
{
return err;
}
// Extract compressed fabric ID and root public key
{
uint8_t compressedFabricIdBuf[sizeof(uint64_t)];
MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf);
P256PublicKey rootPubkey(certificates.GetCertSet()[0].mPublicKey);
ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubkey, outFabricId, compressedFabricIdSpan));
// Decode compressed fabric ID accounting for endianness, as GenerateCompressedFabricId()
// returns a binary buffer and is agnostic of usage of the output as an integer type.
outCompressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf);
if (outRootPublicKey != nullptr)
{
*outRootPublicKey = rootPubkey;
}
}
outNocPubkey = certificates.GetLastCert()->mPublicKey;
return CHIP_NO_ERROR;
}
const FabricInfo * FabricTable::FindFabric(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId) const
{
return FindFabricCommon(rootPubKey, fabricId);
}
const FabricInfo * FabricTable::FindIdentity(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const
{
return FindFabricCommon(rootPubKey, fabricId, nodeId);
}
const FabricInfo * FabricTable::FindFabricCommon(const Crypto::P256PublicKey & rootPubKey, FabricId fabricId, NodeId nodeId) const
{
P256PublicKey candidatePubKey;
// Try to match pending fabric first if available
if (HasPendingFabricUpdate())
{
bool pubKeyAvailable = (mPendingFabric.FetchRootPubkey(candidatePubKey) == CHIP_NO_ERROR);
auto matchingNodeId = (nodeId == kUndefinedNodeId) ? mPendingFabric.GetNodeId() : nodeId;
if (pubKeyAvailable && rootPubKey.Matches(candidatePubKey) && fabricId == mPendingFabric.GetFabricId() &&
matchingNodeId == mPendingFabric.GetNodeId())
{
return &mPendingFabric;
}
}
for (auto & fabric : mStates)
{
auto matchingNodeId = (nodeId == kUndefinedNodeId) ? fabric.GetNodeId() : nodeId;
if (!fabric.IsInitialized())
{
continue;
}
if (fabric.FetchRootPubkey(candidatePubKey) != CHIP_NO_ERROR)
{
continue;
}
if (rootPubKey.Matches(candidatePubKey) && fabricId == fabric.GetFabricId() && matchingNodeId == fabric.GetNodeId())
{
return &fabric;
}
}
return nullptr;
}
FabricInfo * FabricTable::GetMutableFabricByIndex(FabricIndex fabricIndex)
{
// Try to match pending fabric first if available
if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex))
{
return &mPendingFabric;
}
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
if (fabric.GetFabricIndex() == fabricIndex)
{
return &fabric;
}
}
return nullptr;
}
const FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex) const
{
// Try to match pending fabric first if available
if (HasPendingFabricUpdate() && (mPendingFabric.GetFabricIndex() == fabricIndex))
{
return &mPendingFabric;
}
for (const auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
if (fabric.GetFabricIndex() == fabricIndex)
{
return &fabric;
}
}
return nullptr;
}
const FabricInfo * FabricTable::FindFabricWithCompressedId(CompressedFabricId compressedFabricId) const
{
// Try to match pending fabric first if available
if (HasPendingFabricUpdate() && (mPendingFabric.GetCompressedFabricId() == compressedFabricId))
{
return &mPendingFabric;
}
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
if (compressedFabricId == fabric.GetPeerId().GetCompressedFabricId())
{
return &fabric;
}
}
return nullptr;
}
CHIP_ERROR FabricTable::FetchRootCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kRcac, outCert);
}
CHIP_ERROR FabricTable::FetchPendingNonFabricAssociatedRootCert(MutableByteSpan & outCert) const
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending))
{
return CHIP_ERROR_NOT_FOUND;
}
if (mStateFlags.Has(StateFlags::kIsAddPending))
{
// The root certificate is already associated with a pending fabric, so
// does not exist for purposes of this API.
return CHIP_ERROR_NOT_FOUND;
}
return FetchRootCert(mFabricIndexWithPendingState, outCert);
}
CHIP_ERROR FabricTable::FetchICACert(FabricIndex fabricIndex, MutableByteSpan & outCert) const
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kIcac, outCert);
if (err == CHIP_ERROR_NOT_FOUND)
{
if (mOpCertStore->HasCertificateForFabric(fabricIndex, CertChainElement::kNoc))
{
// Didn't find ICAC, but have NOC: return empty for ICAC since not present in chain, but chain exists
outCert.reduce_size(0);
return CHIP_NO_ERROR;
}
}
// For all other cases, delegate to operational cert store for results
return err;
}
CHIP_ERROR FabricTable::FetchNOCCert(FabricIndex fabricIndex, MutableByteSpan & outCert) const
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
return mOpCertStore->GetCertificate(fabricIndex, CertChainElement::kNoc, outCert);
}
CHIP_ERROR FabricTable::FetchRootPubkey(FabricIndex fabricIndex, Crypto::P256PublicKey & outPublicKey) const
{
const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex);
ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
return fabricInfo->FetchRootPubkey(outPublicKey);
}
CHIP_ERROR FabricTable::FetchCATs(const FabricIndex fabricIndex, CATValues & cats) const
{
uint8_t nocBuf[Credentials::kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan));
ReturnErrorOnFailure(ExtractCATsFromOpCert(nocSpan, cats));
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::StoreFabricMetadata(const FabricInfo * fabricInfo) const
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrDie(fabricInfo != nullptr);
FabricIndex fabricIndex = fabricInfo->GetFabricIndex();
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INTERNAL);
// TODO: Refactor not to internally rely directly on storage
ReturnErrorOnFailure(fabricInfo->CommitToStorage(mStorage));
ChipLogProgress(FabricProvisioning, "Metadata for Fabric 0x%x persisted to storage.", static_cast<unsigned>(fabricIndex));
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric, FabricIndex newFabricIndex)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(!fabric->IsInitialized(), CHIP_ERROR_INCORRECT_STATE);
uint8_t nocBuf[kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
uint8_t rcacBuf[kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
CHIP_ERROR err = FetchNOCCert(newFabricIndex, nocSpan);
if (err == CHIP_NO_ERROR)
{
err = FetchRootCert(newFabricIndex, rcacSpan);
}
// TODO(#19935): Sweep-away fabrics without RCAC/NOC by deleting everything and marking fabric gone.
if (err == CHIP_NO_ERROR)
{
err = fabric->LoadFromStorage(mStorage, newFabricIndex, rcacSpan, nocSpan);
}
if (err != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to load Fabric (0x%x): %" CHIP_ERROR_FORMAT, static_cast<unsigned>(newFabricIndex),
err.Format());
fabric->Reset();
return err;
}
ChipLogProgress(FabricProvisioning,
"Fabric index 0x%x was retrieved from storage. Compressed FabricId 0x" ChipLogFormatX64
", FabricId 0x" ChipLogFormatX64 ", NodeId 0x" ChipLogFormatX64 ", VendorId 0x%04X",
static_cast<unsigned>(fabric->GetFabricIndex()), ChipLogValueX64(fabric->GetCompressedFabricId()),
ChipLogValueX64(fabric->GetFabricId()), ChipLogValueX64(fabric->GetNodeId()),
to_underlying(fabric->GetVendorId()));
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::AddNewFabricForTest(const ByteSpan & rootCert, const ByteSpan & icacCert, const ByteSpan & nocCert,
const ByteSpan & opKeySpan, FabricIndex * outFabricIndex)
{
VerifyOrReturnError(outFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
CHIP_ERROR err = CHIP_ERROR_INTERNAL;
Crypto::P256Keypair injectedOpKey;
Crypto::P256SerializedKeypair injectedOpKeysSerialized;
Crypto::P256Keypair * opKey = nullptr;
if (!opKeySpan.empty())
{
VerifyOrReturnError(opKeySpan.size() == injectedOpKeysSerialized.Capacity(), CHIP_ERROR_INVALID_ARGUMENT);
memcpy(injectedOpKeysSerialized.Bytes(), opKeySpan.data(), opKeySpan.size());
SuccessOrExit(err = injectedOpKeysSerialized.SetLength(opKeySpan.size()));
SuccessOrExit(err = injectedOpKey.Deserialize(injectedOpKeysSerialized));
opKey = &injectedOpKey;
}
SuccessOrExit(err = AddNewPendingTrustedRootCert(rootCert));
SuccessOrExit(err = AddNewPendingFabricWithProvidedOpKey(nocCert, icacCert, VendorId::TestVendor1, opKey,
/*isExistingOpKeyExternallyOwned =*/false, outFabricIndex));
SuccessOrExit(err = CommitPendingFabricData());
exit:
if (err != CHIP_NO_ERROR)
{
RevertPendingFabricData();
}
return err;
}
/*
* A validation policy we can pass into VerifyCredentials to extract the
* latest NotBefore time in the certificate chain without having to load the
* certificates into memory again, and one which will pass validation for all
* questions of NotBefore / NotAfter validity.
*
* The rationale is that installed certificates should be valid at the time of
* installation by definition. If they are not and the commissionee and
* commissioner disagree enough on current time, CASE will fail and our
* fail-safe timer will expire.
*
* This then is ultimately how we validate that NotBefore / NotAfter in
* newly installed certificates is workable.
*/
class NotBeforeCollector : public Credentials::CertificateValidityPolicy
{
public:
NotBeforeCollector() : mLatestNotBefore(0) {}
CHIP_ERROR ApplyCertificateValidityPolicy(const ChipCertificateData * cert, uint8_t depth,
CertificateValidityResult result) override
{
if (cert->mNotBeforeTime > mLatestNotBefore.count())
{
mLatestNotBefore = System::Clock::Seconds32(cert->mNotBeforeTime);
}
return CHIP_NO_ERROR;
}
System::Clock::Seconds32 mLatestNotBefore;
};
CHIP_ERROR FabricTable::NotifyFabricUpdated(FabricIndex fabricIndex)
{
FabricTable::Delegate * delegate = mDelegateListRoot;
while (delegate)
{
// It is possible that delegate will remove itself from the list in the callback
// so we grab the next delegate in the list now.
FabricTable::Delegate * nextDelegate = delegate->next;
delegate->OnFabricUpdated(*this, fabricIndex);
delegate = nextDelegate;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::NotifyFabricCommitted(FabricIndex fabricIndex)
{
FabricTable::Delegate * delegate = mDelegateListRoot;
while (delegate)
{
// It is possible that delegate will remove itself from the list in the callback
// so we grab the next delegate in the list now.
FabricTable::Delegate * nextDelegate = delegate->next;
delegate->OnFabricCommitted(*this, fabricIndex);
delegate = nextDelegate;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR
FabricTable::AddOrUpdateInner(FabricIndex fabricIndex, bool isAddition, Crypto::P256Keypair * existingOpKey,
bool isExistingOpKeyExternallyOwned, uint16_t vendorId)
{
// All parameters pre-validated before we get here
FabricInfo::InitParams newFabricInfo;
FabricInfo * fabricEntry = nullptr;
FabricId fabricIdToValidate = kUndefinedFabricId;
CharSpan fabricLabel("");
if (isAddition)
{
// Initialization for Adding a fabric
// Find an available slot.
for (auto & fabric : mStates)
{
if (fabric.IsInitialized())
{
continue;
}
fabricEntry = &fabric;
break;
}
VerifyOrReturnError(fabricEntry != nullptr, CHIP_ERROR_NO_MEMORY);
newFabricInfo.vendorId = static_cast<VendorId>(vendorId);
newFabricInfo.fabricIndex = fabricIndex;
}
else
{
// Initialization for Updating fabric: setting up a shadow fabricInfo
const FabricInfo * existingFabric = FindFabricWithIndex(fabricIndex);
VerifyOrReturnError(existingFabric != nullptr, CHIP_ERROR_INTERNAL);
mPendingFabric.Reset();
fabricEntry = &mPendingFabric;
newFabricInfo.vendorId = existingFabric->GetVendorId();
newFabricInfo.fabricIndex = fabricIndex;
fabricIdToValidate = existingFabric->GetFabricId();
fabricLabel = existingFabric->GetFabricLabel();
}
// Make sure to not modify any of our state until ValidateIncomingNOCChain passes.
NotBeforeCollector notBeforeCollector;
P256PublicKey nocPubKey;
// Validate the cert chain prior to adding
{
Platform::ScopedMemoryBuffer<uint8_t> nocBuf;
Platform::ScopedMemoryBuffer<uint8_t> icacBuf;
Platform::ScopedMemoryBuffer<uint8_t> rcacBuf;
ReturnErrorCodeIf(!nocBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
ReturnErrorCodeIf(!icacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
ReturnErrorCodeIf(!rcacBuf.Alloc(kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY);
MutableByteSpan nocSpan{ nocBuf.Get(), kMaxCHIPCertLength };
MutableByteSpan icacSpan{ icacBuf.Get(), kMaxCHIPCertLength };
MutableByteSpan rcacSpan{ rcacBuf.Get(), kMaxCHIPCertLength };
ReturnErrorOnFailure(FetchNOCCert(fabricIndex, nocSpan));
ReturnErrorOnFailure(FetchICACert(fabricIndex, icacSpan));
ReturnErrorOnFailure(FetchRootCert(fabricIndex, rcacSpan));
ReturnErrorOnFailure(ValidateIncomingNOCChain(nocSpan, icacSpan, rcacSpan, fabricIdToValidate, &notBeforeCollector,
newFabricInfo.compressedFabricId, newFabricInfo.fabricId,
newFabricInfo.nodeId, nocPubKey, newFabricInfo.rootPublicKey));
}
if (existingOpKey != nullptr)
{
// Verify that public key in NOC matches public key of the provided keypair.
// When operational key is not injected (e.g. when mOperationalKeystore != nullptr)
// the check is done by the keystore in `ActivateOpKeypairForFabric`.
VerifyOrReturnError(existingOpKey->Pubkey().Matches(nocPubKey), CHIP_ERROR_INVALID_PUBLIC_KEY);
newFabricInfo.operationalKeypair = existingOpKey;
newFabricInfo.hasExternallyOwnedKeypair = isExistingOpKeyExternallyOwned;
}
else if (mOperationalKeystore != nullptr)
{
// If a keystore exists, we activate the operational key now, which also validates if it was previously installed
if (mOperationalKeystore->HasPendingOpKeypair())
{
ReturnErrorOnFailure(mOperationalKeystore->ActivateOpKeypairForFabric(fabricIndex, nocPubKey));
}
else
{
VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex), CHIP_ERROR_KEY_NOT_FOUND);
}
}
else
{
return CHIP_ERROR_INCORRECT_STATE;
}
// Update local copy of fabric data. For add it's a new entry, for update, it's `mPendingFabric` shadow entry.
ReturnErrorOnFailure(fabricEntry->Init(newFabricInfo));
// Set the label, matching add/update semantics of empty/existing.
fabricEntry->SetFabricLabel(fabricLabel);
if (isAddition)
{
ChipLogProgress(FabricProvisioning, "Added new fabric at index: 0x%x",
static_cast<unsigned>(fabricEntry->GetFabricIndex()));
ChipLogProgress(FabricProvisioning, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64,
ChipLogValueX64(fabricEntry->GetCompressedFabricId()), ChipLogValueX64(fabricEntry->GetNodeId()));
}
else
{
ChipLogProgress(FabricProvisioning, "Updated fabric at index: 0x%x, Node ID: 0x" ChipLogFormatX64,
static_cast<unsigned>(fabricEntry->GetFabricIndex()), ChipLogValueX64(fabricEntry->GetNodeId()));
}
// Failure to update pending Last Known Good Time is non-fatal. If Last
// Known Good Time is incorrect and this causes the commissioner's
// certificates to appear invalid, the certificate validity policy will
// determine what to do. And if the validity policy considers this fatal
// this will prevent CASE and cause the pending fabric and Last Known Good
// Time to be reverted.
CHIP_ERROR lkgtErr = mLastKnownGoodTime.UpdatePendingLastKnownGoodChipEpochTime(notBeforeCollector.mLatestNotBefore);
if (lkgtErr != CHIP_NO_ERROR)
{
// Log but this is not sticky...
ChipLogError(FabricProvisioning, "Failed to update pending Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format());
}
// Must be the last thing before we return, as this is undone later on error handling within Delete.
if (isAddition)
{
mFabricCount++;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::Delete(FabricIndex fabricIndex)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT);
{
FabricTable::Delegate * delegate = mDelegateListRoot;
while (delegate)
{
// It is possible that delegate will remove itself from the list in FabricWillBeRemoved,
// so we grab the next delegate in the list now.
FabricTable::Delegate * nextDelegate = delegate->next;
delegate->FabricWillBeRemoved(*this, fabricIndex);
delegate = nextDelegate;
}
}
FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex);
if (fabricInfo == &mPendingFabric)
{
// Asked to Delete while pending an update: reset the pending state and
// get back to the underlying fabric data for existing fabric.
RevertPendingFabricData();
fabricInfo = GetMutableFabricByIndex(fabricIndex);
}
bool fabricIsInitialized = fabricInfo != nullptr && fabricInfo->IsInitialized();
CHIP_ERROR metadataErr = DeleteMetadataFromStorage(fabricIndex); // Delete from storage regardless
CHIP_ERROR opKeyErr = CHIP_NO_ERROR;
if (mOperationalKeystore != nullptr)
{
opKeyErr = mOperationalKeystore->RemoveOpKeypairForFabric(fabricIndex);
// Not having found data is not an error, we may just have gotten here
// on a fail-safe expiry after `RevertPendingFabricData`.
if (opKeyErr == CHIP_ERROR_INVALID_FABRIC_INDEX)
{
opKeyErr = CHIP_NO_ERROR;
}
}
CHIP_ERROR opCertsErr = CHIP_NO_ERROR;
if (mOpCertStore != nullptr)
{
opCertsErr = mOpCertStore->RemoveOpCertsForFabric(fabricIndex);
// Not having found data is not an error, we may just have gotten here
// on a fail-safe expiry after `RevertPendingFabricData`.
if (opCertsErr == CHIP_ERROR_INVALID_FABRIC_INDEX)
{
opCertsErr = CHIP_NO_ERROR;
}
}
if (!fabricIsInitialized)
{
// Make sure to return the error our API promises, not whatever storage
// chose to return.
return CHIP_ERROR_NOT_FOUND;
}
// Since fabricIsInitialized was true, fabric is not null.
fabricInfo->Reset();
if (!mNextAvailableFabricIndex.HasValue())
{
// We must have been in a situation where CHIP_CONFIG_MAX_FABRICS is 254
// and our fabric table was full, so there was no valid next index. We
// have a single available index now, though; use it as
// mNextAvailableFabricIndex.
mNextAvailableFabricIndex.SetValue(fabricIndex);
}
// If StoreFabricIndexInfo fails here, that's probably OK. When we try to
// read things from storage later we will realize there is nothing for this
// index.
StoreFabricIndexInfo();
// If we ever start moving the FabricInfo entries around in the array on
// delete, we should update DeleteAllFabrics to handle that.
if (mFabricCount == 0)
{
ChipLogError(FabricProvisioning, "Trying to delete a fabric, but the current fabric count is already 0");
}
else
{
mFabricCount--;
ChipLogProgress(FabricProvisioning, "Fabric (0x%x) deleted.", static_cast<unsigned>(fabricIndex));
}
if (mDelegateListRoot != nullptr)
{
FabricTable::Delegate * delegate = mDelegateListRoot;
while (delegate)
{
// It is possible that delegate will remove itself from the list in OnFabricRemoved,
// so we grab the next delegate in the list now.
FabricTable::Delegate * nextDelegate = delegate->next;
delegate->OnFabricRemoved(*this, fabricIndex);
delegate = nextDelegate;
}
}
// Only return error after trying really hard to remove everything we could
ReturnErrorOnFailure(metadataErr);
ReturnErrorOnFailure(opKeyErr);
ReturnErrorOnFailure(opCertsErr);
return CHIP_NO_ERROR;
}
void FabricTable::DeleteAllFabrics()
{
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
RevertPendingFabricData();
for (auto & fabric : *this)
{
Delete(fabric.GetFabricIndex());
}
}
CHIP_ERROR FabricTable::Init(const FabricTable::InitParams & initParams)
{
VerifyOrReturnError(initParams.storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(initParams.opCertStore != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mStorage = initParams.storage;
mOperationalKeystore = initParams.operationalKeystore;
mOpCertStore = initParams.opCertStore;
ChipLogDetail(FabricProvisioning, "Initializing FabricTable from persistent storage");
// Load the current fabrics from the storage.
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
mFabricCount = 0;
for (auto & fabric : mStates)
{
fabric.Reset();
}
mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex);
// Init failure of Last Known Good Time is non-fatal. If Last Known Good
// Time is unknown during incoming certificate validation for CASE and
// current time is also unknown, the certificate validity policy will see
// this condition and can act appropriately.
mLastKnownGoodTime.Init(mStorage);
uint8_t buf[IndexInfoTLVMaxSize()];
uint16_t size = sizeof(buf);
CHIP_ERROR err = mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf, size);
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// No fabrics yet. Nothing to be done here.
}
else
{
ReturnErrorOnFailure(err);
TLV::ContiguousBufferTLVReader reader;
reader.Init(buf, size);
// TODO: A safer way would be to just clean-up the entire fabric table on this situation...
err = ReadFabricInfo(reader);
if (err != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Error loading fabric table: %" CHIP_ERROR_FORMAT ", we are in a bad state!",
err.Format());
}
ReturnErrorOnFailure(err);
}
CommitMarker commitMarker;
err = GetCommitMarker(commitMarker);
if (err == CHIP_NO_ERROR)
{
// Found a commit marker! We need to possibly delete a loaded fabric
ChipLogError(FabricProvisioning, "Found a FabricTable aborted commit for index 0x%x (isAddition: %d), removing!",
static_cast<unsigned>(commitMarker.fabricIndex), static_cast<int>(commitMarker.isAddition));
mDeletedFabricIndexFromInit = commitMarker.fabricIndex;
// Can't do better on error. We just have to hope for the best.
(void) Delete(commitMarker.fabricIndex);
}
else if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// Got an error, but somehow value is not missing altogether: inconsistent state but touch nothing.
ChipLogError(FabricProvisioning, "Error loading Table commit marker: %" CHIP_ERROR_FORMAT ", hope for the best!",
err.Format());
}
return CHIP_NO_ERROR;
}
void FabricTable::Forget(FabricIndex fabricIndex)
{
ChipLogProgress(FabricProvisioning, "Forgetting fabric 0x%x", static_cast<unsigned>(fabricIndex));
auto * fabricInfo = GetMutableFabricByIndex(fabricIndex);
VerifyOrReturn(fabricInfo != nullptr);
RevertPendingFabricData();
fabricInfo->Reset();
}
void FabricTable::Shutdown()
{
VerifyOrReturn(mStorage != nullptr);
ChipLogProgress(FabricProvisioning, "Shutting down FabricTable");
// Remove all links to every delegate
FabricTable::Delegate * delegate = mDelegateListRoot;
while (delegate)
{
FabricTable::Delegate * temp = delegate->next;
delegate->next = nullptr;
delegate = temp;
}
RevertPendingFabricData();
for (FabricInfo & fabricInfo : mStates)
{
// Clear-out any FabricInfo-owned operational keys and make sure any further
// direct lookups fail.
fabricInfo.Reset();
}
mStorage = nullptr;
}
FabricIndex FabricTable::GetDeletedFabricFromCommitMarker()
{
FabricIndex retVal = mDeletedFabricIndexFromInit;
// Reset for next read
mDeletedFabricIndexFromInit = kUndefinedFabricIndex;
return retVal;
}
CHIP_ERROR FabricTable::AddFabricDelegate(FabricTable::Delegate * delegate)
{
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
for (FabricTable::Delegate * iter = mDelegateListRoot; iter != nullptr; iter = iter->next)
{
if (iter == delegate)
{
return CHIP_NO_ERROR;
}
}
delegate->next = mDelegateListRoot;
mDelegateListRoot = delegate;
return CHIP_NO_ERROR;
}
void FabricTable::RemoveFabricDelegate(FabricTable::Delegate * delegateToRemove)
{
VerifyOrReturn(delegateToRemove != nullptr);
if (delegateToRemove == mDelegateListRoot)
{
// Removing head of the list, keep link to next, may
// be nullptr.
mDelegateListRoot = mDelegateListRoot->next;
}
else
{
// Removing some other item: check if next, and
// remove the link, keeping its neighbour.
FabricTable::Delegate * currentNode = mDelegateListRoot;
while (currentNode)
{
if (currentNode->next == delegateToRemove)
{
FabricTable::Delegate * temp = delegateToRemove->next;
currentNode->next = temp;
delegateToRemove->next = nullptr;
return;
}
currentNode = currentNode->next;
}
}
}
CHIP_ERROR FabricTable::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime)
{
CHIP_ERROR err = CHIP_NO_ERROR;
// Find our latest NotBefore time for any installed certificate.
System::Clock::Seconds32 latestNotBefore = System::Clock::Seconds32(0);
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
{
uint8_t rcacBuf[kMaxCHIPCertLength];
MutableByteSpan rcacSpan{ rcacBuf };
SuccessOrExit(err = FetchRootCert(fabric.GetFabricIndex(), rcacSpan));
chip::System::Clock::Seconds32 rcacNotBefore;
SuccessOrExit(err = Credentials::ExtractNotBeforeFromChipCert(rcacSpan, rcacNotBefore));
latestNotBefore = rcacNotBefore > latestNotBefore ? rcacNotBefore : latestNotBefore;
}
{
uint8_t icacBuf[kMaxCHIPCertLength];
MutableByteSpan icacSpan{ icacBuf };
SuccessOrExit(err = FetchICACert(fabric.GetFabricIndex(), icacSpan));
if (!icacSpan.empty())
{
chip::System::Clock::Seconds32 icacNotBefore;
ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(icacSpan, icacNotBefore));
latestNotBefore = icacNotBefore > latestNotBefore ? icacNotBefore : latestNotBefore;
}
}
{
uint8_t nocBuf[kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
SuccessOrExit(err = FetchNOCCert(fabric.GetFabricIndex(), nocSpan));
chip::System::Clock::Seconds32 nocNotBefore;
ReturnErrorOnFailure(Credentials::ExtractNotBeforeFromChipCert(nocSpan, nocNotBefore));
latestNotBefore = nocNotBefore > latestNotBefore ? nocNotBefore : latestNotBefore;
}
}
// Pass this to the LastKnownGoodTime object so it can make determination
// of the legality of our new proposed time.
SuccessOrExit(err = mLastKnownGoodTime.SetLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime, latestNotBefore));
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to update Known Good Time: %" CHIP_ERROR_FORMAT, err.Format());
}
return err;
}
namespace {
// Increment a fabric index in a way that ensures that it stays in the valid
// range [kMinValidFabricIndex, kMaxValidFabricIndex].
FabricIndex NextFabricIndex(FabricIndex fabricIndex)
{
if (fabricIndex == kMaxValidFabricIndex)
{
return kMinValidFabricIndex;
}
return static_cast<FabricIndex>(fabricIndex + 1);
}
} // anonymous namespace
void FabricTable::UpdateNextAvailableFabricIndex()
{
// Only called when mNextAvailableFabricIndex.HasValue()
for (FabricIndex candidate = NextFabricIndex(mNextAvailableFabricIndex.Value()); candidate != mNextAvailableFabricIndex.Value();
candidate = NextFabricIndex(candidate))
{
if (!FindFabricWithIndex(candidate))
{
mNextAvailableFabricIndex.SetValue(candidate);
return;
}
}
mNextAvailableFabricIndex.ClearValue();
}
CHIP_ERROR FabricTable::StoreFabricIndexInfo() const
{
uint8_t buf[IndexInfoTLVMaxSize()];
TLV::TLVWriter writer;
writer.Init(buf);
TLV::TLVType outerType;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
if (mNextAvailableFabricIndex.HasValue())
{
writer.Put(kNextAvailableFabricIndexTag, mNextAvailableFabricIndex.Value());
}
else
{
writer.PutNull(kNextAvailableFabricIndexTag);
}
TLV::TLVType innerContainerType;
ReturnErrorOnFailure(writer.StartContainer(kFabricIndicesTag, TLV::kTLVType_Array, innerContainerType));
// Only enumerate the fabrics that are initialized.
for (const auto & fabric : *this)
{
writer.Put(TLV::AnonymousTag(), fabric.GetFabricIndex());
}
ReturnErrorOnFailure(writer.EndContainer(innerContainerType));
ReturnErrorOnFailure(writer.EndContainer(outerType));
const auto indexInfoLength = writer.GetLengthWritten();
VerifyOrReturnError(CanCastTo<uint16_t>(indexInfoLength), CHIP_ERROR_BUFFER_TOO_SMALL);
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FabricIndexInfo().KeyName(), buf,
static_cast<uint16_t>(indexInfoLength)));
return CHIP_NO_ERROR;
}
void FabricTable::EnsureNextAvailableFabricIndexUpdated()
{
if (!mNextAvailableFabricIndex.HasValue() && mFabricCount < kMaxValidFabricIndex)
{
// We must have a fabric index available here. This situation could
// happen if we fail to store fabric index info when deleting a
// fabric.
mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex);
if (FindFabricWithIndex(kMinValidFabricIndex))
{
UpdateNextAvailableFabricIndex();
}
}
}
CHIP_ERROR FabricTable::ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader)
{
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType containerType;
ReturnErrorOnFailure(reader.EnterContainer(containerType));
ReturnErrorOnFailure(reader.Next(kNextAvailableFabricIndexTag));
if (reader.GetType() == TLV::kTLVType_Null)
{
mNextAvailableFabricIndex.ClearValue();
}
else
{
ReturnErrorOnFailure(reader.Get(mNextAvailableFabricIndex.Emplace()));
}
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, kFabricIndicesTag));
TLV::TLVType arrayType;
ReturnErrorOnFailure(reader.EnterContainer(arrayType));
CHIP_ERROR err;
while ((err = reader.Next()) == CHIP_NO_ERROR)
{
if (mFabricCount >= ArraySize(mStates))
{
// We have nowhere to deserialize this fabric info into.
return CHIP_ERROR_NO_MEMORY;
}
auto & fabric = mStates[mFabricCount];
FabricIndex currentFabricIndex = kUndefinedFabricIndex;
ReturnErrorOnFailure(reader.Get(currentFabricIndex));
err = LoadFromStorage(&fabric, currentFabricIndex);
if (err == CHIP_NO_ERROR)
{
++mFabricCount;
}
else
{
// This could happen if we failed to store our fabric index info
// after we deleted the fabric from storage. Just ignore this
// fabric index and keep going.
}
}
if (err != CHIP_END_OF_TLV)
{
return err;
}
ReturnErrorOnFailure(reader.ExitContainer(arrayType));
ReturnErrorOnFailure(reader.ExitContainer(containerType));
ReturnErrorOnFailure(reader.VerifyEndOfContainer());
EnsureNextAvailableFabricIndexUpdated();
return CHIP_NO_ERROR;
}
Crypto::P256Keypair * FabricTable::AllocateEphemeralKeypairForCASE()
{
if (mOperationalKeystore != nullptr)
{
return mOperationalKeystore->AllocateEphemeralKeypairForCASE();
}
return Platform::New<Crypto::P256Keypair>();
}
void FabricTable::ReleaseEphemeralKeypair(Crypto::P256Keypair * keypair)
{
if (mOperationalKeystore != nullptr)
{
mOperationalKeystore->ReleaseEphemeralKeypair(keypair);
}
else
{
Platform::Delete<Crypto::P256Keypair>(keypair);
}
}
CHIP_ERROR FabricTable::StoreCommitMarker(const CommitMarker & commitMarker)
{
uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()];
TLV::TLVWriter writer;
writer.Init(tlvBuf);
TLV::TLVType outerType;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
ReturnErrorOnFailure(writer.Put(kMarkerFabricIndexTag, commitMarker.fabricIndex));
ReturnErrorOnFailure(writer.Put(kMarkerIsAdditionTag, commitMarker.isAddition));
ReturnErrorOnFailure(writer.EndContainer(outerType));
const auto markerContextTLVLength = writer.GetLengthWritten();
VerifyOrReturnError(CanCastTo<uint16_t>(markerContextTLVLength), CHIP_ERROR_BUFFER_TOO_SMALL);
return mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName(), tlvBuf,
static_cast<uint16_t>(markerContextTLVLength));
}
CHIP_ERROR FabricTable::GetCommitMarker(CommitMarker & outCommitMarker)
{
uint8_t tlvBuf[CommitMarkerContextTLVMaxSize()];
uint16_t tlvSize = sizeof(tlvBuf);
ReturnErrorOnFailure(
mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName(), tlvBuf, tlvSize));
// If buffer was too small, we won't reach here.
TLV::ContiguousBufferTLVReader reader;
reader.Init(tlvBuf, tlvSize);
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType containerType;
ReturnErrorOnFailure(reader.EnterContainer(containerType));
ReturnErrorOnFailure(reader.Next(kMarkerFabricIndexTag));
ReturnErrorOnFailure(reader.Get(outCommitMarker.fabricIndex));
ReturnErrorOnFailure(reader.Next(kMarkerIsAdditionTag));
ReturnErrorOnFailure(reader.Get(outCommitMarker.isAddition));
// Don't try to exit container: we got all we needed. This allows us to
// avoid erroring-out on newer versions.
return CHIP_NO_ERROR;
}
void FabricTable::ClearCommitMarker()
{
mStorage->SyncDeleteKeyValue(DefaultStorageKeyAllocator::FailSafeCommitMarkerKey().KeyName());
}
bool FabricTable::HasOperationalKeyForFabric(FabricIndex fabricIndex) const
{
const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, false);
if (fabricInfo->HasOperationalKey())
{
// Legacy case of manually injected keys: delegate to FabricInfo directly
return true;
}
if (mOperationalKeystore != nullptr)
{
return mOperationalKeystore->HasOpKeypairForFabric(fabricIndex);
}
return false;
}
CHIP_ERROR FabricTable::SignWithOpKeypair(FabricIndex fabricIndex, ByteSpan message, P256ECDSASignature & outSignature) const
{
const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
if (fabricInfo->HasOperationalKey())
{
// Legacy case of manually injected FabricInfo: delegate to FabricInfo directly
return fabricInfo->SignWithOpKeypair(message, outSignature);
}
if (mOperationalKeystore != nullptr)
{
return mOperationalKeystore->SignWithOpKeypair(fabricIndex, message, outSignature);
}
return CHIP_ERROR_KEY_NOT_FOUND;
}
bool FabricTable::HasPendingOperationalKey(bool & outIsPendingKeyForUpdateNoc) const
{
// We can only manage commissionable pending fail-safe state if we have a keystore
bool hasOpKeyPending = mStateFlags.Has(StateFlags::kIsOperationalKeyPending);
if (hasOpKeyPending)
{
// We kept track of whether the last `AllocatePendingOperationalKey` for was for an update,
// so give it back out here.
outIsPendingKeyForUpdateNoc = mStateFlags.Has(StateFlags::kIsPendingKeyForUpdateNoc);
}
return hasOpKeyPending;
}
bool FabricTable::SetPendingDataFabricIndex(FabricIndex fabricIndex)
{
bool isLegal = (mFabricIndexWithPendingState == kUndefinedFabricIndex) || (mFabricIndexWithPendingState == fabricIndex);
if (isLegal)
{
mFabricIndexWithPendingState = fabricIndex;
}
return isLegal;
}
CHIP_ERROR FabricTable::AllocatePendingOperationalKey(Optional<FabricIndex> fabricIndex, MutableByteSpan & outputCsr)
{
// We can only manage commissionable pending fail-safe state if we have a keystore
VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_INCORRECT_STATE);
// We can only allocate a pending key if no pending state (NOC, ICAC) already present,
// since there can only be one pending state per fail-safe.
VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(outputCsr.size() >= Crypto::kMAX_CSR_Length, CHIP_ERROR_BUFFER_TOO_SMALL);
EnsureNextAvailableFabricIndexUpdated();
FabricIndex fabricIndexToUse = kUndefinedFabricIndex;
if (fabricIndex.HasValue())
{
// Check we not are trying to do an update but also change the root: forbidden
ReturnErrorCodeIf(mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE);
// Fabric update case (e.g. UpdateNOC): we already know the fabric index
fabricIndexToUse = fabricIndex.Value();
mStateFlags.Set(StateFlags::kIsPendingKeyForUpdateNoc);
}
else if (mNextAvailableFabricIndex.HasValue())
{
// Fabric addition case (e.g. AddNOC): we need to allocate for the next pending fabric index
fabricIndexToUse = mNextAvailableFabricIndex.Value();
mStateFlags.Clear(StateFlags::kIsPendingKeyForUpdateNoc);
}
else
{
// Fabric addition, but adding NOC would fail on table full: let's not allocate a key
return CHIP_ERROR_NO_MEMORY;
}
VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mOperationalKeystore->NewOpKeypairForFabric(mFabricIndexWithPendingState, outputCsr));
mStateFlags.Set(StateFlags::kIsOperationalKeyPending);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::AddNewPendingTrustedRootCert(const ByteSpan & rcac)
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
// We should not already have pending NOC chain elements when we get here
ReturnErrorCodeIf(
mStateFlags.HasAny(StateFlags::kIsTrustedRootPending, StateFlags::kIsUpdatePending, StateFlags::kIsAddPending),
CHIP_ERROR_INCORRECT_STATE);
EnsureNextAvailableFabricIndexUpdated();
FabricIndex fabricIndexToUse = kUndefinedFabricIndex;
if (mNextAvailableFabricIndex.HasValue())
{
fabricIndexToUse = mNextAvailableFabricIndex.Value();
}
else
{
// Fabric addition, but adding root would fail on table full: let's not allocate a fabric
return CHIP_ERROR_NO_MEMORY;
}
VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX);
VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mOpCertStore->AddNewTrustedRootCertForFabric(fabricIndexToUse, rcac));
mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent);
mStateFlags.Set(StateFlags::kIsTrustedRootPending);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::FindExistingFabricByNocChaining(FabricIndex pendingFabricIndex, const ByteSpan & noc,
FabricIndex & outMatchingFabricIndex) const
{
// Check whether we already have a matching fabric from a cert chain perspective.
// To do so we have to extract the FabricID from the NOC and the root public key from the RCAC.
// We assume the RCAC is currently readable from OperationalCertificateStore, whether pending
// or persisted.
FabricId fabricId;
{
NodeId unused;
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &unused, &fabricId));
}
// Try to find the root public key from the current existing fabric
Crypto::P256PublicKey candidateRootKey;
{
uint8_t tempRcac[kMaxCHIPCertLength];
MutableByteSpan tempRcacSpan{ tempRcac };
Credentials::P256PublicKeySpan publicKeySpan;
ReturnErrorOnFailure(FetchRootCert(pendingFabricIndex, tempRcacSpan));
ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(tempRcacSpan, publicKeySpan));
candidateRootKey = publicKeySpan;
}
for (auto & existingFabric : *this)
{
if (existingFabric.GetFabricId() == fabricId)
{
P256PublicKey existingRootKey;
ReturnErrorOnFailure(FetchRootPubkey(existingFabric.GetFabricIndex(), existingRootKey));
if (existingRootKey.Matches(candidateRootKey))
{
outMatchingFabricIndex = existingFabric.GetFabricIndex();
return CHIP_NO_ERROR;
}
}
}
// Did not find: set outMatchingFabricIndex to kUndefinedFabricIndex
outMatchingFabricIndex = kUndefinedFabricIndex;
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::AddNewPendingFabricCommon(const ByteSpan & noc, const ByteSpan & icac, uint16_t vendorId,
Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned,
FabricIndex * outNewFabricIndex)
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(outNewFabricIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
// We should already have a pending root when we get here
VerifyOrReturnError(mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE);
// We should not have pending update when we get here
VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsUpdatePending), CHIP_ERROR_INCORRECT_STATE);
EnsureNextAvailableFabricIndexUpdated();
FabricIndex fabricIndexToUse = kUndefinedFabricIndex;
if (mNextAvailableFabricIndex.HasValue())
{
fabricIndexToUse = mNextAvailableFabricIndex.Value();
}
else
{
// Fabric addition, but adding fabric would fail on table full: let's not allocate a fabric
return CHIP_ERROR_NO_MEMORY;
}
// Internal consistency check that mNextAvailableFabricIndex is indeed properly updated...
// TODO: Centralize this a bit.
VerifyOrReturnError(IsValidFabricIndex(fabricIndexToUse), CHIP_ERROR_INVALID_FABRIC_INDEX);
if (existingOpKey == nullptr)
{
// If existing operational key not provided, we need to have a keystore present.
// It should already have an operational key pending.
VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
// Make sure we have an operational key, pending or not
VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndexToUse) ||
mOperationalKeystore->HasPendingOpKeypair(),
CHIP_ERROR_KEY_NOT_FOUND);
}
// Check for new fabric colliding with an existing fabric
if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored))
{
FabricIndex collidingFabricIndex = kUndefinedFabricIndex;
ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndexToUse, noc, collidingFabricIndex));
ReturnErrorCodeIf(collidingFabricIndex != kUndefinedFabricIndex, CHIP_ERROR_FABRIC_EXISTS);
}
// We don't have a collision, handle the temp insert of NOC/ICAC
ReturnErrorOnFailure(mOpCertStore->AddNewOpCertsForFabric(fabricIndexToUse, noc, icac));
VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndexToUse), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err =
AddOrUpdateInner(fabricIndexToUse, /* isAddition = */ true, existingOpKey, isExistingOpKeyExternallyOwned, vendorId);
if (err != CHIP_NO_ERROR)
{
// Revert partial state added on error
RevertPendingOpCertsExceptRoot();
return err;
}
mStateFlags.Set(StateFlags::kIsAddPending);
mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent);
// Notify that NOC was added (at least transiently)
*outNewFabricIndex = fabricIndexToUse;
NotifyFabricUpdated(fabricIndexToUse);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::UpdatePendingFabricCommon(FabricIndex fabricIndex, const ByteSpan & noc, const ByteSpan & icac,
Crypto::P256Keypair * existingOpKey, bool isExistingOpKeyExternallyOwned)
{
VerifyOrReturnError(mOpCertStore != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_ARGUMENT);
if (existingOpKey == nullptr)
{
// If existing operational key not provided, we need to have a keystore present.
// It should already have an operational key pending.
VerifyOrReturnError(mOperationalKeystore != nullptr, CHIP_ERROR_KEY_NOT_FOUND);
// Make sure we have an operational key, pending or not
VerifyOrReturnError(mOperationalKeystore->HasOpKeypairForFabric(fabricIndex) || mOperationalKeystore->HasPendingOpKeypair(),
CHIP_ERROR_KEY_NOT_FOUND);
}
// We should should not have a pending root when we get here, since we can't update root on update
VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsTrustedRootPending), CHIP_ERROR_INCORRECT_STATE);
// We should not have pending add when we get here, due to internal interlocks
VerifyOrReturnError(!mStateFlags.Has(StateFlags::kIsAddPending), CHIP_ERROR_INCORRECT_STATE);
// Make sure we are updating at least an existing FabricIndex
const auto * fabricInfo = FindFabricWithIndex(fabricIndex);
ReturnErrorCodeIf(fabricInfo == nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
// Check for an existing fabric matching RCAC and FabricID. We must find a correct
// existing fabric that chains to same root. We assume the stored root is correct.
if (!mStateFlags.Has(StateFlags::kAreCollidingFabricsIgnored))
{
FabricIndex collidingFabricIndex = kUndefinedFabricIndex;
ReturnErrorOnFailure(FindExistingFabricByNocChaining(fabricIndex, noc, collidingFabricIndex));
ReturnErrorCodeIf(collidingFabricIndex != fabricIndex, CHIP_ERROR_INVALID_FABRIC_INDEX);
}
// Handle the temp insert of NOC/ICAC
ReturnErrorOnFailure(mOpCertStore->UpdateOpCertsForFabric(fabricIndex, noc, icac));
VerifyOrReturnError(SetPendingDataFabricIndex(fabricIndex), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = AddOrUpdateInner(fabricIndex, /* isAddition = */ false, existingOpKey, isExistingOpKeyExternallyOwned,
fabricInfo->GetVendorId());
if (err != CHIP_NO_ERROR)
{
// Revert partial state added on error
// TODO: Figure-out if there is a better way. We need to make sure we are not inconsistent on elements
// other than the opcerts.
RevertPendingOpCertsExceptRoot();
return err;
}
mStateFlags.Set(StateFlags::kIsUpdatePending);
mStateFlags.Set(StateFlags::kIsPendingFabricDataPresent);
// Notify that NOC was updated (at least transiently)
NotifyFabricUpdated(fabricIndex);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::CommitPendingFabricData()
{
VerifyOrReturnError((mStorage != nullptr) && (mOpCertStore != nullptr), CHIP_ERROR_INCORRECT_STATE);
bool haveNewTrustedRoot = mStateFlags.Has(StateFlags::kIsTrustedRootPending);
bool isAdding = mStateFlags.Has(StateFlags::kIsAddPending);
bool isUpdating = mStateFlags.Has(StateFlags::kIsUpdatePending);
bool hasPending = mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent);
bool onlyHaveNewTrustedRoot = hasPending && haveNewTrustedRoot && !(isAdding || isUpdating);
bool hasInvalidInternalState = hasPending && (!IsValidFabricIndex(mFabricIndexWithPendingState) || !(isAdding || isUpdating));
FabricIndex fabricIndexBeingCommitted = mFabricIndexWithPendingState;
// Proceed with Update/Add pre-flight checks
if (hasPending && !hasInvalidInternalState)
{
if ((isAdding && isUpdating) || (isAdding && !haveNewTrustedRoot))
{
ChipLogError(FabricProvisioning, "Found inconsistent interlocks during commit %u/%u/%u!",
static_cast<unsigned>(isAdding), static_cast<unsigned>(isUpdating),
static_cast<unsigned>(haveNewTrustedRoot));
hasInvalidInternalState = true;
}
}
// Make sure we actually have a pending fabric
FabricInfo * pendingFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted);
if (isUpdating && hasPending && !hasInvalidInternalState)
{
if (!mPendingFabric.IsInitialized() || (mPendingFabric.GetFabricIndex() != fabricIndexBeingCommitted) ||
(pendingFabricEntry == nullptr))
{
ChipLogError(FabricProvisioning, "Missing pending fabric on update during commit!");
hasInvalidInternalState = true;
}
}
if (isAdding && hasPending && !hasInvalidInternalState)
{
bool opCertStoreHasRoot = mOpCertStore->HasCertificateForFabric(fabricIndexBeingCommitted, CertChainElement::kRcac);
if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending) || !opCertStoreHasRoot)
{
ChipLogError(FabricProvisioning, "Missing trusted root for fabric add during commit!");
hasInvalidInternalState = true;
}
}
if ((isAdding || isUpdating) && hasPending && !hasInvalidInternalState)
{
if (!HasOperationalKeyForFabric(fabricIndexBeingCommitted))
{
ChipLogError(FabricProvisioning, "Could not find an operational key during commit!");
hasInvalidInternalState = true;
}
}
// If there was nothing pending, we are either in a completely OK state, or weird internally inconsistent
// state. In either case, let's clear all pending state anyway, in case it was partially stale!
if (!hasPending || onlyHaveNewTrustedRoot || hasInvalidInternalState)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (onlyHaveNewTrustedRoot)
{
ChipLogError(FabricProvisioning,
"Failed to commit: tried to commit with only a new trusted root cert. No data committed.");
err = CHIP_ERROR_INCORRECT_STATE;
}
else if (hasInvalidInternalState)
{
ChipLogError(FabricProvisioning, "Failed to commit: internally inconsistent state!");
err = CHIP_ERROR_INTERNAL;
}
else
{
// There was nothing pending and no error...
}
// Clear all pending state anyway, in case it was partially stale!
{
mStateFlags.ClearAll();
mFabricIndexWithPendingState = kUndefinedFabricIndex;
mPendingFabric.Reset();
mOpCertStore->RevertPendingOpCerts();
if (mOperationalKeystore != nullptr)
{
mOperationalKeystore->RevertPendingKeypair();
}
}
return err;
}
// ==== Start of actual commit transaction after pre-flight checks ====
CHIP_ERROR stickyError = StoreCommitMarker(CommitMarker{ fabricIndexBeingCommitted, isAdding });
bool failedCommitMarker = (stickyError != CHIP_NO_ERROR);
if (failedCommitMarker)
{
ChipLogError(FabricProvisioning, "Failed to store commit marker, may be inconsistent if reboot happens during fail-safe!");
}
{
// This scope block is to illustrate the complete commit transaction
// state. We can see it contains a LARGE number of items...
// Atomically assume data no longer pending, since we are committing it. Do so here
// so that FindFabricBy* will return real data and never pending.
mStateFlags.Clear(StateFlags::kIsPendingFabricDataPresent);
if (isUpdating)
{
// This will get the non-pending fabric
FabricInfo * existingFabricToUpdate = GetMutableFabricByIndex(fabricIndexBeingCommitted);
// Multiple interlocks validated the below, so it's fatal if we are somehow incoherent here
VerifyOrDie((existingFabricToUpdate != nullptr) && (existingFabricToUpdate != &mPendingFabric));
// Commit the pending entry to local in-memory fabric metadata, which
// also moves operational keys if not backed by OperationalKeystore
*existingFabricToUpdate = std::move(mPendingFabric);
}
// Store pending metadata first
FabricInfo * liveFabricEntry = GetMutableFabricByIndex(fabricIndexBeingCommitted);
VerifyOrDie(liveFabricEntry != nullptr);
CHIP_ERROR metadataErr = StoreFabricMetadata(liveFabricEntry);
if (metadataErr != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to commit pending fabric metadata: %" CHIP_ERROR_FORMAT, metadataErr.Format());
}
stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : metadataErr;
// We can only manage commissionable pending fail-safe state if we have a keystore
CHIP_ERROR keyErr = CHIP_NO_ERROR;
if ((mOperationalKeystore != nullptr) && mOperationalKeystore->HasOpKeypairForFabric(fabricIndexBeingCommitted) &&
mOperationalKeystore->HasPendingOpKeypair())
{
keyErr = mOperationalKeystore->CommitOpKeypairForFabric(fabricIndexBeingCommitted);
if (keyErr != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to commit pending operational keypair %" CHIP_ERROR_FORMAT,
keyErr.Format());
mOperationalKeystore->RevertPendingKeypair();
}
}
stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : keyErr;
// For testing only, early return (NEVER OCCURS OTHERWISE) during the commit
// so that clean-ups using the commit marker can be tested.
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
{
if (mStateFlags.Has(StateFlags::kAbortCommitForTest))
{
// Clear state so that shutdown doesn't attempt clean-up
mStateFlags.ClearAll();
mFabricIndexWithPendingState = kUndefinedFabricIndex;
mPendingFabric.Reset();
ChipLogError(FabricProvisioning, "Aborting commit in middle of transaction for testing.");
return CHIP_ERROR_INTERNAL;
}
}
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
// Commit operational certs
CHIP_ERROR opCertErr = mOpCertStore->CommitOpCertsForFabric(fabricIndexBeingCommitted);
if (opCertErr != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to commit pending operational certificates %" CHIP_ERROR_FORMAT,
opCertErr.Format());
mOpCertStore->RevertPendingOpCerts();
}
stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : opCertErr;
// Failure to commit Last Known Good Time is non-fatal. If Last Known
// Good Time is incorrect and this causes incoming certificates to
// appear invalid, the certificate validity policy will see this
// condition and can act appropriately.
CHIP_ERROR lkgtErr = mLastKnownGoodTime.CommitPendingLastKnownGoodChipEpochTime();
if (lkgtErr != CHIP_NO_ERROR)
{
// Log but this is not sticky...
ChipLogError(FabricProvisioning, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, lkgtErr.Format());
}
// If an Add occurred, let's update the fabric index
CHIP_ERROR fabricIndexErr = CHIP_NO_ERROR;
if (mStateFlags.Has(StateFlags::kIsAddPending))
{
UpdateNextAvailableFabricIndex();
fabricIndexErr = StoreFabricIndexInfo();
if (fabricIndexErr != CHIP_NO_ERROR)
{
ChipLogError(FabricProvisioning, "Failed to commit pending fabric indices: %" CHIP_ERROR_FORMAT,
fabricIndexErr.Format());
}
}
stickyError = (stickyError != CHIP_NO_ERROR) ? stickyError : fabricIndexErr;
}
// Commit must have same side-effect as reverting all pending data
mStateFlags.ClearAll();
mFabricIndexWithPendingState = kUndefinedFabricIndex;
mPendingFabric.Reset();
if (stickyError != CHIP_NO_ERROR)
{
// Blow-away everything if we got past any storage, even on Update: system state is broken
// TODO: Develop a way to properly revert in the future, but this is very difficult
Delete(fabricIndexBeingCommitted);
RevertPendingFabricData();
}
else
{
NotifyFabricCommitted(fabricIndexBeingCommitted);
}
// Clear commit marker no matter what: if we got here, there was no reboot and previous clean-ups
// did their job.
ClearCommitMarker();
return stickyError;
}
void FabricTable::RevertPendingFabricData()
{
// Will clear pending UpdateNoc/AddNOC
RevertPendingOpCertsExceptRoot();
if (mOperationalKeystore != nullptr)
{
mOperationalKeystore->RevertPendingKeypair();
}
// Clear everything else
if (mOpCertStore != nullptr)
{
mOpCertStore->RevertPendingOpCerts();
}
mLastKnownGoodTime.RevertPendingLastKnownGoodChipEpochTime();
mStateFlags.ClearAll();
mFabricIndexWithPendingState = kUndefinedFabricIndex;
}
void FabricTable::RevertPendingOpCertsExceptRoot()
{
mPendingFabric.Reset();
if (mStateFlags.Has(StateFlags::kIsPendingFabricDataPresent))
{
ChipLogError(FabricProvisioning, "Reverting pending fabric data for fabric 0x%x",
static_cast<unsigned>(mFabricIndexWithPendingState));
}
if (mOpCertStore != nullptr)
{
mOpCertStore->RevertPendingOpCertsExceptRoot();
}
if (mStateFlags.Has(StateFlags::kIsAddPending))
{
// If we have a pending add, let's make sure to kill the pending fabric metadata and return it to viable state.
Delete(mFabricIndexWithPendingState);
}
mStateFlags.Clear(StateFlags::kIsAddPending);
mStateFlags.Clear(StateFlags::kIsUpdatePending);
if (!mStateFlags.Has(StateFlags::kIsTrustedRootPending))
{
mFabricIndexWithPendingState = kUndefinedFabricIndex;
}
}
CHIP_ERROR FabricTable::SetFabricLabel(FabricIndex fabricIndex, const CharSpan & fabricLabel)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(IsValidFabricIndex(fabricIndex), CHIP_ERROR_INVALID_FABRIC_INDEX);
ReturnErrorCodeIf(fabricLabel.size() > kFabricLabelMaxLengthInBytes, CHIP_ERROR_INVALID_ARGUMENT);
FabricInfo * fabricInfo = GetMutableFabricByIndex(fabricIndex);
bool fabricIsInitialized = (fabricInfo != nullptr) && fabricInfo->IsInitialized();
VerifyOrReturnError(fabricIsInitialized, CHIP_ERROR_INVALID_FABRIC_INDEX);
// Update fabric table current in-memory entry, whether pending or not
ReturnErrorOnFailure(fabricInfo->SetFabricLabel(fabricLabel));
if (!mStateFlags.HasAny(StateFlags::kIsAddPending, StateFlags::kIsUpdatePending) && (fabricInfo != &mPendingFabric))
{
// Nothing is pending, we have to store immediately.
ReturnErrorOnFailure(StoreFabricMetadata(fabricInfo));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::GetFabricLabel(FabricIndex fabricIndex, CharSpan & outFabricLabel)
{
const FabricInfo * fabricInfo = FindFabricWithIndex(fabricIndex);
VerifyOrReturnError(fabricInfo != nullptr, CHIP_ERROR_INVALID_FABRIC_INDEX);
outFabricLabel = fabricInfo->GetFabricLabel();
return CHIP_NO_ERROR;
}
} // namespace chip