blob: 389d9e534f00541dd45e08ceaf03f569851c428d [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 <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/CHIPSafeCasts.h>
#include <lib/core/Optional.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/Span.h>
#ifdef ENABLE_HSM_CASE_OPS_KEY
#define CASE_OPS_KEY 0xCA5EECC0
#endif
namespace chip {
static constexpr FabricIndex kMinValidFabricIndex = 1;
static constexpr FabricIndex kMaxValidFabricIndex = std::min(UINT8_MAX, CHIP_CONFIG_MAX_DEVICE_ADMINS);
static constexpr uint8_t kFabricLabelMaxLengthInBytes = 32;
// KVS store is sensitive to length of key strings, based on the underlying
// platform. Keeping them short.
constexpr char kFabricTableKeyPrefix[] = "Fabric";
constexpr char kFabricTableCountKey[] = "NumFabrics";
class DLL_EXPORT FabricStorage
{
public:
virtual ~FabricStorage() {}
/**
* Gets called when fabric data needs to be stored.
**/
virtual CHIP_ERROR SyncStore(FabricIndex fabricIndex, const char * key, const void * buffer, uint16_t size) = 0;
/**
* Gets called when fabric data needs to be loaded.
**/
virtual CHIP_ERROR SyncLoad(FabricIndex fabricIndex, const char * key, void * buffer, uint16_t & size) = 0;
/**
* Gets called when fabric data needs to be removed.
**/
virtual CHIP_ERROR SyncDelete(FabricIndex fabricIndex, const char * key) = 0;
};
/**
* @brief A default implementation of Fabric storage that preserves legacy behavior of using
* the Persistent storage delegate directly.
*
* This class automatically prefixes the Fabric Storage Keys with the FabricIndex.
* The keys are formatted like so: "F%02X/" + key.
*
*/
class DLL_EXPORT SimpleFabricStorage : public FabricStorage
{
public:
SimpleFabricStorage(){};
SimpleFabricStorage(PersistentStorageDelegate * storage) : mStorage(storage){};
~SimpleFabricStorage() override { mStorage = nullptr; };
CHIP_ERROR Initialize(PersistentStorageDelegate * storage)
{
VerifyOrReturnError(mStorage == nullptr || storage == mStorage, CHIP_ERROR_INCORRECT_STATE);
mStorage = storage;
return CHIP_NO_ERROR;
}
CHIP_ERROR SyncStore(FabricIndex fabricIndex, const char * key, const void * buffer, uint16_t size) override;
CHIP_ERROR SyncLoad(FabricIndex fabricIndex, const char * key, void * buffer, uint16_t & size) override;
CHIP_ERROR SyncDelete(FabricIndex fabricIndex, const char * key) override;
private:
const static int MAX_KEY_SIZE = 32;
PersistentStorageDelegate * mStorage = nullptr;
};
/**
* 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();
mFabric = kUndefinedFabricIndex;
}
// 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();
}
PeerId GetPeerId() const { return mOperationalId; }
FabricId GetFabricId() const { return mFabricId; }
FabricIndex GetFabricIndex() const { return mFabric; }
uint16_t GetVendorId() const { return mVendorId; }
void SetVendorId(uint16_t vendorId) { mVendorId = vendorId; }
Crypto::P256Keypair * GetOperationalKey()
{
if (mOperationalKey == nullptr)
{
#ifdef ENABLE_HSM_CASE_OPS_KEY
mOperationalKey = chip::Platform::New<Crypto::P256KeypairHSM>();
mOperationalKey->SetKeyId(CASE_OPS_KEY);
#else
mOperationalKey = chip::Platform::New<Crypto::P256Keypair>();
#endif
mOperationalKey->Initialize();
}
return mOperationalKey;
}
CHIP_ERROR SetEphemeralKey(const Crypto::P256Keypair * key);
// 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()); }
CHIP_ERROR GenerateDestinationID(const ByteSpan & ipk, const ByteSpan & random, NodeId destNodeId,
MutableByteSpan & destinationId);
CHIP_ERROR MatchDestinationID(const ByteSpan & destinationId, const ByteSpan & initiatorRandom, const ByteSpan * ipkList,
size_t ipkListEntries);
// 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)
{
cert = mICACert;
return CHIP_NO_ERROR;
}
CHIP_ERROR GetNOCCert(ByteSpan & cert)
{
ReturnErrorCodeIf(mNOCCert.empty(), CHIP_ERROR_INCORRECT_STATE);
cert = mNOCCert;
return CHIP_NO_ERROR;
}
Credentials::CertificateKeyId GetTrustedRootId() const
{
Credentials::CertificateKeyId skid;
Credentials::ExtractSKIDFromChipCert(mRootCert, skid);
return skid;
}
Credentials::P256PublicKeySpan GetRootPubkey() const
{
Credentials::P256PublicKeySpan publicKey;
Credentials::ExtractPublicKeyFromChipCert(mRootCert, publicKey);
return 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 = kUndefinedVendorId;
mFabricLabel[0] = '\0';
if (mOperationalKey != nullptr)
{
chip::Platform::Delete(mOperationalKey);
mOperationalKey = nullptr;
}
ReleaseOperationalCerts();
}
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 GetCompressedId(FabricId fabricId, NodeId nodeId, PeerId * compressedPeerId) const;
friend class FabricTable;
private:
PeerId mOperationalId;
FabricIndex mFabric = kUndefinedFabricIndex;
uint16_t mVendorId = kUndefinedVendorId;
char mFabricLabel[kFabricLabelMaxLengthInBytes + 1] = { '\0' };
#ifdef ENABLE_HSM_CASE_OPS_KEY
Crypto::P256KeypairHSM * mOperationalKey = nullptr;
#else
Crypto::P256Keypair * mOperationalKey = nullptr;
#endif
MutableByteSpan mRootCert;
MutableByteSpan mICACert;
MutableByteSpan mNOCCert;
FabricId mFabricId = 0;
static constexpr size_t kKeySize = sizeof(kFabricTableKeyPrefix) + 2 * sizeof(FabricIndex);
static CHIP_ERROR GenerateKey(FabricIndex id, char * key, size_t len);
CHIP_ERROR CommitToStorage(FabricStorage * storage);
CHIP_ERROR LoadFromStorage(FabricStorage * storage);
static CHIP_ERROR DeleteFromStorage(FabricStorage * 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
{
uint16_t mFabric; /* This field is serialized in LittleEndian byte order */
uint64_t mNodeId; /* This field is serialized in LittleEndian byte order */
uint64_t mFabricId; /* This field is serialized in LittleEndian byte order */
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
{
public:
virtual ~FabricTableDelegate() {}
/**
* Gets called when a fabric is deleted from KVS store.
**/
virtual void OnFabricDeletedFromStorage(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;
};
/**
* 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() { Reset(); }
CHIP_ERROR Store(FabricIndex id);
CHIP_ERROR LoadFromStorage(FabricInfo * info);
CHIP_ERROR Delete(FabricIndex id);
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.
*/
CHIP_ERROR AddNewFabric(FabricInfo & fabric, FabricIndex * assignedIndex);
void ReleaseFabricIndex(FabricIndex fabricIndex);
FabricInfo * FindFabricWithIndex(FabricIndex fabricIndex);
FabricInfo * FindFabricWithCompressedId(CompressedFabricId fabricId);
FabricIndex FindDestinationIDCandidate(const ByteSpan & destinationId, const ByteSpan & initiatorRandom,
const ByteSpan * ipkList, size_t ipkListEntries);
void Reset();
CHIP_ERROR Init(FabricStorage * storage);
CHIP_ERROR SetFabricDelegate(FabricTableDelegate * delegate);
uint8_t FabricCount() const { return mFabricCount; }
ConstFabricIterator cbegin() const { return ConstFabricIterator(mStates, 0, CHIP_CONFIG_MAX_DEVICE_ADMINS); }
ConstFabricIterator cend() const
{
return ConstFabricIterator(mStates, CHIP_CONFIG_MAX_DEVICE_ADMINS, CHIP_CONFIG_MAX_DEVICE_ADMINS);
}
ConstFabricIterator begin() const { return cbegin(); }
ConstFabricIterator end() const { return cend(); }
private:
FabricInfo mStates[CHIP_CONFIG_MAX_DEVICE_ADMINS];
FabricStorage * mStorage = nullptr;
// TODO: Fabric table should be backed by a single backing store (attribute store), remove delegate callbacks #6419
FabricTableDelegate * mDelegate = nullptr;
FabricIndex mNextAvailableFabricIndex = kMinValidFabricIndex;
uint8_t mFabricCount = 0;
};
} // namespace chip