blob: 3d2a5a1c3853fe07af658861ac492ce45ca42aad [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.
*/
/****************************************************************************
* @file
* @brief Implementation for the Operational Credentials Cluster
***************************************************************************/
#include <app-common/zap-generated/af-structs.h>
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/cluster-id.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/command-id.h>
#include <app-common/zap-generated/enums.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.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/DeviceAttestationConstructor.h>
#include <credentials/DeviceAttestationCredsProvider.h>
#include <credentials/examples/DeviceAttestationCredsExample.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 <transport/FabricTable.h>
using namespace chip;
using namespace ::chip::DeviceLayer;
using namespace ::chip::Transport;
using namespace chip::app::Clusters::OperationalCredentials;
namespace {
constexpr uint8_t kDACCertificate = 1;
constexpr uint8_t kPAICertificate = 2;
} // namespace
// As per specifications section 11.22.5.1. Constant RESP_MAX
constexpr uint16_t kMaxRspLen = 900;
/*
* Temporary flow for fabric management until addOptCert + fabric index are implemented:
* 1) When Commissioner pairs with CHIP device, store device nodeId in Fabric table as NodeId
* and store commissioner nodeId in Fabric table as FabricId (This is temporary until AddOptCert is implemented and
* Fabrics are implemented correctely) 2) When pairing is complete, commissioner calls SetFabric to set the vendorId on the newly
* created fabric. The corresponding fabric is found by looking in fabric table and finding a fabric that has the matching
* commissioner node ID as fabricId + device nodeId as nodeId and an uninitialized vendorId. 3) RemoveFabric uses the passed in
* fabricId, nodeId, vendorID to find matching entry and remove it from fabric table. Once fabricIndex is implemented, it
* should use that instead.
*/
EmberAfStatus writeFabricAttribute(uint8_t * buffer, int32_t index = -1)
{
EmberAfAttributeSearchRecord record;
record.endpoint = 0;
record.clusterId = ZCL_OPERATIONAL_CREDENTIALS_CLUSTER_ID;
record.clusterMask = CLUSTER_MASK_SERVER;
record.manufacturerCode = EMBER_AF_NULL_MANUFACTURER_CODE;
record.attributeId = ZCL_FABRICS_ATTRIBUTE_ID;
// When reading or writing a List attribute the 'index' value could have 3 types of values:
// -1: Read/Write the whole list content, including the number of elements in the list
// 0: Read/Write the number of elements in the list, represented as a uint16_t
// n: Read/Write the nth element of the list
//
// Since the first 2 bytes of the attribute are used to store the number of elements, elements indexing starts
// at 1. In order to hide this to the rest of the code of this file, the element index is incremented by 1 here.
// This also allows calling writeAttribute() with no index arg to mean "write the length".
return emAfReadOrWriteAttribute(&record,
NULL, // metadata
buffer,
0, // read length
true, // write ?
index + 1);
}
EmberAfStatus writeFabric(FabricIndex fabricIndex, FabricId fabricId, NodeId nodeId, uint16_t vendorId, const uint8_t * fabricLabel,
Credentials::P256PublicKeySpan rootPubkey, uint8_t index)
{
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
auto * fabricDescriptor = chip::Platform::New<::FabricDescriptor>();
VerifyOrReturnError(fabricDescriptor != nullptr, EMBER_ZCL_STATUS_FAILURE);
fabricDescriptor->FabricIndex = fabricIndex;
fabricDescriptor->RootPublicKey = ByteSpan(rootPubkey.data(), rootPubkey.size());
fabricDescriptor->VendorId = vendorId;
fabricDescriptor->FabricId = fabricId;
fabricDescriptor->NodeId = nodeId;
if (fabricLabel != nullptr)
{
size_t lengthToStore = strnlen(Uint8::to_const_char(fabricLabel), kFabricLabelMaxLengthInBytes);
fabricDescriptor->Label = ByteSpan(fabricLabel, lengthToStore);
}
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"OpCreds: Writing fabric into attribute store at index %d: fabricId 0x" ChipLogFormatX64
", nodeId 0x" ChipLogFormatX64 " vendorId 0x%04" PRIX16,
index, ChipLogValueX64(fabricId), ChipLogValueX64(nodeId), vendorId);
status = writeFabricAttribute((uint8_t *) fabricDescriptor, static_cast<int32_t>(index));
chip::Platform::Delete(fabricDescriptor);
return status;
}
CHIP_ERROR writeFabricsIntoFabricsListAttribute()
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Call to writeFabricsIntoFabricsListAttribute");
CHIP_ERROR err = CHIP_NO_ERROR;
// Loop through fabrics
uint8_t fabricIndex = 0;
for (auto & fabricInfo : Server::GetInstance().GetFabricTable())
{
NodeId nodeId = fabricInfo.GetPeerId().GetNodeId();
uint64_t fabricId = fabricInfo.GetFabricId();
uint16_t vendorId = fabricInfo.GetVendorId();
const uint8_t * fabricLabel = fabricInfo.GetFabricLabel();
// Skip over uninitialized fabrics
if (nodeId == kUndefinedNodeId)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"OpCreds: Skipping over uninitialized fabric with fabricId 0x" ChipLogFormatX64
", nodeId 0x" ChipLogFormatX64 " vendorId 0x%04" PRIX16,
ChipLogValueX64(fabricId), ChipLogValueX64(nodeId), vendorId);
continue;
}
else if (writeFabric(fabricInfo.GetFabricIndex(), fabricId, nodeId, vendorId, fabricLabel, fabricInfo.GetRootPubkey(),
fabricIndex) != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"OpCreds: Failed to write fabric with fabricId 0x" ChipLogFormatX64 " in fabrics list",
ChipLogValueX64(fabricId));
err = CHIP_ERROR_PERSISTED_STORAGE_FAILED;
break;
}
fabricIndex++;
}
// Store the count of fabrics we just stored
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Stored %" PRIu8 " fabrics in fabrics list attribute.", fabricIndex);
uint16_t u16Index = fabricIndex;
if (writeFabricAttribute(reinterpret_cast<uint8_t *>(&u16Index)) != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed to write fabric count %" PRIu8 " in fabrics list", fabricIndex);
err = CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
if (err == CHIP_NO_ERROR && Attributes::CommissionedFabrics::Set(0, fabricIndex) != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed to write fabrics count %" PRIu8 " in commissioned fabrics",
fabricIndex);
err = CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
if (err == CHIP_NO_ERROR && Attributes::SupportedFabrics::Set(0, CHIP_CONFIG_MAX_DEVICE_ADMINS) != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed to write %" PRIu8 " in supported fabrics count attribute",
CHIP_CONFIG_MAX_DEVICE_ADMINS);
err = CHIP_ERROR_PERSISTED_STORAGE_FAILED;
}
return err;
}
static FabricInfo * retrieveCurrentFabric()
{
if (emberAfCurrentCommand()->source == nullptr)
{
return nullptr;
}
FabricIndex index = emberAfCurrentCommand()->source->GetSecureSession().GetFabricIndex();
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Finding fabric with fabricIndex %d", index);
return Server::GetInstance().GetFabricTable().FindFabricWithIndex(index);
}
// TODO: The code currently has two sources of truths for fabrics, the fabricInfo table + the attributes. There should only be one,
// the attributes list. Currently the attributes are not persisted so we are keeping the fabric table to have the
// fabrics/admrins be persisted. Once attributes are persisted, there should only be one sorce of truth, the attributes list and
// only that should be modifed to perosst/read/write fabrics.
// TODO: Once attributes are persisted, implement reading/writing/manipulation fabrics around that and remove fabricTable
// logic.
class OpCredsFabricTableDelegate : public FabricTableDelegate
{
// Gets called when a fabric is deleted from KVS store
void OnFabricDeletedFromStorage(FabricIndex fabricId) override
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Fabric 0x%" PRIX16 " was deleted from fabric storage.", fabricId);
writeFabricsIntoFabricsListAttribute();
}
// Gets called when a fabric is loaded into the FabricTable from KVS store.
void OnFabricRetrievedFromStorage(FabricInfo * fabric) override
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"OpCreds: Fabric 0x%" PRIX16 " was retrieved from storage. FabricId 0x" ChipLogFormatX64
", NodeId 0x" ChipLogFormatX64 ", VendorId 0x%04" PRIX16,
fabric->GetFabricIndex(), ChipLogValueX64(fabric->GetFabricId()),
ChipLogValueX64(fabric->GetPeerId().GetNodeId()), fabric->GetVendorId());
writeFabricsIntoFabricsListAttribute();
}
// Gets called when a fabric in FabricTable is persisted to KVS store.
void OnFabricPersistedToStorage(FabricInfo * fabric) override
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG,
"OpCreds: Fabric %" PRIX16 " was persisted to storage. FabricId %0x" ChipLogFormatX64
", NodeId %0x" ChipLogFormatX64 ", VendorId 0x%04" PRIX16,
fabric->GetFabricIndex(), ChipLogValueX64(fabric->GetFabricId()),
ChipLogValueX64(fabric->GetPeerId().GetNodeId()), fabric->GetVendorId());
writeFabricsIntoFabricsListAttribute();
}
};
OpCredsFabricTableDelegate gFabricDelegate;
void emberAfPluginOperationalCredentialsServerInitCallback(void)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Initiating OpCreds cluster by writing fabrics list from fabric table.");
Server::GetInstance().GetFabricTable().SetFabricDelegate(&gFabricDelegate);
writeFabricsIntoFabricsListAttribute();
}
namespace {
class FabricCleanupExchangeDelegate : public Messaging::ExchangeDelegate
{
public:
CHIP_ERROR OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload) override
{
return CHIP_NO_ERROR;
}
void OnResponseTimeout(Messaging::ExchangeContext * ec) override {}
void OnExchangeClosing(Messaging::ExchangeContext * ec) override
{
FabricIndex currentFabricIndex = ec->GetSecureSession().GetFabricIndex();
ec->GetExchangeMgr()->GetSessionManager()->ExpireAllPairingsForFabric(currentFabricIndex);
}
};
FabricCleanupExchangeDelegate gFabricCleanupExchangeDelegate;
} // namespace
bool emberAfOperationalCredentialsClusterRemoveFabricCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath, EndpointId endpoint,
FabricIndex fabricBeingRemoved,
Commands::RemoveFabric::DecodableType & commandData)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: RemoveFabric"); // TODO: Generate emberAfFabricClusterPrintln
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
CHIP_ERROR err = Server::GetInstance().GetFabricTable().Delete(fabricBeingRemoved);
VerifyOrExit(err == CHIP_NO_ERROR, status = EMBER_ZCL_STATUS_FAILURE);
exit:
writeFabricsIntoFabricsListAttribute();
emberAfSendImmediateDefaultResponse(status);
if (err == CHIP_NO_ERROR)
{
Messaging::ExchangeContext * ec = commandObj->GetExchangeContext();
FabricIndex currentFabricIndex = ec->GetSecureSession().GetFabricIndex();
if (currentFabricIndex == fabricBeingRemoved)
{
// If the current fabric is being removed, expiring all the secure sessions causes crashes as
// the message sent by emberAfSendImmediateDefaultResponse() is still in the queue. Also, RMP
// retries to send the message and runs into issues.
// We are hijacking the exchange delegate here (as no more messages should be received on this exchange),
// and wait for it to close, before expiring the secure sessions for the fabric.
// TODO: https://github.com/project-chip/connectedhomeip/issues/9642
ec->SetDelegate(&gFabricCleanupExchangeDelegate);
}
else
{
ec->GetExchangeMgr()->GetSessionManager()->ExpireAllPairingsForFabric(fabricBeingRemoved);
}
}
return true;
}
bool emberAfOperationalCredentialsClusterUpdateFabricLabelCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
EndpointId endpoint, uint8_t * Label,
Commands::UpdateFabricLabel::DecodableType & commandData)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: UpdateFabricLabel");
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
CHIP_ERROR err;
// Fetch current fabric
FabricInfo * fabric = retrieveCurrentFabric();
VerifyOrExit(fabric != nullptr, status = EMBER_ZCL_STATUS_FAILURE);
// Set Label on fabric
err = fabric->SetFabricLabel(Label);
VerifyOrExit(err == CHIP_NO_ERROR, status = EMBER_ZCL_STATUS_FAILURE);
// Persist updated fabric
err = Server::GetInstance().GetFabricTable().Store(fabric->GetFabricIndex());
VerifyOrExit(err == CHIP_NO_ERROR, status = EMBER_ZCL_STATUS_FAILURE);
exit:
writeFabricsIntoFabricsListAttribute();
emberAfSendImmediateDefaultResponse(status);
return true;
}
namespace {
FabricInfo gFabricBeingCommissioned;
CHIP_ERROR SendNOCResponse(app::Command * commandObj, EmberAfNodeOperationalCertStatus status, uint8_t index, ByteSpan debug_text)
{
app::CommandPathParams cmdParams = { emberAfCurrentEndpoint(), /* group id */ 0, ZCL_OPERATIONAL_CREDENTIALS_CLUSTER_ID,
ZCL_NOC_RESPONSE_COMMAND_ID, (app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
VerifyOrReturnError(commandObj != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(commandObj->PrepareCommand(cmdParams));
writer = commandObj->GetCommandDataElementTLVWriter();
ReturnErrorOnFailure(writer->Put(TLV::ContextTag(0), status));
if (status == EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS)
{
ReturnErrorOnFailure(writer->Put(TLV::ContextTag(1), index));
}
// TODO: Change DebugText to CHAR_STRING once strings are supported in command/response fields
ReturnErrorOnFailure(writer->Put(TLV::ContextTag(2), debug_text));
return commandObj->FinishCommand();
}
EmberAfNodeOperationalCertStatus ConvertToNOCResponseStatus(CHIP_ERROR err)
{
if (err == CHIP_NO_ERROR)
{
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS;
}
else if (err == CHIP_ERROR_INVALID_PUBLIC_KEY)
{
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_INVALID_PUBLIC_KEY;
}
else if (err == CHIP_ERROR_INVALID_FABRIC_ID || err == CHIP_ERROR_WRONG_NODE_ID)
{
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_INVALID_NODE_OP_ID;
}
else if (err == CHIP_ERROR_CA_CERT_NOT_FOUND || err == CHIP_ERROR_CERT_PATH_LEN_CONSTRAINT_EXCEEDED ||
err == CHIP_ERROR_CERT_PATH_TOO_LONG || err == CHIP_ERROR_CERT_USAGE_NOT_ALLOWED || err == CHIP_ERROR_CERT_EXPIRED ||
err == CHIP_ERROR_CERT_NOT_VALID_YET || err == CHIP_ERROR_UNSUPPORTED_CERT_FORMAT ||
err == CHIP_ERROR_UNSUPPORTED_ELLIPTIC_CURVE || err == CHIP_ERROR_CERT_LOAD_FAILED ||
err == CHIP_ERROR_CERT_NOT_TRUSTED || err == CHIP_ERROR_WRONG_CERT_SUBJECT)
{
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_INVALID_NOC;
}
else if (err == CHIP_ERROR_NO_MEMORY)
{
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_TABLE_FULL;
}
return EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_INVALID_NOC;
}
} // namespace
bool emberAfOperationalCredentialsClusterAddNOCCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath, EndpointId endpoint,
ByteSpan NOCValue, ByteSpan ICACValue, ByteSpan IPKValue,
NodeId adminNodeId, uint16_t adminVendorId,
Commands::AddNOC::DecodableType & commandData)
{
EmberAfNodeOperationalCertStatus nocResponse = EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS;
CHIP_ERROR err = CHIP_NO_ERROR;
FabricIndex fabricIndex = 0;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: commissioner has added an Op Cert");
err = gFabricBeingCommissioned.SetNOCCert(NOCValue);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
err = gFabricBeingCommissioned.SetICACert(ICACValue);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
gFabricBeingCommissioned.SetVendorId(adminVendorId);
err = Server::GetInstance().GetFabricTable().AddNewFabric(gFabricBeingCommissioned, &fabricIndex);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
err = Server::GetInstance().GetFabricTable().Store(fabricIndex);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
// We might have a new operational identity, so we should start advertising it right away.
app::DnssdServer::Instance().AdvertiseOperational();
exit:
gFabricBeingCommissioned.Reset();
SendNOCResponse(commandObj, nocResponse, fabricIndex, ByteSpan());
if (nocResponse != EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed AddNOC request. Status %d", nocResponse);
}
return true;
}
bool emberAfOperationalCredentialsClusterUpdateNOCCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath, EndpointId endpoint,
ByteSpan NOCValue, ByteSpan ICACValue,
Commands::UpdateNOC::DecodableType & commandData)
{
EmberAfNodeOperationalCertStatus nocResponse = EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS;
CHIP_ERROR err = CHIP_NO_ERROR;
FabricIndex fabricIndex = 0;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: an administrator has updated the Op Cert");
// Fetch current fabric
FabricInfo * fabric = retrieveCurrentFabric();
VerifyOrExit(fabric != nullptr, nocResponse = ConvertToNOCResponseStatus(CHIP_ERROR_INVALID_FABRIC_ID));
err = fabric->SetNOCCert(NOCValue);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
err = fabric->SetICACert(ICACValue);
VerifyOrExit(err == CHIP_NO_ERROR, nocResponse = ConvertToNOCResponseStatus(err));
fabricIndex = fabric->GetFabricIndex();
// We have a new operational identity and should start advertising it. We
// can't just wait until we get network configuration commands, because we
// might be on the operational network already, in which case we are
// expected to be live with our new identity at this point.
app::DnssdServer::Instance().AdvertiseOperational();
exit:
SendNOCResponse(commandObj, nocResponse, fabricIndex, ByteSpan());
if (nocResponse != EMBER_ZCL_NODE_OPERATIONAL_CERT_STATUS_SUCCESS)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed UpdateNOC request. Status %d", nocResponse);
}
return true;
}
bool emberAfOperationalCredentialsClusterCertificateChainRequestCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, EndpointId endpoint, uint8_t certificateType,
Commands::CertificateChainRequest::DecodableType & commandData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: commissioner has requested Device Attestation Credentials");
app::CommandPathParams cmdParams = { emberAfCurrentEndpoint(), /* group id */ 0, ZCL_OPERATIONAL_CREDENTIALS_CLUSTER_ID,
ZCL_CERTIFICATE_CHAIN_RESPONSE_COMMAND_ID, (app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
uint8_t derBuf[Credentials::kMaxDERCertLength];
MutableByteSpan derBufSpan(derBuf);
Credentials::DeviceAttestationCredentialsProvider * dacProvider = Credentials::GetDeviceAttestationCredentialsProvider();
VerifyOrExit(commandObj != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
writer = commandObj->GetCommandDataElementTLVWriter();
if (certificateType == kDACCertificate)
{
SuccessOrExit(err = dacProvider->GetDeviceAttestationCert(derBufSpan));
}
else if (certificateType == kPAICertificate)
{
err = dacProvider->GetProductAttestationIntermediateCert(derBufSpan);
if (err == CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE) // If Node does not have a PAI Certificate
{
// Send an empty octet string
derBufSpan = MutableByteSpan();
}
else
{
SuccessOrExit(err);
}
}
else
{
SuccessOrExit(err = CHIP_ERROR_INVALID_ARGUMENT);
}
// TODO: Update ZAP Templates to parse TLVs starting with Tag ID 1 in order for this be spec compliant.
// TODO: Use ContextTag(1) instead
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), derBufSpan));
SuccessOrExit(err = commandObj->FinishCommand());
exit:
if (err != CHIP_NO_ERROR)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed CertificateChainRequest: %s", ErrorStr(err));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfOperationalCredentialsClusterAttestationRequestCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath,
EndpointId endpoint, ByteSpan attestationNonce,
Commands::AttestationRequest::DecodableType & commandData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
TLV::TLVWriter * writer = nullptr;
Platform::ScopedMemoryBuffer<uint8_t> attestationElements;
size_t attestationElementsLen;
Crypto::P256ECDSASignature signature;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: commissioner has requested Attestation");
app::CommandPathParams cmdParams = { emberAfCurrentEndpoint(), /* group id */ 0, ZCL_OPERATIONAL_CREDENTIALS_CLUSTER_ID,
ZCL_ATTESTATION_RESPONSE_COMMAND_ID, (app::CommandPathFlags::kEndpointIdValid) };
Credentials::DeviceAttestationCredentialsProvider * dacProvider = Credentials::GetDeviceAttestationCredentialsProvider();
VerifyOrExit(commandObj != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(attestationNonce.size() == 32, err = CHIP_ERROR_INVALID_ARGUMENT);
{
uint8_t certDeclBuf[512];
MutableByteSpan certDeclSpan(certDeclBuf);
// TODO: retrieve vendor information to populate the fields below.
uint32_t timestamp = 0;
ByteSpan firmwareInfo;
ByteSpan * vendorReservedArray = nullptr;
size_t vendorReservedArraySize = 0;
uint16_t vendorId = 0;
uint16_t profileNum = 0;
SuccessOrExit(err = dacProvider->GetCertificationDeclaration(certDeclSpan));
// TODO: Retrieve firmware Information
attestationElementsLen = certDeclSpan.size() + attestationNonce.size() + sizeof(uint64_t) * 8;
VerifyOrExit(attestationElements.Alloc(attestationElementsLen), err = CHIP_ERROR_NO_MEMORY);
MutableByteSpan attestationElementsSpan(attestationElements.Get(), attestationElementsLen);
SuccessOrExit(err = Credentials::ConstructAttestationElements(certDeclSpan, attestationNonce, timestamp, firmwareInfo,
vendorReservedArray, vendorReservedArraySize, vendorId,
profileNum, attestationElementsSpan));
attestationElementsLen = attestationElementsSpan.size();
}
{
uint8_t md[Crypto::kSHA256_Hash_Length];
MutableByteSpan messageDigestSpan(md);
// TODO: Create an alternative way to retrieve the Attestation Challenge without this huge amount of calls.
// Retrieve attestation challenge
ByteSpan attestationChallenge = commandObj->GetExchangeContext()
->GetExchangeMgr()
->GetSessionManager()
->GetSecureSession(commandObj->GetExchangeContext()->GetSecureSession())
->GetCryptoContext()
.GetAttestationChallenge();
Hash_SHA256_stream hashStream;
SuccessOrExit(err = hashStream.Begin());
SuccessOrExit(err = hashStream.AddData(ByteSpan(attestationElements.Get(), attestationElementsLen)));
SuccessOrExit(err = hashStream.AddData(attestationChallenge));
SuccessOrExit(err = hashStream.Finish(messageDigestSpan));
MutableByteSpan signatureSpan(signature, signature.Capacity());
SuccessOrExit(err = dacProvider->SignWithDeviceAttestationKey(messageDigestSpan, signatureSpan));
SuccessOrExit(err = signature.SetLength(signatureSpan.size()));
}
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
writer = commandObj->GetCommandDataElementTLVWriter();
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), ByteSpan(attestationElements.Get(), attestationElementsLen)));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), ByteSpan(signature, signature.Length())));
SuccessOrExit(err = commandObj->FinishCommand());
exit:
if (err != CHIP_NO_ERROR)
{
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed AttestationRequest: %s", ErrorStr(err));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfOperationalCredentialsClusterOpCSRRequestCallback(app::CommandHandler * commandObj,
const app::ConcreteCommandPath & commandPath, EndpointId endpoint,
ByteSpan CSRNonce,
Commands::OpCSRRequest::DecodableType & commandData)
{
CHIP_ERROR err = CHIP_NO_ERROR;
Platform::ScopedMemoryBuffer<uint8_t> csr;
size_t csrLength = Crypto::kMAX_CSR_Length;
chip::Platform::ScopedMemoryBuffer<uint8_t> csrElements;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: commissioner has requested an OpCSR");
app::CommandPathParams cmdParams = { emberAfCurrentEndpoint(), /* group id */ 0, ZCL_OPERATIONAL_CREDENTIALS_CLUSTER_ID,
ZCL_OP_CSR_RESPONSE_COMMAND_ID, (app::CommandPathFlags::kEndpointIdValid) };
TLV::TLVWriter * writer = nullptr;
TLV::TLVWriter csrElementWriter;
TLV::TLVType containerType;
VerifyOrExit(csr.Alloc(Crypto::kMAX_CSR_Length), err = CHIP_ERROR_NO_MEMORY);
if (gFabricBeingCommissioned.GetOperationalKey() == nullptr)
{
Crypto::P256Keypair keypair;
keypair.Initialize();
SuccessOrExit(err = gFabricBeingCommissioned.SetEphemeralKey(&keypair));
}
err = gFabricBeingCommissioned.GetOperationalKey()->NewCertificateSigningRequest(csr.Get(), csrLength);
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: NewCertificateSigningRequest returned %d", err);
SuccessOrExit(err);
VerifyOrExit(csrLength < UINT8_MAX, err = CHIP_ERROR_INTERNAL);
VerifyOrExit(csrElements.Alloc(kMaxRspLen), err = CHIP_ERROR_NO_MEMORY);
csrElementWriter.Init(csrElements.Get(), kMaxRspLen);
SuccessOrExit(err = csrElementWriter.StartContainer(TLV::AnonymousTag, TLV::TLVType::kTLVType_Structure, containerType));
SuccessOrExit(err = csrElementWriter.Put(TLV::ContextTag(1), ByteSpan(csr.Get(), csrLength)));
SuccessOrExit(err = csrElementWriter.Put(TLV::ContextTag(2), CSRNonce));
SuccessOrExit(err = csrElementWriter.Put(TLV::ContextTag(3), ByteSpan()));
SuccessOrExit(err = csrElementWriter.Put(TLV::ContextTag(4), ByteSpan()));
SuccessOrExit(err = csrElementWriter.Put(TLV::ContextTag(5), ByteSpan()));
SuccessOrExit(err = csrElementWriter.EndContainer(containerType));
SuccessOrExit(err = csrElementWriter.Finalize());
VerifyOrExit(commandObj != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = commandObj->PrepareCommand(cmdParams));
writer = commandObj->GetCommandDataElementTLVWriter();
// Write CSR Elements
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), ByteSpan(csrElements.Get(), csrElementWriter.GetLengthWritten())));
// TODO - Write attestation signature using attestation key
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), ByteSpan()));
SuccessOrExit(err = commandObj->FinishCommand());
exit:
if (err != CHIP_NO_ERROR)
{
gFabricBeingCommissioned.Reset();
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed OpCSRRequest: %s", ErrorStr(err));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
return true;
}
bool emberAfOperationalCredentialsClusterAddTrustedRootCertificateCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, EndpointId endpoint, ByteSpan RootCertificate,
Commands::AddTrustedRootCertificate::DecodableType & commandData)
{
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: commissioner has added a trusted root Cert");
VerifyOrExit(gFabricBeingCommissioned.SetRootCert(RootCertificate) == CHIP_NO_ERROR, status = EMBER_ZCL_STATUS_FAILURE);
exit:
emberAfSendImmediateDefaultResponse(status);
if (status == EMBER_ZCL_STATUS_FAILURE)
{
gFabricBeingCommissioned.Reset();
emberAfPrintln(EMBER_AF_PRINT_DEBUG, "OpCreds: Failed AddTrustedRootCert request.");
}
return true;
}
bool emberAfOperationalCredentialsClusterRemoveTrustedRootCertificateCallback(
app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, EndpointId endpoint,
ByteSpan TrustedRootIdentifier, Commands::RemoveTrustedRootCertificate::DecodableType & commandData)
{
EmberAfStatus status = EMBER_ZCL_STATUS_FAILURE;
emberAfSendImmediateDefaultResponse(status);
return true;
}