blob: 26bbaab0687858b9855695c727bcfacc3b9cb830 [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.
*/
#pragma once
#include <algorithm>
#include <app/util/basic-types.h>
#include <credentials/CHIPCert.h>
#include <crypto/CHIPCryptoPAL.h>
#include <lib/core/CHIPPersistentStorageDelegate.h>
#if CHIP_CRYPTO_HSM
#include <crypto/hsm/CHIPCryptoPALHsm.h>
#endif
#include <lib/core/CHIPEncoding.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/CHIPTLV.h>
#include <lib/core/Optional.h>
#include <lib/core/ScopedNodeId.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/Span.h>
namespace chip {
static constexpr FabricIndex kMinValidFabricIndex = 1;
static constexpr FabricIndex kMaxValidFabricIndex = UINT8_MAX - 1;
static_assert(kMinValidFabricIndex <= CHIP_CONFIG_MAX_FABRICS, "Must support some fabrics.");
static_assert(CHIP_CONFIG_MAX_FABRICS <= kMaxValidFabricIndex, "Max fabric count out of range.");
static constexpr uint8_t kFabricLabelMaxLengthInBytes = 32;
static_assert(kUndefinedFabricIndex < chip::kMinValidFabricIndex, "Undefined fabric index should not be valid");
/**
* Defines state of a pairing established by a fabric.
* Node ID is only settable using the device operational credentials.
*
* Information contained within the state:
* - Fabric identification
* - Node Id assigned by the fabric to the device
* - Vendor Id
* - Fabric Id
* - Device operational credentials
*/
class DLL_EXPORT FabricInfo
{
public:
FabricInfo() { Reset(); }
// Returns a span into our internal storage.
CharSpan GetFabricLabel() const { return CharSpan(mFabricLabel, strnlen(mFabricLabel, kFabricLabelMaxLengthInBytes)); }
CHIP_ERROR SetFabricLabel(const CharSpan & fabricLabel);
~FabricInfo()
{
if (mOperationalKey != nullptr)
{
chip::Platform::Delete(mOperationalKey);
}
ReleaseOperationalCerts();
}
NodeId GetNodeId() const { return mOperationalId.GetNodeId(); }
ScopedNodeId GetScopedNodeId() const { return ScopedNodeId(mOperationalId.GetNodeId(), mFabricIndex); }
ScopedNodeId GetScopedNodeIdForNode(const NodeId node) const { return ScopedNodeId(node, mFabricIndex); }
// TODO(#15049): Refactor/rename PeerId to OperationalId or OpId throughout source
PeerId GetPeerId() const { return mOperationalId; }
PeerId GetPeerIdForNode(const NodeId node) const
{
PeerId peer = mOperationalId;
peer.SetNodeId(node);
return peer;
}
FabricId GetFabricId() const { return mFabricId; }
FabricIndex GetFabricIndex() const { return mFabricIndex; }
CompressedFabricId GetCompressedId() const { return mOperationalId.GetCompressedFabricId(); }
CHIP_ERROR GetCompressedId(MutableByteSpan & compressedFabricId) const
{
ReturnErrorCodeIf(compressedFabricId.size() != sizeof(uint64_t), CHIP_ERROR_INVALID_ARGUMENT);
Encoding::BigEndian::Put64(compressedFabricId.data(), GetCompressedId());
return CHIP_NO_ERROR;
}
uint16_t GetVendorId() const { return mVendorId; }
void SetVendorId(uint16_t vendorId) { mVendorId = vendorId; }
Crypto::P256Keypair * GetOperationalKey() const
{
if (mOperationalKey == nullptr)
{
#ifdef ENABLE_HSM_CASE_OPS_KEY
mOperationalKey = chip::Platform::New<Crypto::P256KeypairHSM>();
mOperationalKey->CreateOperationalKey(mFabricIndex);
#else
mOperationalKey = chip::Platform::New<Crypto::P256Keypair>();
mOperationalKey->Initialize();
#endif
}
return mOperationalKey;
}
CHIP_ERROR SetOperationalKeypair(const Crypto::P256Keypair * keyPair);
// TODO - Update these APIs to take ownership of the buffer, instead of copying
// internally.
// TODO - Optimize persistent storage of NOC and Root Cert in FabricInfo.
CHIP_ERROR SetRootCert(const chip::ByteSpan & cert) { return SetCert(mRootCert, cert); }
CHIP_ERROR SetICACert(const chip::ByteSpan & cert) { return SetCert(mICACert, cert); }
CHIP_ERROR SetICACert(const Optional<ByteSpan> & cert) { return SetICACert(cert.ValueOr(ByteSpan())); }
CHIP_ERROR SetNOCCert(const chip::ByteSpan & cert) { return SetCert(mNOCCert, cert); }
bool IsInitialized() const { return IsOperationalNodeId(mOperationalId.GetNodeId()); }
// TODO - Refactor storing and loading of fabric info from persistent storage.
// The op cert array doesn't need to be in RAM except when it's being
// transmitted to peer node during CASE session setup.
CHIP_ERROR GetRootCert(ByteSpan & cert) const
{
ReturnErrorCodeIf(mRootCert.empty(), CHIP_ERROR_INCORRECT_STATE);
cert = mRootCert;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetICACert(ByteSpan & cert) const
{
cert = mICACert;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetNOCCert(ByteSpan & cert) const
{
ReturnErrorCodeIf(mNOCCert.empty(), CHIP_ERROR_INCORRECT_STATE);
cert = mNOCCert;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetTrustedRootId(Credentials::CertificateKeyId & skid) const
{
return Credentials::ExtractSKIDFromChipCert(mRootCert, skid);
}
CHIP_ERROR GetRootPubkey(Credentials::P256PublicKeySpan & publicKey) const
{
return Credentials::ExtractPublicKeyFromChipCert(mRootCert, publicKey);
}
CHIP_ERROR VerifyCredentials(const ByteSpan & noc, const ByteSpan & icac, Credentials::ValidationContext & context,
PeerId & nocPeerId, FabricId & fabricId, Crypto::P256PublicKey & nocPubkey) const;
/**
* Reset the state to a completely uninitialized status.
*/
void Reset()
{
mOperationalId = PeerId();
mVendorId = VendorId::NotSpecified;
mFabricLabel[0] = '\0';
if (mOperationalKey != nullptr)
{
chip::Platform::Delete(mOperationalKey);
mOperationalKey = nullptr;
}
ReleaseOperationalCerts();
mFabricIndex = kUndefinedFabricIndex;
}
CHIP_ERROR SetFabricInfo(FabricInfo & fabric);
/* Generate a compressed peer ID (containing compressed fabric ID) using provided fabric ID, node ID and
root public key of the fabric. The generated compressed ID is returned via compressedPeerId
output parameter */
CHIP_ERROR GeneratePeerId(FabricId fabricId, NodeId nodeId, PeerId * compressedPeerId) const;
friend class FabricTable;
// Test-only, build a fabric using given root cert and NOC
CHIP_ERROR TestOnlyBuildFabric(ByteSpan rootCert, ByteSpan icacCert, ByteSpan nocCert, ByteSpan nodePubKey,
ByteSpan nodePrivateKey);
private:
static constexpr size_t MetadataTLVMaxSize()
{
return TLV::EstimateStructOverhead(sizeof(VendorId), kFabricLabelMaxLengthInBytes);
}
static constexpr size_t OpKeyTLVMaxSize()
{
return TLV::EstimateStructOverhead(sizeof(uint16_t), Crypto::P256SerializedKeypair::Capacity());
}
PeerId mOperationalId;
FabricIndex mFabricIndex = kUndefinedFabricIndex;
uint16_t mVendorId = VendorId::NotSpecified;
char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' };
#ifdef ENABLE_HSM_CASE_OPS_KEY
mutable Crypto::P256KeypairHSM * mOperationalKey = nullptr;
#else
mutable Crypto::P256Keypair * mOperationalKey = nullptr;
#endif
MutableByteSpan mRootCert;
MutableByteSpan mICACert;
MutableByteSpan mNOCCert;
FabricId mFabricId = 0;
CHIP_ERROR CommitToStorage(PersistentStorageDelegate * storage);
CHIP_ERROR LoadFromStorage(PersistentStorageDelegate * storage);
static CHIP_ERROR DeleteFromStorage(PersistentStorageDelegate * storage, FabricIndex fabricIndex);
void ReleaseCert(MutableByteSpan & cert);
void ReleaseOperationalCerts()
{
ReleaseCert(mRootCert);
ReleaseCert(mICACert);
ReleaseCert(mNOCCert);
}
CHIP_ERROR SetCert(MutableByteSpan & dstCert, const ByteSpan & srcCert);
struct StorableFabricInfo
{
uint8_t mFabricIndex;
uint16_t mVendorId; /* This field is serialized in LittleEndian byte order */
uint16_t mRootCertLen; /* This field is serialized in LittleEndian byte order */
uint16_t mICACertLen; /* This field is serialized in LittleEndian byte order */
uint16_t mNOCCertLen; /* This field is serialized in LittleEndian byte order */
Crypto::P256SerializedKeypair mOperationalKey;
uint8_t mRootCert[Credentials::kMaxCHIPCertLength];
uint8_t mICACert[Credentials::kMaxCHIPCertLength];
uint8_t mNOCCert[Credentials::kMaxCHIPCertLength];
char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' };
};
};
// Once attribute store has persistence implemented, FabricTable shoud be backed using
// attribute store so no need for this Delegate API anymore
// TODO: Reimplement FabricTable to only have one backing store.
class DLL_EXPORT FabricTableDelegate
{
friend class FabricTable;
public:
FabricTableDelegate(bool ownedByFabricTable = false) : mOwnedByFabricTable(ownedByFabricTable) {}
virtual ~FabricTableDelegate() {}
/**
* Gets called when a fabric is deleted from KVS store.
**/
virtual void OnFabricDeletedFromStorage(CompressedFabricId compressedId, FabricIndex fabricIndex) = 0;
/**
* Gets called when a fabric is loaded into Fabric Table from KVS store.
**/
virtual void OnFabricRetrievedFromStorage(FabricInfo * fabricInfo) = 0;
/**
* Gets called when a fabric in Fabric Table is persisted to KVS store.
**/
virtual void OnFabricPersistedToStorage(FabricInfo * fabricInfo) = 0;
private:
FabricTableDelegate * mNext = nullptr;
bool mOwnedByFabricTable = false;
};
/**
* Iterates over valid fabrics within a list
*/
class ConstFabricIterator
{
public:
using value_type = FabricInfo;
using pointer = FabricInfo *;
using reference = FabricInfo &;
ConstFabricIterator(const FabricInfo * start, size_t index, size_t maxSize) : mStart(start), mIndex(index), mMaxSize(maxSize)
{
if (mIndex >= maxSize)
{
mIndex = maxSize;
}
else if (!mStart[mIndex].IsInitialized())
{
Advance();
}
}
ConstFabricIterator(const ConstFabricIterator &) = default;
ConstFabricIterator & operator=(const ConstFabricIterator &) = default;
ConstFabricIterator & operator++() { return Advance(); }
ConstFabricIterator operator++(int)
{
ConstFabricIterator other(*this);
Advance();
return other;
}
const FabricInfo & operator*() const { return mStart[mIndex]; }
const FabricInfo * operator->() const { return mStart + mIndex; }
bool operator==(const ConstFabricIterator & other)
{
if (IsAtEnd())
{
return other.IsAtEnd();
}
return (mStart == other.mStart) && (mIndex == other.mIndex) && (mMaxSize == other.mMaxSize);
}
bool operator!=(const ConstFabricIterator & other) { return !(*this == other); }
bool IsAtEnd() const { return (mIndex == mMaxSize); }
private:
const FabricInfo * mStart;
size_t mIndex;
size_t mMaxSize;
ConstFabricIterator & Advance()
{
do
{
if (mIndex < mMaxSize)
{
mIndex++;
}
} while (!IsAtEnd() && !mStart[mIndex].IsInitialized());
return *this;
}
};
class DLL_EXPORT FabricTable
{
public:
FabricTable() {}
~FabricTable();
CHIP_ERROR Store(FabricIndex index);
CHIP_ERROR LoadFromStorage(FabricInfo * info);
// Returns CHIP_ERROR_NOT_FOUND if there is no fabric for that index.
CHIP_ERROR Delete(FabricIndex index);
void DeleteAllFabrics();
/**
* Add the new fabric information to fabric table if the table has space to store
* more fabrics. CHIP_ERROR_NO_MEMORY error will be returned if the table is full.
*
* The provided information will get copied to internal data structures, and the caller
* can release the memory associated with input parameter after the call is complete.
*
* If the call is successful, the assigned fabric index is returned as output parameter.
* The fabric information will also be persisted to storage.
*/
CHIP_ERROR AddNewFabric(FabricInfo & fabric, FabricIndex * assignedIndex);
FabricInfo * FindFabric(Credentials::P256PublicKeySpan rootPubKey, FabricId fabricId);
FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex);
FabricInfo * FindFabricWithCompressedId(CompressedFabricId fabricId);
CHIP_ERROR Init(PersistentStorageDelegate * storage);
CHIP_ERROR AddFabricDelegate(FabricTableDelegate * delegate);
uint8_t FabricCount() const { return mFabricCount; }
ConstFabricIterator cbegin() const { return ConstFabricIterator(mStates, 0, CHIP_CONFIG_MAX_FABRICS); }
ConstFabricIterator cend() const { return ConstFabricIterator(mStates, CHIP_CONFIG_MAX_FABRICS, CHIP_CONFIG_MAX_FABRICS); }
ConstFabricIterator begin() const { return cbegin(); }
ConstFabricIterator end() const { return cend(); }
private:
static 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);
}
/**
* UpdateNextAvailableFabricIndex should only be called when
* mNextAvailableFabricIndex has a value and that value stops being
* available. It will set mNextAvailableFabricIndex to the next available
* value, or no value if there is none available.
*/
void UpdateNextAvailableFabricIndex();
/**
* Store our current fabric index state: what our next available index is
* and what indices we're using right now.
*/
CHIP_ERROR StoreFabricIndexInfo() const;
/**
* Read our fabric index info from the given TLV reader and set up the
* fabric table accordingly.
*/
CHIP_ERROR ReadFabricInfo(TLV::ContiguousBufferTLVReader & reader);
FabricInfo mStates[CHIP_CONFIG_MAX_FABRICS];
PersistentStorageDelegate * mStorage = nullptr;
FabricTableDelegate * mDelegate = nullptr;
// We may not have an mNextAvailableFabricIndex if our table is as large as
// it can go and is full.
Optional<FabricIndex> mNextAvailableFabricIndex;
uint8_t mFabricCount = 0;
};
} // namespace chip