blob: 7b6bf7f5009b9ce778d7e773bc1c6fbf82e1a0e7 [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.
*/
/****************************************************************************
* @file
* @brief Implementation for the Operational Credentials Cluster
***************************************************************************/
#include <access/AccessControl.h>
#include <app-common/zap-generated/af-structs.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/enums.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/EventLogging.h>
#include <app/InteractionModelEngine.h>
#include <app/reporting/reporting.h>
#include <app/server/Dnssd.h>
#include <app/server/Server.h>
#include <app/util/af.h>
#include <app/util/attribute-storage.h>
#include <credentials/CHIPCert.h>
#include <credentials/CertificationDeclaration.h>
#include <credentials/DeviceAttestationConstructor.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/FabricTable.h>
#include <credentials/GroupDataProvider.h>
#include <lib/core/CHIPSafeCasts.h>
#include <lib/core/PeerId.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/CHIPDeviceLayer.h>
#include <string.h>
#include <trace/trace.h>
using namespace chip;
using namespace ::chip::Transport;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::OperationalCredentials;
using namespace chip::Credentials;
using namespace chip::Protocols::InteractionModel;
namespace {
void SendNOCResponse(app::CommandHandler * commandObj, const ConcreteCommandPath & path, OperationalCertStatus status,
uint8_t index, const CharSpan & debug_text);
OperationalCertStatus ConvertToNOCResponseStatus(CHIP_ERROR err);
constexpr uint8_t kDACCertificate = 1;
constexpr uint8_t kPAICertificate = 2;
CHIP_ERROR CreateAccessControlEntryForNewFabricAdministrator(const Access::SubjectDescriptor & subjectDescriptor,
FabricIndex fabricIndex, uint64_t subject)
{
NodeId subjectAsNodeID = static_cast<NodeId>(subject);
if (!IsOperationalNodeId(subjectAsNodeID) && !IsCASEAuthTag(subjectAsNodeID))
{
return CHIP_ERROR_INVALID_ADMIN_SUBJECT;
}
Access::AccessControl::Entry entry;
ReturnErrorOnFailure(Access::GetAccessControl().PrepareEntry(entry));
ReturnErrorOnFailure(entry.SetFabricIndex(fabricIndex));
ReturnErrorOnFailure(entry.SetPrivilege(Access::Privilege::kAdminister));
ReturnErrorOnFailure(entry.SetAuthMode(Access::AuthMode::kCase));
ReturnErrorOnFailure(entry.AddSubject(nullptr, subject));
CHIP_ERROR err = Access::GetAccessControl().CreateEntry(&subjectDescriptor, fabricIndex, nullptr, entry);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "OpCreds: Failed to add administrative node ACL entry: %" CHIP_ERROR_FORMAT, err.Format());
return err;
}
ChipLogProgress(Zcl, "OpCreds: ACL entry created for Fabric index 0x%x CASE Admin Subject 0x" ChipLogFormatX64,
static_cast<unsigned>(fabricIndex), ChipLogValueX64(subject));
return CHIP_NO_ERROR;
}
class OperationalCredentialsAttrAccess : public AttributeAccessInterface
{
public:
// Register for the OperationalCredentials cluster on all endpoints.
OperationalCredentialsAttrAccess() :
AttributeAccessInterface(Optional<EndpointId>::Missing(), Clusters::OperationalCredentials::Id)
{}
CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
private:
CHIP_ERROR ReadNOCs(EndpointId endpoint, AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadSupportedFabrics(EndpointId endpoint, AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadCommissionedFabrics(EndpointId endpoint, AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadFabricsList(EndpointId endpoint, AttributeValueEncoder & aEncoder);
CHIP_ERROR ReadRootCertificates(EndpointId endpoint, AttributeValueEncoder & aEncoder);
};
CHIP_ERROR OperationalCredentialsAttrAccess::ReadNOCs(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
auto accessingFabricIndex = aEncoder.AccessingFabricIndex();
return aEncoder.EncodeList([accessingFabricIndex](const auto & encoder) -> CHIP_ERROR {
const auto & fabricTable = Server::GetInstance().GetFabricTable();
for (const auto & fabricInfo : fabricTable)
{
Clusters::OperationalCredentials::Structs::NOCStruct::Type noc;
uint8_t nocBuf[kMaxCHIPCertLength];
uint8_t icacBuf[kMaxCHIPCertLength];
MutableByteSpan nocSpan{ nocBuf };
MutableByteSpan icacSpan{ icacBuf };
FabricIndex fabricIndex = fabricInfo.GetFabricIndex();
noc.fabricIndex = fabricIndex;
if (accessingFabricIndex == fabricIndex)
{
ReturnErrorOnFailure(fabricTable.FetchNOCCert(fabricIndex, nocSpan));
ReturnErrorOnFailure(fabricTable.FetchICACert(fabricIndex, icacSpan));
noc.noc = nocSpan;
if (!icacSpan.empty())
{
noc.icac.SetNonNull(icacSpan);
}
}
ReturnErrorOnFailure(encoder.Encode(noc));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR OperationalCredentialsAttrAccess::ReadSupportedFabrics(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
uint8_t fabricCount = CHIP_CONFIG_MAX_FABRICS;
return aEncoder.Encode(fabricCount);
}
CHIP_ERROR OperationalCredentialsAttrAccess::ReadCommissionedFabrics(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
return aEncoder.Encode(Server::GetInstance().GetFabricTable().FabricCount());
}
CHIP_ERROR OperationalCredentialsAttrAccess::ReadFabricsList(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
return aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR {
const auto & fabricTable = Server::GetInstance().GetFabricTable();
for (const auto & fabricInfo : fabricTable)
{
Clusters::OperationalCredentials::Structs::FabricDescriptor::Type fabricDescriptor;
FabricIndex fabricIndex = fabricInfo.GetFabricIndex();
fabricDescriptor.fabricIndex = fabricIndex;
fabricDescriptor.nodeId = fabricInfo.GetPeerId().GetNodeId();
fabricDescriptor.vendorId = fabricInfo.GetVendorId();
fabricDescriptor.fabricId = fabricInfo.GetFabricId();
fabricDescriptor.label = fabricInfo.GetFabricLabel();
Crypto::P256PublicKey pubKey;
ReturnErrorOnFailure(fabricTable.FetchRootPubkey(fabricIndex, pubKey));
fabricDescriptor.rootPublicKey = ByteSpan{ pubKey.ConstBytes(), pubKey.Length() };
ReturnErrorOnFailure(encoder.Encode(fabricDescriptor));
}
return CHIP_NO_ERROR;
});
}
CHIP_ERROR OperationalCredentialsAttrAccess::ReadRootCertificates(EndpointId endpoint, AttributeValueEncoder & aEncoder)
{
// It is OK to have duplicates.
return aEncoder.EncodeList([](const auto & encoder) -> CHIP_ERROR {
const auto & fabricTable = Server::GetInstance().GetFabricTable();
for (const auto & fabricInfo : fabricTable)
{
uint8_t certBuf[kMaxCHIPCertLength];
MutableByteSpan cert{ certBuf };
ReturnErrorOnFailure(fabricTable.FetchRootCert(fabricInfo.GetFabricIndex(), cert));
ReturnErrorOnFailure(encoder.Encode(ByteSpan{ cert }));
}
{
uint8_t certBuf[kMaxCHIPCertLength];
MutableByteSpan cert{ certBuf };
CHIP_ERROR err = fabricTable.FetchPendingNonFabricAssociatedRootCert(cert);
if (err == CHIP_ERROR_NOT_FOUND)
{
// No pending root cert, do nothing
}
else if (err != CHIP_NO_ERROR)
{
return err;
}
else
{
ReturnErrorOnFailure(encoder.Encode(ByteSpan{ cert }));
}
}
return CHIP_NO_ERROR;
});
}
OperationalCredentialsAttrAccess gAttrAccess;
CHIP_ERROR OperationalCredentialsAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
{
VerifyOrDie(aPath.mClusterId == Clusters::OperationalCredentials::Id);
switch (aPath.mAttributeId)
{
case Attributes::NOCs::Id: {
return ReadNOCs(aPath.mEndpointId, aEncoder);
}
case Attributes::SupportedFabrics::Id: {
return ReadSupportedFabrics(aPath.mEndpointId, aEncoder);
}
case Attributes::CommissionedFabrics::Id: {
return ReadCommissionedFabrics(aPath.mEndpointId, aEncoder);
}
case Attributes::Fabrics::Id: {
return ReadFabricsList(aPath.mEndpointId, aEncoder);
}
case Attributes::TrustedRootCertificates::Id: {
return ReadRootCertificates(aPath.mEndpointId, aEncoder);
}
case Attributes::CurrentFabricIndex::Id: {
return aEncoder.Encode(aEncoder.AccessingFabricIndex());
}
default:
break;
}
return CHIP_NO_ERROR;
}
const FabricInfo * RetrieveCurrentFabric(CommandHandler * aCommandHandler)
{
FabricIndex index = aCommandHandler->GetAccessingFabricIndex();
ChipLogDetail(Zcl, "OpCreds: Finding fabric with fabricIndex 0x%x", static_cast<unsigned>(index));
return Server::GetInstance().GetFabricTable().FindFabricWithIndex(index);
}
CHIP_ERROR DeleteFabricFromTable(FabricIndex fabricIndex)
{
ReturnErrorOnFailure(Server::GetInstance().GetFabricTable().Delete(fabricIndex));
return CHIP_NO_ERROR;
}
void CleanupSessionsForFabric(SessionManager & sessionMgr, FabricIndex fabricIndex)
{
sessionMgr.ExpireAllSessionsForFabric(fabricIndex);
}
void FailSafeCleanup(const chip::DeviceLayer::ChipDeviceEvent * event)
{
ChipLogError(Zcl, "OpCreds: Proceeding to FailSafeCleanup on fail-safe expiry!");
FabricIndex fabricIndex = event->FailSafeTimerExpired.fabricIndex;
// If an AddNOC or UpdateNOC command has been successfully invoked, terminate all CASE sessions associated with the Fabric
// whose Fabric Index is recorded in the Fail-Safe context (see ArmFailSafe Command) by clearing any associated Secure
// Session Context at the Server.
if (event->FailSafeTimerExpired.addNocCommandHasBeenInvoked || event->FailSafeTimerExpired.updateNocCommandHasBeenInvoked)
{
SessionManager & sessionMgr = Server::GetInstance().GetSecureSessionManager();
CleanupSessionsForFabric(sessionMgr, fabricIndex);
}
auto & fabricTable = Server::GetInstance().GetFabricTable();
fabricTable.RevertPendingFabricData();
// If an AddNOC command had been successfully invoked, achieve the equivalent effect of invoking the RemoveFabric command
// against the Fabric Index stored in the Fail-Safe Context for the Fabric Index that was the subject of the AddNOC
// command.
if (event->FailSafeTimerExpired.addNocCommandHasBeenInvoked)
{
CHIP_ERROR err;
err = DeleteFabricFromTable(fabricIndex);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "OpCreds: failed to delete fabric at index %u: %" CHIP_ERROR_FORMAT, fabricIndex, err.Format());
}
}
}
void OnPlatformEventHandler(const chip::DeviceLayer::ChipDeviceEvent * event, intptr_t arg)
{
if (event->Type == DeviceLayer::DeviceEventType::kFailSafeTimerExpired)
{
ChipLogError(Zcl, "OpCreds: Got FailSafeTimerExpired");
FailSafeCleanup(event);
}
}
} // anonymous namespace
class OpCredsFabricTableDelegate : public chip::FabricTable::Delegate
{
public:
// Gets called when a fabric is about to be deleted
void FabricWillBeRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override
{
// The Leave event SHOULD be emitted by a Node prior to permanently leaving the Fabric.
for (auto endpoint : EnabledEndpointsWithServerCluster(BasicInformation::Id))
{
// If Basic cluster is implemented on this endpoint
BasicInformation::Events::Leave::Type event;
event.fabricIndex = fabricIndex;
EventNumber eventNumber;
if (CHIP_NO_ERROR != LogEvent(event, endpoint, eventNumber))
{
ChipLogError(Zcl, "OpCredsFabricTableDelegate: Failed to record Leave event");
}
}
// Try to send the queued events as soon as possible for this fabric. If the just emitted leave event won't
// be sent this time, it will likely not be delivered at all for the following reasons:
// - removing the fabric expires all associated ReadHandlers, so all subscriptions to
// the leave event will be cancelled.
// - removing the fabric removes all associated access control entries, so generating
// subsequent reports containing the leave event will fail the access control check.
InteractionModelEngine::GetInstance()->GetReportingEngine().ScheduleUrgentEventDeliverySync(MakeOptional(fabricIndex));
}
// Gets called when a fabric is deleted
void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override
{
ChipLogProgress(Zcl, "OpCreds: Fabric index 0x%x was removed", static_cast<unsigned>(fabricIndex));
// We need to withdraw the advertisement for the now-removed fabric, so need
// to restart advertising altogether.
app::DnssdServer::Instance().StartServer();
EventManagement::GetInstance().FabricRemoved(fabricIndex);
NotifyFabricTableChanged();
}
// Gets called when a fabric is added/updated, but not necessarily committed to storage
void OnFabricUpdated(const FabricTable & fabricTable, FabricIndex fabricIndex) override { NotifyFabricTableChanged(); }
// Gets called when a fabric in FabricTable is persisted to storage
void OnFabricCommitted(const FabricTable & fabricTable, FabricIndex fabricIndex) override
{
const FabricInfo * fabric = fabricTable.FindFabricWithIndex(fabricIndex);
// Safety check, but should not happen by the code paths involved
VerifyOrReturn(fabric != nullptr);
ChipLogProgress(Zcl,
"OpCreds: Fabric index 0x%x was committed to storage. Compressed Fabric Id 0x" ChipLogFormatX64
", FabricId " ChipLogFormatX64 ", NodeId " ChipLogFormatX64 ", VendorId 0x%04X",
static_cast<unsigned>(fabric->GetFabricIndex()), ChipLogValueX64(fabric->GetCompressedFabricId()),
ChipLogValueX64(fabric->GetFabricId()), ChipLogValueX64(fabric->GetNodeId()), fabric->GetVendorId());
}
private:
void NotifyFabricTableChanged()
{
// Opcreds cluster is always on Endpoint 0
MatterReportingAttributeChangeCallback(0, OperationalCredentials::Id,
OperationalCredentials::Attributes::CommissionedFabrics::Id);
MatterReportingAttributeChangeCallback(0, OperationalCredentials::Id, OperationalCredentials::Attributes::Fabrics::Id);
}
};
OpCredsFabricTableDelegate gFabricDelegate;
void MatterOperationalCredentialsPluginServerInitCallback()
{
registerAttributeAccessOverride(&gAttrAccess);
Server::GetInstance().GetFabricTable().AddFabricDelegate(&gFabricDelegate);
DeviceLayer::PlatformMgrImpl().AddEventHandler(OnPlatformEventHandler);
}
bool emberAfOperationalCredentialsClusterRemoveFabricCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::RemoveFabric::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("RemoveFabric", "OperationalCredentials");
auto & fabricBeingRemoved = commandData.fabricIndex;
ChipLogProgress(Zcl, "OpCreds: Received a RemoveFabric Command for FabricIndex 0x%x",
static_cast<unsigned>(fabricBeingRemoved));
if (!IsValidFabricIndex(fabricBeingRemoved))
{
ChipLogError(Zcl, "OpCreds: Failed RemoveFabric due to invalid FabricIndex");
commandObj->AddStatus(commandPath, Status::InvalidCommand);
return true;
}
commandObj->FlushAcksRightAwayOnSlowCommand();
CHIP_ERROR err = DeleteFabricFromTable(fabricBeingRemoved);
SuccessOrExit(err);
// Notification was already done by FabricTable delegate
exit:
// Not using ConvertToNOCResponseStatus here because it's pretty
// AddNOC/UpdateNOC specific.
if (err == CHIP_ERROR_NOT_FOUND)
{
ChipLogError(Zcl, "OpCreds: Failed RemoveFabric due to FabricIndex not found locally");
SendNOCResponse(commandObj, commandPath, OperationalCertStatus::kInvalidFabricIndex, fabricBeingRemoved, CharSpan());
}
else if (err != CHIP_NO_ERROR)
{
// We have no idea what happened; just report failure.
ChipLogError(Zcl, "OpCreds: Failed RemoveFabric due to internal error (err = %" CHIP_ERROR_FORMAT ")", err.Format());
StatusIB status(err);
commandObj->AddStatus(commandPath, status.mStatus);
}
else
{
ChipLogProgress(Zcl, "OpCreds: RemoveFabric successful");
SendNOCResponse(commandObj, commandPath, OperationalCertStatus::kSuccess, fabricBeingRemoved, CharSpan());
chip::Messaging::ExchangeContext * ec = commandObj->GetExchangeContext();
FabricIndex currentFabricIndex = commandObj->GetAccessingFabricIndex();
if (currentFabricIndex == fabricBeingRemoved)
{
ec->AbortAllOtherCommunicationOnFabric();
}
else
{
SessionManager * sessionManager = ec->GetExchangeMgr()->GetSessionManager();
CleanupSessionsForFabric(*sessionManager, fabricBeingRemoved);
}
}
return true;
}
bool emberAfOperationalCredentialsClusterUpdateFabricLabelCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::UpdateFabricLabel::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("UpdateFabricLabel", "OperationalCredentials");
auto & label = commandData.label;
auto ourFabricIndex = commandObj->GetAccessingFabricIndex();
auto finalStatus = Status::Failure;
auto & fabricTable = Server::GetInstance().GetFabricTable();
ChipLogProgress(Zcl, "OpCreds: Received an UpdateFabricLabel command");
if (label.size() > 32)
{
ChipLogError(Zcl, "OpCreds: Failed UpdateFabricLabel due to invalid label size %u", static_cast<unsigned>(label.size()));
commandObj->AddStatus(commandPath, Status::InvalidCommand);
return true;
}
for (const auto & fabricInfo : fabricTable)
{
if (fabricInfo.GetFabricLabel().data_equal(label) && fabricInfo.GetFabricIndex() != ourFabricIndex)
{
ChipLogError(Zcl, "Fabric label already in use");
SendNOCResponse(commandObj, commandPath, OperationalCertStatus::kLabelConflict, ourFabricIndex, CharSpan());
return true;
}
}
// Set Label on fabric. Any error on this is basically an internal error...
// NOTE: if an UpdateNOC had caused a pending fabric, that pending fabric is
// the one updated thereafter. Otherwise, the data is committed to storage
// as soon as the update is done.
CHIP_ERROR err = fabricTable.SetFabricLabel(ourFabricIndex, label);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
finalStatus = Status::Success;
// Succeeded at updating the label, mark Fabrics table changed.
MatterReportingAttributeChangeCallback(commandPath.mEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::Fabrics::Id);
exit:
if (finalStatus == Status::Success)
{
SendNOCResponse(commandObj, commandPath, OperationalCertStatus::kSuccess, ourFabricIndex, CharSpan());
}
else
{
commandObj->AddStatus(commandPath, finalStatus);
}
return true;
}
namespace {
void SendNOCResponse(app::CommandHandler * commandObj, const ConcreteCommandPath & path, OperationalCertStatus status,
uint8_t index, const CharSpan & debug_text)
{
Commands::NOCResponse::Type payload;
payload.statusCode = status;
if (status == OperationalCertStatus::kSuccess)
{
payload.fabricIndex.Emplace(index);
}
if (!debug_text.empty())
{
// Max length of DebugText is 128 in the spec.
const CharSpan & to_send = debug_text.size() > 128 ? debug_text.SubSpan(0, 128) : debug_text;
payload.debugText.Emplace(to_send);
}
commandObj->AddResponse(path, payload);
}
OperationalCertStatus ConvertToNOCResponseStatus(CHIP_ERROR err)
{
if (err == CHIP_NO_ERROR)
{
return OperationalCertStatus::kSuccess;
}
if (err == CHIP_ERROR_INVALID_PUBLIC_KEY)
{
return OperationalCertStatus::kInvalidPublicKey;
}
if (err == CHIP_ERROR_WRONG_NODE_ID)
{
return OperationalCertStatus::kInvalidNodeOpId;
}
if (err == CHIP_ERROR_UNSUPPORTED_CERT_FORMAT)
{
return OperationalCertStatus::kInvalidNOC;
}
if (err == CHIP_ERROR_WRONG_CERT_DN)
{
return OperationalCertStatus::kInvalidNOC;
}
if (err == CHIP_ERROR_INCORRECT_STATE)
{
return OperationalCertStatus::kMissingCsr;
}
if (err == CHIP_ERROR_NO_MEMORY)
{
return OperationalCertStatus::kTableFull;
}
if (err == CHIP_ERROR_FABRIC_EXISTS)
{
return OperationalCertStatus::kFabricConflict;
}
if (err == CHIP_ERROR_INVALID_FABRIC_INDEX)
{
return OperationalCertStatus::kInvalidFabricIndex;
}
if (err == CHIP_ERROR_INVALID_ADMIN_SUBJECT)
{
return OperationalCertStatus::kInvalidAdminSubject;
}
return OperationalCertStatus::kInvalidNOC;
}
} // namespace
bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::AddNOC::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("AddNOC", "OperationalCredentials");
auto & NOCValue = commandData.NOCValue;
auto & ICACValue = commandData.ICACValue;
auto & adminVendorId = commandData.adminVendorId;
auto & ipkValue = commandData.IPKValue;
auto * groupDataProvider = Credentials::GetGroupDataProvider();
auto nocResponse = OperationalCertStatus::kSuccess;
auto nonDefaultStatus = Status::Success;
bool needRevert = false;
CHIP_ERROR err = CHIP_NO_ERROR;
FabricIndex newFabricIndex = kUndefinedFabricIndex;
Credentials::GroupDataProvider::KeySet keyset;
const FabricInfo * newFabricInfo = nullptr;
auto & fabricTable = Server::GetInstance().GetFabricTable();
auto * secureSession = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession();
auto & failSafeContext = Server::GetInstance().GetFailSafeContext();
uint8_t compressed_fabric_id_buffer[sizeof(uint64_t)];
MutableByteSpan compressed_fabric_id(compressed_fabric_id_buffer);
bool csrWasForUpdateNoc = false; //< Output param of HasPendingOperationalKey
bool hasPendingKey = fabricTable.HasPendingOperationalKey(csrWasForUpdateNoc);
ChipLogProgress(Zcl, "OpCreds: Received an AddNOC command");
VerifyOrExit(NOCValue.size() <= Credentials::kMaxCHIPCertLength, nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(!ICACValue.HasValue() || ICACValue.Value().size() <= Credentials::kMaxCHIPCertLength,
nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(ipkValue.size() == Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES, nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(IsVendorIdValidOperationally(adminVendorId), nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(failSafeContext.IsFailSafeArmed(commandObj->GetAccessingFabricIndex()),
nonDefaultStatus = Status::FailsafeRequired);
VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), nonDefaultStatus = Status::ConstraintError);
// Must have had a previous CSR request, not tagged for UpdateNOC
VerifyOrExit(hasPendingKey, nocResponse = OperationalCertStatus::kMissingCsr);
VerifyOrExit(!csrWasForUpdateNoc, nonDefaultStatus = Status::ConstraintError);
// Internal error that would prevent IPK from being added
VerifyOrExit(groupDataProvider != nullptr, nonDefaultStatus = Status::Failure);
// Flush acks before really slow work
commandObj->FlushAcksRightAwayOnSlowCommand();
// We can't possibly have a matching root based on the fact that we don't have
// a shared root store. Therefore we would later fail path validation due to
// missing root. Let's early-bail with InvalidNOC.
VerifyOrExit(failSafeContext.AddTrustedRootCertHasBeenInvoked(), nocResponse = OperationalCertStatus::kInvalidNOC);
err = fabricTable.AddNewPendingFabricWithOperationalKeystore(NOCValue, ICACValue.ValueOr(ByteSpan{}), adminVendorId,
&newFabricIndex);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
// From here if we error-out, we should revert the fabric table pending updates
needRevert = true;
newFabricInfo = fabricTable.FindFabricWithIndex(newFabricIndex);
VerifyOrExit(newFabricInfo != nullptr, nonDefaultStatus = Status::Failure);
// Set the Identity Protection Key (IPK)
// The IPK SHALL be the operational group key under GroupKeySetID of 0
keyset.keyset_id = Credentials::GroupDataProvider::kIdentityProtectionKeySetId;
keyset.policy = GroupKeyManagement::GroupKeySecurityPolicy::kTrustFirst;
keyset.num_keys_used = 1;
memcpy(keyset.epoch_keys[0].key, ipkValue.data(), Crypto::CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES);
err = newFabricInfo->GetCompressedFabricIdBytes(compressed_fabric_id);
VerifyOrExit(err == CHIP_NO_ERROR, nonDefaultStatus = Status::Failure);
err = groupDataProvider->SetKeySet(newFabricIndex, compressed_fabric_id, keyset);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
/**
* . If the current secure session was established with PASE,
* the receiver SHALL:
* .. Augment the secure session context with the `FabricIndex` generated above
* such that subsequent interactions have the proper accessing fabric.
*
* . If the current secure session was established with CASE, subsequent configuration
* of the newly installed Fabric requires the opening of a new CASE session from the
* Administrator from the Fabric just installed. This Administrator is the one listed
* in the `caseAdminSubject` argument.
*
*/
if (secureSession->GetSecureSessionType() == SecureSession::Type::kPASE)
{
err = secureSession->AdoptFabricIndex(newFabricIndex);
VerifyOrExit(err == CHIP_NO_ERROR, nonDefaultStatus = Status::Failure);
}
// Creating the initial ACL must occur after the PASE session has adopted the fabric index
// (see above) so that the concomitant event, which is fabric scoped, is properly handled.
err = CreateAccessControlEntryForNewFabricAdministrator(commandObj->GetSubjectDescriptor(), newFabricIndex,
commandData.caseAdminSubject);
VerifyOrExit(err != CHIP_ERROR_INTERNAL, nonDefaultStatus = Status::Failure);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
// The Fabric Index associated with the armed fail-safe context SHALL be updated to match the Fabric
// Index just allocated.
failSafeContext.SetAddNocCommandInvoked(newFabricIndex);
// Done all intermediate steps, we are now successful
needRevert = false;
// We might have a new operational identity, so we should start advertising it right away.
app::DnssdServer::Instance().AdvertiseOperational();
// Notify the attributes containing fabric metadata can be read with new data
MatterReportingAttributeChangeCallback(commandPath.mEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::Fabrics::Id);
// Notify we have one more fabric
MatterReportingAttributeChangeCallback(commandPath.mEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::CommissionedFabrics::Id);
exit:
if (needRevert)
{
// Here, on revert, we DO NOT call FabricTable::Delete as this would also remove the existing
// trusted root previously added. It possibly got reverted in case of the worst kinds of errors,
// but a better impl of the innards of FabricTable::CommitPendingFabricData would make it work.
fabricTable.RevertPendingOpCertsExceptRoot();
// Revert IPK and ACL entries added, ignoring errors, since some steps may have been skipped
// and error handling does not assist.
if (groupDataProvider != nullptr)
{
(void) groupDataProvider->RemoveFabric(newFabricIndex);
}
(void) Access::GetAccessControl().DeleteAllEntriesForFabric(newFabricIndex);
MatterReportingAttributeChangeCallback(commandPath.mEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::CommissionedFabrics::Id);
MatterReportingAttributeChangeCallback(commandPath.mEndpointId, OperationalCredentials::Id,
OperationalCredentials::Attributes::Fabrics::Id);
}
// We have an NOC response
if (nonDefaultStatus == Status::Success)
{
SendNOCResponse(commandObj, commandPath, nocResponse, newFabricIndex, CharSpan());
// Failed to add NOC
if (nocResponse != OperationalCertStatus::kSuccess)
{
ChipLogError(Zcl, "OpCreds: Failed AddNOC request (err=%" CHIP_ERROR_FORMAT ") with OperationalCert error %d",
err.Format(), to_underlying(nocResponse));
}
// Success
else
{
ChipLogProgress(Zcl, "OpCreds: successfully created fabric index 0x%x via AddNOC",
static_cast<unsigned>(newFabricIndex));
}
}
// No NOC response - Failed constraints
else
{
commandObj->AddStatus(commandPath, nonDefaultStatus);
ChipLogError(Zcl, "OpCreds: Failed AddNOC request with IM error 0x%02x", to_underlying(nonDefaultStatus));
}
return true;
}
bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::UpdateNOC::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("UpdateNOC", "OperationalCredentials");
auto & NOCValue = commandData.NOCValue;
auto & ICACValue = commandData.ICACValue;
auto nocResponse = OperationalCertStatus::kSuccess;
auto nonDefaultStatus = Status::Success;
CHIP_ERROR err = CHIP_NO_ERROR;
FabricIndex fabricIndex = 0;
ChipLogProgress(Zcl, "OpCreds: Received an UpdateNOC command");
auto & fabricTable = Server::GetInstance().GetFabricTable();
auto & failSafeContext = Server::GetInstance().GetFailSafeContext();
const FabricInfo * fabricInfo = RetrieveCurrentFabric(commandObj);
bool csrWasForUpdateNoc = false; //< Output param of HasPendingOperationalKey
bool hasPendingKey = fabricTable.HasPendingOperationalKey(csrWasForUpdateNoc);
VerifyOrExit(NOCValue.size() <= Credentials::kMaxCHIPCertLength, nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(!ICACValue.HasValue() || ICACValue.Value().size() <= Credentials::kMaxCHIPCertLength,
nonDefaultStatus = Status::InvalidCommand);
VerifyOrExit(failSafeContext.IsFailSafeArmed(commandObj->GetAccessingFabricIndex()),
nonDefaultStatus = Status::FailsafeRequired);
VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), nonDefaultStatus = Status::ConstraintError);
// Must have had a previous CSR request, tagged for UpdateNOC
VerifyOrExit(hasPendingKey, nocResponse = OperationalCertStatus::kMissingCsr);
VerifyOrExit(csrWasForUpdateNoc, nonDefaultStatus = Status::ConstraintError);
// If current fabric is not available, command was invoked over PASE which is not legal
VerifyOrExit(fabricInfo != nullptr, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INSUFFICIENT_PRIVILEGE));
fabricIndex = fabricInfo->GetFabricIndex();
// Flush acks before really slow work
commandObj->FlushAcksRightAwayOnSlowCommand();
err = fabricTable.UpdatePendingFabricWithOperationalKeystore(fabricIndex, NOCValue, ICACValue.ValueOr(ByteSpan{}));
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
// Flag on the fail-safe context that the UpdateNOC command was invoked.
failSafeContext.SetUpdateNocCommandInvoked();
// We might have a new operational identity, so we should start advertising
// it right away. Also, we need to withdraw our old operational identity.
// So we need to StartServer() here.
app::DnssdServer::Instance().StartServer();
// Attribute notification was already done by fabric table
exit:
// We have an NOC response
if (nonDefaultStatus == Status::Success)
{
SendNOCResponse(commandObj, commandPath, nocResponse, fabricIndex, CharSpan());
// Failed to update NOC
if (nocResponse != OperationalCertStatus::kSuccess)
{
ChipLogError(Zcl, "OpCreds: Failed UpdateNOC request (err=%" CHIP_ERROR_FORMAT ") with OperationalCert error %d",
err.Format(), to_underlying(nocResponse));
}
// Success
else
{
ChipLogProgress(Zcl, "OpCreds: UpdateNOC successful.");
// On success, revoke all CASE sessions on the fabric hosting the exchange.
// From spec:
//
// All internal data reflecting the prior operational identifier of the Node within the Fabric
// SHALL be revoked and removed, to an outcome equivalent to the disappearance of the prior Node,
// except for the ongoing CASE session context, which SHALL temporarily remain valid until the
// `NOCResponse` has been successfully delivered or until the next transport-layer error, so
// that the response can be received by the Administrator invoking the command.
commandObj->GetExchangeContext()->AbortAllOtherCommunicationOnFabric();
}
}
// No NOC response - Failed constraints
else
{
commandObj->AddStatus(commandPath, nonDefaultStatus);
ChipLogError(Zcl, "OpCreds: Failed UpdateNOC request with IM error 0x%02x", to_underlying(nonDefaultStatus));
}
return true;
}
bool emberAfOperationalCredentialsClusterCertificateChainRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::CertificateChainRequest::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("CertificateChainRequest", "OperationalCredentials");
auto & certificateType = commandData.certificateType;
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t derBuf[Credentials::kMaxDERCertLength];
MutableByteSpan derBufSpan(derBuf);
Commands::CertificateChainResponse::Type response;
Credentials::DeviceAttestationCredentialsProvider * dacProvider = Credentials::GetDeviceAttestationCredentialsProvider();
VerifyOrExit(commandObj != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
if (certificateType == kDACCertificate)
{
ChipLogProgress(Zcl, "OpCreds: Certificate Chain request received for DAC");
SuccessOrExit(err = dacProvider->GetDeviceAttestationCert(derBufSpan));
}
else if (certificateType == kPAICertificate)
{
ChipLogProgress(Zcl, "OpCreds: Certificate Chain request received for PAI");
SuccessOrExit(err = dacProvider->GetProductAttestationIntermediateCert(derBufSpan));
}
else
{
ChipLogError(Zcl, "OpCreds: Certificate Chain request received for unknown type: %d", static_cast<int>(certificateType));
commandObj->AddStatus(commandPath, Status::InvalidCommand);
return true;
}
response.certificate = derBufSpan;
commandObj->AddResponse(commandPath, response);
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "OpCreds: Failed CertificateChainRequest: %" CHIP_ERROR_FORMAT, err.Format());
commandObj->AddStatus(commandPath, Status::Failure);
}
return true;
}
bool emberAfOperationalCredentialsClusterAttestationRequestCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::AttestationRequest::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("AttestationRequest", "OperationalCredentials");
auto & attestationNonce = commandData.attestationNonce;
auto finalStatus = Status::Failure;
CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT;
ByteSpan tbsSpan;
Platform::ScopedMemoryBuffer<uint8_t> attestationElements;
size_t attestationElementsLen = 0;
MutableByteSpan attestationElementsSpan;
uint8_t certDeclBuf[Credentials::kMaxCMSSignedCDMessage]; // Sized to hold the example certificate declaration with 100 PIDs.
// See DeviceAttestationCredsExample
MutableByteSpan certDeclSpan(certDeclBuf);
// TODO: Create an alternative way to retrieve the Attestation Challenge without this huge amount of calls.
// Retrieve attestation challenge
ByteSpan attestationChallenge =
commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge();
// TODO: in future versions, retrieve vendor information to populate the fields below.
uint32_t timestamp = 0;
Credentials::DeviceAttestationVendorReservedConstructor emptyVendorReserved(nullptr, 0);
// TODO: in future versions, also retrieve and use firmware Information
const ByteSpan kEmptyFirmwareInfo;
ChipLogProgress(Zcl, "OpCreds: Received an AttestationRequest command");
// Flush acks before really slow work
commandObj->FlushAcksRightAwayOnSlowCommand();
Credentials::DeviceAttestationCredentialsProvider * dacProvider = Credentials::GetDeviceAttestationCredentialsProvider();
VerifyOrExit(attestationNonce.size() == Credentials::kExpectedAttestationNonceSize, finalStatus = Status::InvalidCommand);
if (dacProvider == nullptr)
{
err = CHIP_ERROR_INTERNAL;
VerifyOrExit(dacProvider != nullptr, finalStatus = Status::Failure);
}
err = dacProvider->GetCertificationDeclaration(certDeclSpan);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
attestationElementsLen = TLV::EstimateStructOverhead(certDeclSpan.size(), attestationNonce.size(), sizeof(uint64_t) * 8);
if (!attestationElements.Alloc(attestationElementsLen + attestationChallenge.size()))
{
err = CHIP_ERROR_NO_MEMORY;
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::ResourceExhausted);
}
attestationElementsSpan = MutableByteSpan{ attestationElements.Get(), attestationElementsLen };
err = Credentials::ConstructAttestationElements(certDeclSpan, attestationNonce, timestamp, kEmptyFirmwareInfo,
emptyVendorReserved, attestationElementsSpan);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
// Append attestation challenge in the back of the reserved space for the signature
memcpy(attestationElements.Get() + attestationElementsSpan.size(), attestationChallenge.data(), attestationChallenge.size());
tbsSpan = ByteSpan{ attestationElements.Get(), attestationElementsSpan.size() + attestationChallenge.size() };
{
Crypto::P256ECDSASignature signature;
MutableByteSpan signatureSpan{ signature.Bytes(), signature.Capacity() };
// Generate attestation signature
err = dacProvider->SignWithDeviceAttestationKey(tbsSpan, signatureSpan);
ClearSecretData(attestationElements.Get() + attestationElementsSpan.size(), attestationChallenge.size());
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
VerifyOrExit(signatureSpan.size() == Crypto::P256ECDSASignature::Capacity(), finalStatus = Status::Failure);
Commands::AttestationResponse::Type response;
response.attestationElements = attestationElementsSpan;
response.signature = signatureSpan;
ChipLogProgress(Zcl, "OpCreds: AttestationRequest successful.");
finalStatus = Status::Success;
commandObj->AddResponse(commandPath, response);
}
exit:
if (finalStatus != Status::Success)
{
commandObj->AddStatus(commandPath, finalStatus);
ChipLogError(Zcl, "OpCreds: Failed AttestationRequest request with IM error 0x%02x (err = %" CHIP_ERROR_FORMAT ")",
to_underlying(finalStatus), err.Format());
}
return true;
}
bool emberAfOperationalCredentialsClusterCSRRequestCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
const Commands::CSRRequest::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("CSRRequest", "OperationalCredentials");
ChipLogProgress(Zcl, "OpCreds: Received a CSRRequest command");
chip::Platform::ScopedMemoryBuffer<uint8_t> nocsrElements;
MutableByteSpan nocsrElementsSpan;
auto finalStatus = Status::Failure;
ByteSpan tbsSpan;
// Start with CHIP_ERROR_INVALID_ARGUMENT so that cascading errors yield correct
// logs by the end. We use finalStatus as our overall success marker, not error
CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT;
auto & fabricTable = Server::GetInstance().GetFabricTable();
auto & failSafeContext = Server::GetInstance().GetFailSafeContext();
auto & CSRNonce = commandData.CSRNonce;
bool isForUpdateNoc = commandData.isForUpdateNOC.ValueOr(false);
// TODO: Create an alternative way to retrieve the Attestation Challenge without this huge amount of calls.
// Retrieve attestation challenge
ByteSpan attestationChallenge =
commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession()->GetCryptoContext().GetAttestationChallenge();
failSafeContext.SetCsrRequestForUpdateNoc(isForUpdateNoc);
const FabricInfo * fabricInfo = RetrieveCurrentFabric(commandObj);
VerifyOrExit(CSRNonce.size() == Credentials::kExpectedAttestationNonceSize, finalStatus = Status::InvalidCommand);
// If current fabric is not available, command was invoked over PASE which is not legal if IsForUpdateNOC is true.
VerifyOrExit(!isForUpdateNoc || (fabricInfo != nullptr), finalStatus = Status::InvalidCommand);
VerifyOrExit(failSafeContext.IsFailSafeArmed(commandObj->GetAccessingFabricIndex()), finalStatus = Status::FailsafeRequired);
VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), finalStatus = Status::ConstraintError);
// Flush acks before really slow work
commandObj->FlushAcksRightAwayOnSlowCommand();
// Prepare NOCSRElements structure
{
constexpr size_t csrLength = Crypto::kMAX_CSR_Length;
size_t nocsrLengthEstimate = 0;
ByteSpan kNoVendorReserved;
Platform::ScopedMemoryBuffer<uint8_t> csr;
MutableByteSpan csrSpan;
// Generate the actual CSR from the ephemeral key
if (!csr.Alloc(csrLength))
{
err = CHIP_ERROR_NO_MEMORY;
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::ResourceExhausted);
}
csrSpan = MutableByteSpan{ csr.Get(), csrLength };
Optional<FabricIndex> fabricIndexForCsr;
if (isForUpdateNoc)
{
fabricIndexForCsr.SetValue(commandObj->GetAccessingFabricIndex());
}
err = fabricTable.AllocatePendingOperationalKey(fabricIndexForCsr, csrSpan);
if (csrSpan.size() > Crypto::kMAX_CSR_Length)
{
err = CHIP_ERROR_INTERNAL;
}
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "OpCreds: AllocatePendingOperationalKey returned %" CHIP_ERROR_FORMAT, err.Format());
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
}
ChipLogProgress(Zcl, "OpCreds: AllocatePendingOperationalKey succeeded");
// Encode the NOCSR elements with the CSR and Nonce
nocsrLengthEstimate = TLV::EstimateStructOverhead(csrSpan.size(), // CSR buffer
CSRNonce.size(), // CSR Nonce
0u // no vendor reserved data
);
if (!nocsrElements.Alloc(nocsrLengthEstimate + attestationChallenge.size()))
{
err = CHIP_ERROR_NO_MEMORY;
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::ResourceExhausted);
}
nocsrElementsSpan = MutableByteSpan{ nocsrElements.Get(), nocsrLengthEstimate };
err = Credentials::ConstructNOCSRElements(ByteSpan{ csrSpan.data(), csrSpan.size() }, CSRNonce, kNoVendorReserved,
kNoVendorReserved, kNoVendorReserved, nocsrElementsSpan);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
// Append attestation challenge in the back of the reserved space for the signature
memcpy(nocsrElements.Get() + nocsrElementsSpan.size(), attestationChallenge.data(), attestationChallenge.size());
tbsSpan = ByteSpan{ nocsrElements.Get(), nocsrElementsSpan.size() + attestationChallenge.size() };
{
Credentials::DeviceAttestationCredentialsProvider * dacProvider =
Credentials::GetDeviceAttestationCredentialsProvider();
Crypto::P256ECDSASignature signature;
MutableByteSpan signatureSpan{ signature.Bytes(), signature.Capacity() };
// Generate attestation signature
err = dacProvider->SignWithDeviceAttestationKey(tbsSpan, signatureSpan);
ClearSecretData(nocsrElements.Get() + nocsrElementsSpan.size(), attestationChallenge.size());
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
VerifyOrExit(signatureSpan.size() == Crypto::P256ECDSASignature::Capacity(), finalStatus = Status::Failure);
Commands::CSRResponse::Type response;
response.NOCSRElements = nocsrElementsSpan;
response.attestationSignature = signatureSpan;
ChipLogProgress(Zcl, "OpCreds: CSRRequest successful.");
finalStatus = Status::Success;
commandObj->AddResponse(commandPath, response);
}
}
exit:
// If failed constraints or internal errors, send a status report instead of the response sent above
if (finalStatus != Status::Success)
{
commandObj->AddStatus(commandPath, finalStatus);
ChipLogError(Zcl, "OpCreds: Failed CSRRequest request with IM error 0x%02x (err = %" CHIP_ERROR_FORMAT ")",
to_underlying(finalStatus), err.Format());
}
return true;
}
bool emberAfOperationalCredentialsClusterAddTrustedRootCertificateCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::AddTrustedRootCertificate::DecodableType & commandData)
{
MATTER_TRACE_EVENT_SCOPE("AddTrustedRootCertificate", "OperationalCredentials");
auto & fabricTable = Server::GetInstance().GetFabricTable();
auto finalStatus = Status::Failure;
// Start with CHIP_ERROR_INVALID_ARGUMENT so that cascading errors yield correct
// logs by the end. We use finalStatus as our overall success marker, not error
CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT;
auto & rootCertificate = commandData.rootCertificate;
auto & failSafeContext = Server::GetInstance().GetFailSafeContext();
ChipLogProgress(Zcl, "OpCreds: Received an AddTrustedRootCertificate command");
VerifyOrExit(rootCertificate.size() <= Credentials::kMaxCHIPCertLength, finalStatus = Status::InvalidCommand);
VerifyOrExit(failSafeContext.IsFailSafeArmed(commandObj->GetAccessingFabricIndex()), finalStatus = Status::FailsafeRequired);
// Can only add a single trusted root cert per fail-safe
VerifyOrExit(!failSafeContext.AddTrustedRootCertHasBeenInvoked(), finalStatus = Status::ConstraintError);
// If we successfully invoked AddNOC/UpdateNOC, this command cannot possibly
// be useful in the context.
VerifyOrExit(!failSafeContext.NocCommandHasBeenInvoked(), finalStatus = Status::ConstraintError);
// Flush acks before really slow work
commandObj->FlushAcksRightAwayOnSlowCommand();
err = ValidateChipRCAC(rootCertificate);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::InvalidCommand);
err = fabricTable.AddNewPendingTrustedRootCert(rootCertificate);
VerifyOrExit(err != CHIP_ERROR_NO_MEMORY, finalStatus = Status::ResourceExhausted);
// CHIP_ERROR_INVALID_ARGUMENT by the time we reach here means bad format
VerifyOrExit(err != CHIP_ERROR_INVALID_ARGUMENT, finalStatus = Status::InvalidCommand);
VerifyOrExit(err == CHIP_NO_ERROR, finalStatus = Status::Failure);
// Got here, so we succeeded, mark AddTrustedRootCert has having been invoked.
ChipLogProgress(Zcl, "OpCreds: AddTrustedRootCertificate successful.");
finalStatus = Status::Success;
failSafeContext.SetAddTrustedRootCertInvoked();
exit:
if (finalStatus != Status::Success)
{
ChipLogError(Zcl, "OpCreds: Failed AddTrustedRootCertificate request with IM error 0x%02x (err = %" CHIP_ERROR_FORMAT ")",
to_underlying(finalStatus), err.Format());
}
commandObj->AddStatus(commandPath, finalStatus);
return true;
}