blob: 7737e1a24cf5665e58a64a0d611e6212d251f3a2 [file]
/*
*
* Copyright (c) 2025 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.
*/
/****************************************************************************'
* @file
* @brief Implementation for the TlsCertificateManagement Server Cluster
***************************************************************************/
#include "TLSCertificateManagementCluster.h"
#include <app/ConcreteAttributePath.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <clusters/TlsCertificateManagement/Attributes.h>
#include <clusters/TlsCertificateManagement/Commands.h>
#include <clusters/TlsCertificateManagement/Metadata.h>
#include <clusters/TlsCertificateManagement/Structs.h>
#include <protocols/interaction_model/StatusCode.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Tls;
using namespace chip::app::Clusters::TlsCertificateManagement;
using namespace chip::app::Clusters::TlsCertificateManagement::Commands;
using namespace chip::app::Clusters::TlsCertificateManagement::Structs;
using namespace chip::app::Clusters::TlsCertificateManagement::Attributes;
using chip::Protocols::InteractionModel::Status;
static constexpr uint16_t kSpecMaxCertBytes = 3000;
static constexpr uint16_t kSpecMaxFingerprintBytes = 64;
static constexpr uint16_t kMaxIntermediateCertificates = 10;
static constexpr uint16_t kNonceBytes = 32;
static constexpr uint16_t kMaxSignatureBytes = 128;
constexpr DataModel::AcceptedCommandEntry kAcceptedCommands[] = {
Commands::ProvisionRootCertificate::kMetadataEntry,
Commands::FindRootCertificate::kMetadataEntry,
Commands::LookupRootCertificate::kMetadataEntry,
Commands::RemoveRootCertificate::kMetadataEntry,
Commands::ClientCSR::kMetadataEntry,
Commands::ProvisionClientCertificate::kMetadataEntry,
Commands::FindClientCertificate::kMetadataEntry,
Commands::LookupClientCertificate::kMetadataEntry,
Commands::RemoveClientCertificate::kMetadataEntry,
};
TLSCertificateManagementCluster::TLSCertificateManagementCluster(const Context & context, EndpointId endpointId,
TLSCertificateManagementDelegate & delegate,
Tls::CertificateDependencyChecker & dependencyChecker,
CertificateTable & certificateTable, uint8_t maxRootCertificates,
uint8_t maxClientCertificates) :
DefaultServerCluster(ConcreteClusterPath(endpointId, TlsCertificateManagement::Id)),
mContext(context), mDelegate(delegate), mDependencyChecker(dependencyChecker), mCertificateTable(certificateTable),
mMaxRootCertificates(maxRootCertificates), mMaxClientCertificates(maxClientCertificates)
{
VerifyOrDieWithMsg(mMaxRootCertificates >= 5, NotSpecified, "Spec requires MaxRootCertificates be >= 5");
VerifyOrDieWithMsg(mMaxClientCertificates >= 5, NotSpecified, "Spec requires MaxClientCertificates be >= 5");
mDelegate.SetTLSCertificateManagementCluster(this);
}
TLSCertificateManagementCluster::~TLSCertificateManagementCluster()
{
// null out the ref to us on the delegate
mDelegate.SetTLSCertificateManagementCluster(nullptr);
}
CHIP_ERROR TLSCertificateManagementCluster::Startup(ServerClusterContext & context)
{
ChipLogProgress(DataManagement, "TLSCertificateManagementCluster: initializing");
ReturnErrorOnFailure(DefaultServerCluster::Startup(context));
ReturnErrorOnFailure(mCertificateTable.Init(context.storage));
return mContext.fabricTable.AddFabricDelegate(this);
}
void TLSCertificateManagementCluster::Shutdown(ClusterShutdownType shutdownType)
{
ChipLogProgress(DataManagement, "TLSCertificateManagementCluster: shutdown");
mCertificateTable.Finish();
mContext.fabricTable.RemoveFabricDelegate(this);
DefaultServerCluster::Shutdown(shutdownType);
}
// DefaultServerCluster override
DataModel::ActionReturnStatus TLSCertificateManagementCluster::ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder)
{
TLSCertificateManagementCluster * server = this;
auto matterEndpoint = request.path.mEndpointId;
auto fabric = request.GetAccessingFabricIndex();
bool largePayload = request.readFlags.Has(DataModel::ReadFlags::kAllowsLargePayload);
switch (request.path.mAttributeId)
{
case MaxRootCertificates::Id:
return encoder.Encode(mMaxRootCertificates);
case ProvisionedRootCertificates::Id: {
return encoder.EncodeList([server, matterEndpoint, fabric, largePayload](const auto & listEncoder) -> CHIP_ERROR {
return server->EncodeProvisionedRootCertificates(matterEndpoint, fabric, largePayload, listEncoder);
});
}
case MaxClientCertificates::Id:
return encoder.Encode(mMaxClientCertificates);
case ProvisionedClientCertificates::Id: {
return encoder.EncodeList([server, matterEndpoint, fabric, largePayload](const auto & listEncoder) -> CHIP_ERROR {
return server->EncodeProvisionedClientCertificates(matterEndpoint, fabric, largePayload, listEncoder);
});
}
case ClusterRevision::Id:
return encoder.Encode(kRevision);
case FeatureMap::Id:
return encoder.Encode<uint32_t>(0);
default:
return Status::UnsupportedAttribute;
}
}
CHIP_ERROR
TLSCertificateManagementCluster::EncodeProvisionedRootCertificates(EndpointId matterEndpoint, FabricIndex fabric, bool largePayload,
const AttributeValueEncoder::ListEncodeHelper & encoder)
{
return mDelegate.LoadedRootCerts(matterEndpoint, fabric, [&](auto & cert) -> CHIP_ERROR {
if (largePayload)
{
return encoder.Encode(cert);
}
// Drop the certificate payload if transport doesn't support large payload
TLSCertStruct::Type idOnlyCert;
idOnlyCert.fabricIndex = cert.fabricIndex;
idOnlyCert.caid = cert.caid;
return encoder.Encode(idOnlyCert);
});
}
CHIP_ERROR
TLSCertificateManagementCluster::EncodeProvisionedClientCertificates(EndpointId matterEndpoint, FabricIndex fabric,
bool largePayload,
const AttributeValueEncoder::ListEncodeHelper & encoder)
{
return mDelegate.LoadedClientCerts(matterEndpoint, fabric, [&](auto & cert) -> CHIP_ERROR {
if (largePayload)
{
return encoder.Encode(cert);
}
// Drop the certificate payload if transport doesn't support large payload
TLSClientCertificateDetailStruct::Type idOnlyCert;
idOnlyCert.fabricIndex = cert.fabricIndex;
idOnlyCert.ccdid = cert.ccdid;
return encoder.Encode(idOnlyCert);
});
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::InvokeCommand(const DataModel::InvokeRequest & request, TLV::TLVReader & input_arguments,
CommandHandler * handler)
{
FabricIndex accessingFabricIndex = handler->GetAccessingFabricIndex();
switch (request.path.mCommandId)
{
case ProvisionRootCertificate::Id: {
ProvisionRootCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleProvisionRootCertificate(*handler, req);
}
case FindRootCertificate::Id: {
FindRootCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleFindRootCertificate(*handler, req);
}
case LookupRootCertificate::Id: {
LookupRootCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleLookupRootCertificate(*handler, req);
}
case RemoveRootCertificate::Id: {
RemoveRootCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleRemoveRootCertificate(*handler, req);
}
case ClientCSR::Id: {
ClientCSR::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleGenerateClientCsr(*handler, req);
}
case ProvisionClientCertificate::Id: {
ProvisionClientCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleProvisionClientCertificate(*handler, req);
}
case FindClientCertificate::Id: {
FindClientCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleFindClientCertificate(*handler, req);
}
case LookupClientCertificate::Id: {
LookupClientCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleLookupClientCertificate(*handler, req);
}
case RemoveClientCertificate::Id: {
RemoveClientCertificate::DecodableType req;
ReturnErrorOnFailure(req.Decode(input_arguments, accessingFabricIndex));
return HandleRemoveClientCertificate(*handler, req);
}
default:
return Status::UnsupportedCommand;
}
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleProvisionRootCertificate(CommandHandler & commandHandler,
const ProvisionRootCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: ProvisionRootCertificate");
VerifyOrReturnError(req.certificate.size() <= kSpecMaxCertBytes, Status::ConstraintError);
auto fabric = commandHandler.GetAccessingFabricIndex();
DataModel::Nullable<Tls::TLSCAID> foundId;
auto lookupResult = mDelegate.LookupRootCert(mPath.mEndpointId, fabric, req.certificate, [&](auto & certificate) -> CHIP_ERROR {
foundId = certificate.caid;
return CHIP_NO_ERROR;
});
VerifyOrReturnError(lookupResult == CHIP_ERROR_NOT_FOUND, Status::AlreadyExists);
if (req.caid.IsNull())
{
uint8_t numRootCerts;
VerifyOrReturnError(mCertificateTable.GetRootCertificateCount(fabric, numRootCerts) == CHIP_NO_ERROR, Status::Failure);
VerifyOrReturnError(numRootCerts < mMaxRootCertificates, Status::ResourceExhausted);
}
else
{
auto caid = req.caid.Value();
VerifyOrReturnError(caid <= kMaxRootCertId, Status::ConstraintError);
VerifyOrReturnError(mCertificateTable.HasRootCertificateEntry(fabric, caid) == CHIP_NO_ERROR, Status::NotFound);
}
#if CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME
if (Crypto::IsCertificateValidAtCurrentTime(req.certificate) != CHIP_NO_ERROR)
{
return Status::DynamicConstraintError;
}
#endif
ProvisionRootCertificateResponse::Type response;
auto status = mDelegate.ProvisionRootCert(mPath.mEndpointId, fabric, req, response.caid);
if (status != Status::Success)
{
return status;
}
VerifyOrDieWithMsg(response.caid <= kMaxRootCertId, NotSpecified, "Spec requires CAID to be <= kMaxRootCertId");
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id, Commands::ProvisionRootCertificate::Id);
commandHandler.AddResponse(responsePath, response);
NotifyAttributeChanged(TlsCertificateManagement::Attributes::ProvisionedRootCertificates::Id);
return std::nullopt;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleFindRootCertificate(CommandHandler & commandHandler,
const FindRootCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: FindRootCertificate");
CHIP_ERROR result;
if (req.caid.IsNull())
{
result = mDelegate.RootCertsForFabric(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(),
[&](auto & certs) -> CHIP_ERROR {
if (certs.size() == 0)
{
return CHIP_ERROR_NOT_FOUND;
}
FindRootCertificateResponse::Type response;
response.certificateDetails = certs;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id,
Commands::FindRootCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
}
else
{
VerifyOrReturnError(req.caid.Value() <= kMaxRootCertId, Status::ConstraintError);
result = mDelegate.FindRootCert(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.caid.Value(),
[&](auto & certificate) -> CHIP_ERROR {
FindRootCertificateResponse::Type response;
DataModel::List<const TLSCertStruct::Type> details(&certificate, 1);
response.certificateDetails = details;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id,
Commands::FindRootCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
}
if (result == CHIP_ERROR_NOT_FOUND)
{
return Status::NotFound;
}
VerifyOrReturnError(result == CHIP_NO_ERROR, Status::Failure);
return std::nullopt;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleLookupRootCertificate(CommandHandler & commandHandler,
const LookupRootCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: LookupRootCertificate");
VerifyOrReturnError(req.fingerprint.size() <= kSpecMaxFingerprintBytes, Status::ConstraintError);
auto result = mDelegate.LookupRootCertByFingerprint(
mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.fingerprint, [&](auto & certificate) -> CHIP_ERROR {
LookupRootCertificateResponse::Type response;
response.caid = certificate.caid;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id, Commands::LookupRootCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
if (result == CHIP_ERROR_NOT_FOUND)
{
return Status::NotFound;
}
VerifyOrReturnError(result == CHIP_NO_ERROR, Status::Failure);
return std::nullopt;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleRemoveRootCertificate(CommandHandler & commandHandler,
const RemoveRootCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: RemoveRootCertificate");
VerifyOrReturnError(req.caid <= kMaxRootCertId, Status::ConstraintError);
VerifyOrReturnError(mDependencyChecker.RootCertCanBeRemoved(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(),
req.caid) == CHIP_NO_ERROR,
Status::InvalidInState);
auto status = mDelegate.RemoveRootCert(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.caid);
if (status == Status::Success)
{
NotifyAttributeChanged(TlsCertificateManagement::Attributes::ProvisionedRootCertificates::Id);
}
return status;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleGenerateClientCsr(CommandHandler & commandHandler, const ClientCSR::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: ClientCSR");
VerifyOrReturnError(req.nonce.size() == kNonceBytes, Status::ConstraintError);
auto fabric = commandHandler.GetAccessingFabricIndex();
// If no CCDID is specified, ensure we have capacity for a new client certificate.
if (req.ccdid.IsNull())
{
uint8_t numClientCerts;
VerifyOrReturnError(mCertificateTable.GetClientCertificateCount(fabric, numClientCerts) == CHIP_NO_ERROR, Status::Failure);
VerifyOrReturnError(numClientCerts < mMaxClientCertificates, Status::ResourceExhausted);
}
auto status = mDelegate.GenerateClientCsr(mPath.mEndpointId, fabric, req, [&](auto & response) -> Status {
VerifyOrDieWithMsg(response.ccdid <= kMaxClientCertId, NotSpecified, "Spec requires CCDID to be <= kMaxClientCertId");
VerifyOrDieWithMsg(response.csr.size() <= 3000, NotSpecified, "Spec requires csr.size() to be <= 3000");
VerifyOrDieWithMsg(response.nonceSignature.size() <= kMaxSignatureBytes, NotSpecified,
"Spec requires nonceSignature.size() to be < kMaxSignatureBytes");
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id, Commands::ClientCSR::Id);
commandHandler.AddResponse(responsePath, response);
return Status::Success;
});
if (status == Status::Success)
{
return std::nullopt;
}
return status;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleProvisionClientCertificate(CommandHandler & commandHandler,
const ProvisionClientCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: ProvisionClientCertificate");
VerifyOrReturnError(req.ccdid <= kMaxClientCertId, Status::ConstraintError);
VerifyOrReturnError(req.clientCertificate.size() <= kSpecMaxCertBytes, Status::ConstraintError);
#if CHIP_SYSTEM_CONFIG_PLATFORM_PROVIDES_TIME
if (Crypto::IsCertificateValidAtCurrentTime(req.clientCertificate) != CHIP_NO_ERROR)
{
return Status::DynamicConstraintError;
}
#endif
size_t intermediateSize;
VerifyOrReturnError(req.intermediateCertificates.ComputeSize(&intermediateSize) == CHIP_NO_ERROR, Status::InvalidCommand);
VerifyOrReturnError(intermediateSize <= kMaxIntermediateCertificates, Status::ConstraintError);
auto srcIter = req.intermediateCertificates.begin();
while (srcIter.Next())
{
auto & cert = srcIter.GetValue();
VerifyOrReturnError(cert.size() <= kSpecMaxCertBytes, Status::ConstraintError);
VerifyOrReturnError(Crypto::IsCertificateValidAtCurrentTime(cert) == CHIP_NO_ERROR, Status::DynamicConstraintError);
}
VerifyOrReturnError(srcIter.GetStatus() == CHIP_NO_ERROR, Status::InvalidAction);
auto fabric = commandHandler.GetAccessingFabricIndex();
DataModel::Nullable<Tls::TLSCCDID> foundId;
auto lookupResult =
mDelegate.LookupClientCert(mPath.mEndpointId, fabric, req.clientCertificate, [&](auto & certificate) -> CHIP_ERROR {
foundId = certificate.ccdid;
return CHIP_NO_ERROR;
});
VerifyOrReturnError(lookupResult == CHIP_ERROR_NOT_FOUND, Status::AlreadyExists);
VerifyOrReturnError(mCertificateTable.HasClientCertificateEntry(fabric, req.ccdid) == CHIP_NO_ERROR, Status::NotFound);
auto status = mDelegate.ProvisionClientCert(mPath.mEndpointId, fabric, req);
if (status == Status::Success)
{
NotifyAttributeChanged(TlsCertificateManagement::Attributes::ProvisionedClientCertificates::Id);
}
return status;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleFindClientCertificate(CommandHandler & commandHandler,
const FindClientCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: FindClientCertificate");
CHIP_ERROR result;
if (req.ccdid.IsNull())
{
result = mDelegate.ClientCertsForFabric(
mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), [&](auto & certs) -> CHIP_ERROR {
VerifyOrReturnError(certs.size() > 0, CHIP_ERROR_NOT_FOUND);
FindClientCertificateResponse::Type response;
response.certificateDetails = certs;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id,
Commands::FindClientCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
}
else
{
VerifyOrReturnError(req.ccdid.Value() <= kMaxClientCertId, Status::ConstraintError);
result = mDelegate.FindClientCert(
mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.ccdid.Value(), [&](auto & certificate) -> CHIP_ERROR {
FindClientCertificateResponse::Type response;
DataModel::List<const TLSClientCertificateDetailStruct::Type> details(&certificate, 1);
response.certificateDetails = details;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id,
Commands::FindClientCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
}
if (result == CHIP_ERROR_NOT_FOUND)
{
return Status::NotFound;
}
VerifyOrReturnError(result == CHIP_NO_ERROR, Status::Failure);
return std::nullopt;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleLookupClientCertificate(CommandHandler & commandHandler,
const LookupClientCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: LookupClientCertificate");
VerifyOrReturnError(req.fingerprint.size() <= kSpecMaxFingerprintBytes, Status::ConstraintError);
auto result = mDelegate.LookupClientCertByFingerprint(
mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.fingerprint, [&](auto & certificate) -> CHIP_ERROR {
LookupClientCertificateResponse::Type response;
response.ccdid = certificate.ccdid;
ConcreteCommandPath responsePath(mPath.mEndpointId, TlsCertificateManagement::Id,
Commands::LookupClientCertificate::Id);
commandHandler.AddResponse(responsePath, response);
return CHIP_NO_ERROR;
});
if (result == CHIP_ERROR_NOT_FOUND)
{
return Status::NotFound;
}
VerifyOrReturnError(result == CHIP_NO_ERROR, Status::Failure);
return std::nullopt;
}
std::optional<DataModel::ActionReturnStatus>
TLSCertificateManagementCluster::HandleRemoveClientCertificate(CommandHandler & commandHandler,
const RemoveClientCertificate::DecodableType & req)
{
ChipLogDetail(Zcl, "TlsCertificateManagement: RemoveClientCertificate");
VerifyOrReturnError(req.ccdid <= kMaxClientCertId, Status::ConstraintError);
VerifyOrReturnError(mDependencyChecker.ClientCertCanBeRemoved(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(),
req.ccdid) == CHIP_NO_ERROR,
Status::InvalidInState);
auto status = mDelegate.RemoveClientCert(mPath.mEndpointId, commandHandler.GetAccessingFabricIndex(), req.ccdid);
if (status == Status::Success)
{
NotifyAttributeChanged(TlsCertificateManagement::Attributes::ProvisionedClientCertificates::Id);
}
return status;
}
CHIP_ERROR TLSCertificateManagementCluster::AcceptedCommands(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder)
{
return builder.ReferenceExisting(kAcceptedCommands);
}
CHIP_ERROR TLSCertificateManagementCluster::Attributes(const ConcreteClusterPath & path,
ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder)
{
AttributeListBuilder listBuilder(builder);
// TlsCertificateManagement does not have optional attributes implemented yet,
// so we just return mandatory ones.
return listBuilder.Append(Span(kMandatoryMetadata), {});
}
void TLSCertificateManagementCluster::OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex)
{
ReturnAndLogOnFailure(mCertificateTable.RemoveFabric(fabricIndex), Zcl, "Failed to remove TLS certificate data for fabric 0x%x",
fabricIndex);
}