blob: 3d188530f149dc074cd1799340ea8b4856d43e9c [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>
#if CHIP_CRYPTO_HSM
#include <crypto/hsm/CHIPCryptoPALHsm.h>
#endif
namespace chip {
using namespace Credentials;
using namespace Crypto;
CHIP_ERROR FabricInfo::SetFabricLabel(const CharSpan & fabricLabel)
{
Platform::CopyString(mFabricLabel, fabricLabel);
return CHIP_NO_ERROR;
}
namespace {
// Tags for our metadata storage.
constexpr TLV::Tag kVendorIdTag = TLV::ContextTag(0);
constexpr TLV::Tag kFabricLabelTag = TLV::ContextTag(1);
// Tags for our operational keypair storage.
constexpr TLV::Tag kOpKeyVersionTag = TLV::ContextTag(0);
constexpr TLV::Tag kOpKeyDataTag = TLV::ContextTag(1);
// If this version grows beyond UINT16_MAX, adjust OpKeypairTLVMaxSize
// accordingly.
constexpr uint16_t kOpKeyVersion = 1;
// Tags for our index list storage.
constexpr TLV::Tag kNextAvailableFabricIndexTag = TLV::ContextTag(0);
constexpr TLV::Tag kFabricIndicesTag = TLV::ContextTag(1);
} // anonymous namespace
CHIP_ERROR FabricInfo::CommitToStorage(PersistentStorageDelegate * storage)
{
DefaultStorageKeyAllocator keyAlloc;
VerifyOrReturnError(mRootCert.size() <= kMaxCHIPCertLength && mICACert.size() <= kMaxCHIPCertLength &&
mNOCCert.size() <= kMaxCHIPCertLength,
CHIP_ERROR_BUFFER_TOO_SMALL);
static_assert(kMaxCHIPCertLength <= UINT16_MAX, "Casting to uint16_t won't be safe");
ReturnErrorOnFailure(
storage->SyncSetKeyValue(keyAlloc.FabricRCAC(mFabricIndex), mRootCert.data(), static_cast<uint16_t>(mRootCert.size())));
// Workaround for the fact that some storage backends do not allow storing
// a nullptr with 0 length. See
// https://github.com/project-chip/connectedhomeip/issues/16030.
if (!mICACert.empty())
{
ReturnErrorOnFailure(
storage->SyncSetKeyValue(keyAlloc.FabricICAC(mFabricIndex), mICACert.data(), static_cast<uint16_t>(mICACert.size())));
}
else
{
// Make sure there is no stale data.
CHIP_ERROR err = storage->SyncDeleteKeyValue(keyAlloc.FabricICAC(mFabricIndex));
if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
ReturnErrorOnFailure(err);
}
}
ReturnErrorOnFailure(
storage->SyncSetKeyValue(keyAlloc.FabricNOC(mFabricIndex), mNOCCert.data(), static_cast<uint16_t>(mNOCCert.size())));
{
Crypto::P256SerializedKeypair serializedOpKey;
if (mOperationalKey != nullptr)
{
ReturnErrorOnFailure(mOperationalKey->Serialize(serializedOpKey));
}
else
{
// Could we just not store it instead? What would deserialize need
// to do then?
P256Keypair keypair;
ReturnErrorOnFailure(keypair.Initialize());
ReturnErrorOnFailure(keypair.Serialize(serializedOpKey));
}
uint8_t buf[OpKeyTLVMaxSize()];
TLV::TLVWriter writer;
writer.Init(buf);
TLV::TLVType outerType;
ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
ReturnErrorOnFailure(writer.Put(kOpKeyVersionTag, kOpKeyVersion));
ReturnErrorOnFailure(writer.Put(kOpKeyDataTag, ByteSpan(serializedOpKey.Bytes(), serializedOpKey.Length())));
ReturnErrorOnFailure(writer.EndContainer(outerType));
const auto opKeyLength = writer.GetLengthWritten();
VerifyOrReturnError(CanCastTo<uint16_t>(opKeyLength), CHIP_ERROR_BUFFER_TOO_SMALL);
ReturnErrorOnFailure(storage->SyncSetKeyValue(keyAlloc.FabricOpKey(mFabricIndex), buf, static_cast<uint16_t>(opKeyLength)));
}
{
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(keyAlloc.FabricMetadata(mFabricIndex), buf, static_cast<uint16_t>(metadataLength)));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::LoadFromStorage(PersistentStorageDelegate * storage)
{
DefaultStorageKeyAllocator keyAlloc;
ChipLogProgress(Inet, "Loading from storage for fabric index 0x%x", static_cast<unsigned>(mFabricIndex));
// Scopes for "size" so we don't forget to re-initialize it between gets,
// since each get modifies it.
{
uint8_t buf[Credentials::kMaxCHIPCertLength];
uint16_t size = sizeof(buf);
ReturnErrorOnFailure(storage->SyncGetKeyValue(keyAlloc.FabricRCAC(mFabricIndex), buf, size));
ReturnErrorOnFailure(SetRootCert(ByteSpan(buf, size)));
}
{
uint8_t buf[Credentials::kMaxCHIPCertLength];
uint16_t size = sizeof(buf);
CHIP_ERROR err = storage->SyncGetKeyValue(keyAlloc.FabricICAC(mFabricIndex), buf, size);
if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
// That's OK; that just means no ICAC.
size = 0;
}
else
{
ReturnErrorOnFailure(err);
}
ReturnErrorOnFailure(SetICACert(ByteSpan(buf, size)));
}
{
uint8_t buf[Credentials::kMaxCHIPCertLength];
uint16_t size = sizeof(buf);
ReturnErrorOnFailure(storage->SyncGetKeyValue(keyAlloc.FabricNOC(mFabricIndex), buf, size));
ByteSpan nocCert(buf, size);
NodeId nodeId;
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(nocCert, &nodeId, &mFabricId));
// The compressed fabric ID doesn't change for a fabric over time.
// Computing it here will save computational overhead when it's accessed by other
// parts of the code.
ReturnErrorOnFailure(GeneratePeerId(mFabricId, nodeId, &mOperationalId));
ReturnErrorOnFailure(SetNOCCert(nocCert));
}
{
// Use a CapacityBoundBuffer to get RAII secret data clearing on scope exit.
Crypto::CapacityBoundBuffer<OpKeyTLVMaxSize()> buf;
uint16_t size = static_cast<uint16_t>(buf.Capacity());
ReturnErrorOnFailure(storage->SyncGetKeyValue(keyAlloc.FabricOpKey(mFabricIndex), buf.Bytes(), size));
buf.SetLength(static_cast<size_t>(size));
TLV::ContiguousBufferTLVReader reader;
reader.Init(buf.Bytes(), buf.Length());
ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
TLV::TLVType containerType;
ReturnErrorOnFailure(reader.EnterContainer(containerType));
ReturnErrorOnFailure(reader.Next(kOpKeyVersionTag));
uint16_t opKeyVersion;
ReturnErrorOnFailure(reader.Get(opKeyVersion));
VerifyOrReturnError(opKeyVersion == kOpKeyVersion, CHIP_ERROR_VERSION_MISMATCH);
ReturnErrorOnFailure(reader.Next(kOpKeyDataTag));
ByteSpan keyData;
ReturnErrorOnFailure(reader.GetByteView(keyData));
// Unfortunately, we have to copy the data into a P256SerializedKeypair.
Crypto::P256SerializedKeypair serializedOpKey;
VerifyOrReturnError(keyData.size() <= serializedOpKey.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
memcpy(serializedOpKey.Bytes(), keyData.data(), keyData.size());
serializedOpKey.SetLength(keyData.size());
if (mOperationalKey == nullptr)
{
#ifdef ENABLE_HSM_CASE_OPS_KEY
mOperationalKey = chip::Platform::New<P256KeypairHSM>();
#else
mOperationalKey = chip::Platform::New<P256Keypair>();
#endif
}
VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY);
ReturnErrorOnFailure(mOperationalKey->Deserialize(serializedOpKey));
ReturnErrorOnFailure(reader.ExitContainer(containerType));
ReturnErrorOnFailure(reader.VerifyEndOfContainer());
}
{
uint8_t buf[MetadataTLVMaxSize()];
uint16_t size = sizeof(buf);
ReturnErrorOnFailure(storage->SyncGetKeyValue(keyAlloc.FabricMetadata(mFabricIndex), 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());
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::GeneratePeerId(FabricId fabricId, NodeId nodeId, PeerId * compressedPeerId) const
{
ReturnErrorCodeIf(compressedPeerId == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
uint8_t compressedFabricIdBuf[sizeof(uint64_t)];
MutableByteSpan compressedFabricIdSpan(compressedFabricIdBuf);
P256PublicKey rootPubkey;
{
P256PublicKeySpan rootPubkeySpan;
ReturnErrorOnFailure(GetRootPubkey(rootPubkeySpan));
rootPubkey = rootPubkeySpan;
}
ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubkey, fabricId, 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.
CompressedFabricId compressedFabricId = Encoding::BigEndian::Get64(compressedFabricIdBuf);
compressedPeerId->SetCompressedFabricId(compressedFabricId);
compressedPeerId->SetNodeId(nodeId);
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::DeleteFromStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex)
{
DefaultStorageKeyAllocator keyAlloc;
// Try to delete all the state even if one of the deletes fails.
typedef const char * (DefaultStorageKeyAllocator::*KeyGetter)(FabricIndex);
constexpr KeyGetter keyGetters[] = { &DefaultStorageKeyAllocator::FabricNOC, &DefaultStorageKeyAllocator::FabricICAC,
&DefaultStorageKeyAllocator::FabricRCAC, &DefaultStorageKeyAllocator::FabricMetadata,
&DefaultStorageKeyAllocator::FabricOpKey };
CHIP_ERROR prevDeleteErr = CHIP_NO_ERROR;
for (auto & keyGetter : keyGetters)
{
CHIP_ERROR deleteErr = storage->SyncDeleteKeyValue((keyAlloc.*keyGetter)(fabricIndex));
// Keys not existing is not really an error condition.
if (prevDeleteErr == CHIP_NO_ERROR && deleteErr != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
{
prevDeleteErr = deleteErr;
}
}
if (prevDeleteErr != CHIP_NO_ERROR)
{
ChipLogDetail(Discovery, "Error deleting part of fabric %d: %" CHIP_ERROR_FORMAT, fabricIndex, prevDeleteErr.Format());
}
return prevDeleteErr;
}
CHIP_ERROR FabricInfo::SetOperationalKeypair(const P256Keypair * keyPair)
{
VerifyOrReturnError(keyPair != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
P256SerializedKeypair serialized;
ReturnErrorOnFailure(keyPair->Serialize(serialized));
if (mOperationalKey == nullptr)
{
#ifdef ENABLE_HSM_CASE_OPS_KEY
mOperationalKey = chip::Platform::New<P256KeypairHSM>();
#else
mOperationalKey = chip::Platform::New<P256Keypair>();
#endif
}
VerifyOrReturnError(mOperationalKey != nullptr, CHIP_ERROR_NO_MEMORY);
return mOperationalKey->Deserialize(serialized);
}
void FabricInfo::ReleaseCert(MutableByteSpan & cert)
{
if (cert.data() != nullptr)
{
chip::Platform::MemoryFree(cert.data());
}
cert = MutableByteSpan();
}
CHIP_ERROR FabricInfo::SetCert(MutableByteSpan & dstCert, const ByteSpan & srcCert)
{
ReleaseCert(dstCert);
if (srcCert.data() == nullptr || srcCert.size() == 0)
{
return CHIP_NO_ERROR;
}
VerifyOrReturnError(srcCert.size() <= kMaxCHIPCertLength, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(CanCastTo<uint16_t>(srcCert.size()), CHIP_ERROR_INVALID_ARGUMENT);
dstCert = MutableByteSpan(static_cast<uint8_t *>(chip::Platform::MemoryAlloc(srcCert.size())), srcCert.size());
VerifyOrReturnError(dstCert.data() != nullptr, CHIP_ERROR_NO_MEMORY);
memcpy(dstCert.data(), srcCert.data(), srcCert.size());
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, ValidationContext & context,
PeerId & nocPeerId, FabricId & fabricId, Crypto::P256PublicKey & nocPubkey) const
{
// 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(mRootCert, 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 mRootCert.
// It confirms that the certs link correctly (noc -> icac -> mRootCert), and have been correctly signed.
ReturnErrorOnFailure(certificates.FindValidCert(nocSubjectDN, nocSubjectKeyId, context, &resultCert));
NodeId nodeId;
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(certificates.GetLastCert()[0], &nodeId, &fabricId));
CHIP_ERROR err;
FabricId icacFabricId = kUndefinedFabricId;
if (!icac.empty())
{
err = ExtractFabricIdFromCert(certificates.GetCertSet()[1], &icacFabricId);
if (err == CHIP_NO_ERROR)
{
ReturnErrorCodeIf(icacFabricId != fabricId, 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 != fabricId, 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;
}
ReturnErrorOnFailure(GeneratePeerId(fabricId, nodeId, &nocPeerId));
nocPubkey = P256PublicKey(certificates.GetLastCert()[0].mPublicKey);
return CHIP_NO_ERROR;
}
FabricTable::~FabricTable()
{
FabricTableDelegate * delegate = mDelegate;
while (delegate)
{
FabricTableDelegate * temp = delegate->mNext;
if (delegate->mOwnedByFabricTable)
{
chip::Platform::Delete(delegate);
}
delegate = temp;
}
}
FabricInfo * FabricTable::FindFabric(P256PublicKeySpan rootPubKey, FabricId fabricId)
{
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
P256PublicKeySpan candidatePubKey;
if (fabric.GetRootPubkey(candidatePubKey) != CHIP_NO_ERROR)
{
continue;
}
if (rootPubKey.data_equal(candidatePubKey) && fabricId == fabric.GetFabricId())
{
return &fabric;
}
}
return nullptr;
}
FabricInfo * FabricTable::FindFabricWithIndex(FabricIndex fabricIndex)
{
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
if (fabric.GetFabricIndex() == fabricIndex)
{
return &fabric;
}
}
return nullptr;
}
FabricInfo * FabricTable::FindFabricWithCompressedId(CompressedFabricId fabricId)
{
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
continue;
}
if (fabricId == fabric.GetPeerId().GetCompressedFabricId())
{
return &fabric;
}
}
return nullptr;
}
CHIP_ERROR FabricTable::Store(FabricIndex index)
{
CHIP_ERROR err = CHIP_NO_ERROR;
FabricInfo * fabric = nullptr;
VerifyOrExit(mStorage != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
fabric = FindFabricWithIndex(index);
VerifyOrExit(fabric != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
err = fabric->CommitToStorage(mStorage);
exit:
if (err == CHIP_NO_ERROR && mDelegate != nullptr)
{
ChipLogProgress(Discovery, "Fabric (%d) persisted to storage. Calling OnFabricPersistedToStorage", index);
FabricTableDelegate * delegate = mDelegate;
while (delegate)
{
delegate->OnFabricPersistedToStorage(fabric);
delegate = delegate->mNext;
}
}
return err;
}
CHIP_ERROR FabricTable::LoadFromStorage(FabricInfo * fabric)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
if (!fabric->IsInitialized())
{
ReturnErrorOnFailure(fabric->LoadFromStorage(mStorage));
FabricTableDelegate * delegate = mDelegate;
while (delegate)
{
ChipLogProgress(Discovery, "Fabric (%d) loaded from storage", fabric->GetFabricIndex());
delegate->OnFabricRetrievedFromStorage(fabric);
delegate = delegate->mNext;
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::SetFabricInfo(FabricInfo & newFabric)
{
P256PublicKey pubkey;
ValidationContext validContext;
validContext.Reset();
validContext.mRequiredKeyUsages.Set(KeyUsageFlags::kDigitalSignature);
validContext.mRequiredKeyPurposes.Set(KeyPurposeFlags::kServerAuth);
SetOperationalKeypair(newFabric.GetOperationalKey());
SetRootCert(newFabric.mRootCert);
ChipLogProgress(Discovery, "Verifying the received credentials");
ReturnErrorOnFailure(
VerifyCredentials(newFabric.mNOCCert, newFabric.mICACert, validContext, mOperationalId, mFabricId, pubkey));
SetICACert(newFabric.mICACert);
SetNOCCert(newFabric.mNOCCert);
SetVendorId(newFabric.GetVendorId());
SetFabricLabel(newFabric.GetFabricLabel());
ChipLogProgress(Discovery, "Added new fabric at index: 0x%x, Initialized: %d", static_cast<unsigned>(GetFabricIndex()),
IsInitialized());
ChipLogProgress(Discovery, "Assigned compressed fabric ID: 0x" ChipLogFormatX64 ", node ID: 0x" ChipLogFormatX64,
ChipLogValueX64(mOperationalId.GetCompressedFabricId()), ChipLogValueX64(mOperationalId.GetNodeId()));
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::AddNewFabric(FabricInfo & newFabric, FabricIndex * outputIndex)
{
VerifyOrReturnError(outputIndex != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
// Check whether we already have a matching fabric. An incoming fabric does
// not have its fabric id set yet, so we have to extract it here to do the
// comparison.
FabricId fabricId;
{
ByteSpan noc;
ReturnErrorOnFailure(newFabric.GetNOCCert(noc));
NodeId unused;
ReturnErrorOnFailure(ExtractNodeIdFabricIdFromOpCert(noc, &unused, &fabricId));
}
for (auto & existingFabric : *this)
{
if (existingFabric.GetFabricId() == fabricId)
{
P256PublicKeySpan existingRootKey, newRootKey;
ReturnErrorOnFailure(existingFabric.GetRootPubkey(existingRootKey));
ReturnErrorOnFailure(newFabric.GetRootPubkey(newRootKey));
if (existingRootKey.data_equal(newRootKey))
{
return CHIP_ERROR_FABRIC_EXISTS;
}
}
}
if (!mNextAvailableFabricIndex.HasValue())
{
// No more indices available. Bail out.
return CHIP_ERROR_NO_MEMORY;
}
// Find an available slot.
for (auto & fabric : mStates)
{
if (!fabric.IsInitialized())
{
FabricIndex newFabricIndex = mNextAvailableFabricIndex.Value();
fabric.mFabricIndex = newFabricIndex;
CHIP_ERROR err = fabric.SetFabricInfo(newFabric);
if (err != CHIP_NO_ERROR)
{
fabric.Reset();
return err;
}
err = Store(newFabricIndex);
if (err != CHIP_NO_ERROR)
{
fabric.Reset();
FabricInfo::DeleteFromStorage(mStorage, newFabricIndex);
return err;
}
UpdateNextAvailableFabricIndex();
err = StoreFabricIndexInfo();
if (err != CHIP_NO_ERROR)
{
// Roll everything back.
mNextAvailableFabricIndex.SetValue(newFabricIndex);
fabric.Reset();
FabricInfo::DeleteFromStorage(mStorage, newFabricIndex);
}
else
{
*outputIndex = newFabricIndex;
mFabricCount++;
}
return err;
}
}
return CHIP_ERROR_NO_MEMORY;
}
CHIP_ERROR FabricTable::Delete(FabricIndex index)
{
VerifyOrReturnError(mStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
FabricInfo * fabric = FindFabricWithIndex(index);
bool fabricIsInitialized = fabric != nullptr && fabric->IsInitialized();
CompressedFabricId compressedFabricId =
fabricIsInitialized ? fabric->GetPeerId().GetCompressedFabricId() : kUndefinedCompressedFabricId;
CHIP_ERROR err = FabricInfo::DeleteFromStorage(mStorage, index); // Delete from storage regardless
if (!fabricIsInitialized)
{
// Make sure to return the error our API promises, not whatever storage
// chose to return.
return CHIP_ERROR_NOT_FOUND;
}
ReturnErrorOnFailure(err);
// Since fabricIsInitialized was true, fabric is not null.
fabric->Reset();
// If we ever start moving the FabricInfo entries around in the array on
// delete, we should update DeleteAllFabrics to handle that.
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(index);
}
// 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 (mDelegate != nullptr)
{
if (mFabricCount == 0)
{
ChipLogError(Discovery, "Trying to delete a fabric, but the current fabric count is already 0");
}
else
{
mFabricCount--;
ChipLogProgress(Discovery, "Fabric (0x%x) deleted. Calling OnFabricDeletedFromStorage", static_cast<unsigned>(index));
}
FabricTableDelegate * delegate = mDelegate;
while (delegate)
{
delegate->OnFabricDeletedFromStorage(compressedFabricId, index);
delegate = delegate->mNext;
}
}
return CHIP_NO_ERROR;
}
void FabricTable::DeleteAllFabrics()
{
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
for (auto & fabric : *this)
{
Delete(fabric.GetFabricIndex());
}
}
CHIP_ERROR FabricTable::Init(PersistentStorageDelegate * storage)
{
VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mStorage = storage;
ChipLogDetail(Discovery, "Init fabric pairing table with server storage");
// Load the current fabrics from the storage. This is done here, since ConstFabricIterator
// iterator doesn't have mechanism to load fabric info from storage on demand.
// TODO - Update ConstFabricIterator to load fabric info from storage
static_assert(kMaxValidFabricIndex <= UINT8_MAX, "Cannot create more fabrics than UINT8_MAX");
mFabricCount = 0;
for (auto & fabric : mStates)
{
fabric.Reset();
}
mNextAvailableFabricIndex.SetValue(kMinValidFabricIndex);
uint8_t buf[IndexInfoTLVMaxSize()];
uint16_t size = sizeof(buf);
DefaultStorageKeyAllocator keyAlloc;
CHIP_ERROR err = mStorage->SyncGetKeyValue(keyAlloc.FabricIndexInfo(), 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);
ReturnErrorOnFailure(ReadFabricInfo(reader));
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricTable::AddFabricDelegate(FabricTableDelegate * delegate)
{
VerifyOrReturnError(delegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
for (FabricTableDelegate * iter = mDelegate; iter != nullptr; iter = iter->mNext)
{
if (iter == delegate)
{
return CHIP_NO_ERROR;
}
}
delegate->mNext = mDelegate;
mDelegate = delegate;
ChipLogDetail(Discovery, "Add fabric pairing table delegate");
return CHIP_NO_ERROR;
}
namespace {
// Increment a fabric index in a way that ensures that it stays in the valid
// range [kMinValidFabricIndex, kMaxValidFabricIndex].
FabricIndex NextFabricIndex(FabricIndex index)
{
if (index == kMaxValidFabricIndex)
{
return kMinValidFabricIndex;
}
return static_cast<FabricIndex>(index + 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);
DefaultStorageKeyAllocator keyAlloc;
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(keyAlloc.FabricIndexInfo(), buf, static_cast<uint16_t>(indexInfoLength)));
return CHIP_NO_ERROR;
}
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];
ReturnErrorOnFailure(reader.Get(fabric.mFabricIndex));
err = LoadFromStorage(&fabric);
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());
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();
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR FabricInfo::TestOnlyBuildFabric(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan nodePubKey,
ByteSpan nodePrivateKey)
{
Reset();
ReturnErrorOnFailure(SetRootCert(rootCert));
ReturnErrorOnFailure(SetICACert(icacCert));
ReturnErrorOnFailure(SetNOCCert(nocCert));
// NOTE: this requres ENABLE_HSM_CASE_OPS_KEY is not defined
P256SerializedKeypair opKeysSerialized;
memcpy(static_cast<uint8_t *>(opKeysSerialized), nodePubKey.data(), nodePubKey.size());
memcpy(static_cast<uint8_t *>(opKeysSerialized) + nodePubKey.size(), nodePrivateKey.data(), nodePrivateKey.size());
ReturnErrorOnFailure(opKeysSerialized.SetLength(nodePubKey.size() + nodePrivateKey.size()));
P256Keypair opKey;
ReturnErrorOnFailure(opKey.Deserialize(opKeysSerialized));
ReturnErrorOnFailure(SetOperationalKeypair(&opKey));
// NOTE: mVendorId and mFabricLabel are not initialize, because they are not used in tests.
return CHIP_NO_ERROR;
}
} // namespace chip