blob: ce899c630fe03cb5e19cdb25b1e3d0ada9c3f59b [file] [log] [blame]
/*
*
* Copyright (c) 2025 Project CHIP Authors
* All rights reserved.
*
* 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.
*/
#include "JFAManager.h"
#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/ConcreteAttributePath.h>
#include <app/server/Server.h>
#include <controller/CHIPCluster.h>
#include <lib/support/logging/CHIPLogging.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::Controller;
using namespace chip::Crypto;
using namespace chip::app::Clusters::JointFabricAdministrator;
constexpr uint8_t kJFAvailableShift = 0;
constexpr uint8_t kJFAdminShift = 1;
constexpr uint8_t kJFAnchorShift = 2;
constexpr uint8_t kJFDatastoreShift = 3;
constexpr chip::EndpointId kJointFabricAdminEndpointId = 1;
JFAManager JFAManager::sJFA;
CHIP_ERROR JFAManager::Init(Server & server)
{
mServer = &server;
mCASESessionManager = server.GetCASESessionManager();
return CHIP_NO_ERROR;
}
CHIP_ERROR JFAManager::GetJointFabricMode(uint8_t & jointFabricMode)
{
jointFabricMode = ((IsDeviceCommissioned() ? 0 : 1) << kJFAvailableShift) |
(IsDeviceCommissioned() ? (IsDeviceJFAdmin() ? (1 << kJFAdminShift) : 0) : 0) |
(IsDeviceCommissioned() ? (IsDeviceJFAnchor() ? (1 << kJFAnchorShift) : 0) : 0) |
(IsDeviceCommissioned() ? (1 << kJFDatastoreShift) : 0);
return CHIP_NO_ERROR;
}
CHIP_ERROR JFAManager::FinalizeCommissioning(NodeId nodeId, bool isJCM, P256PublicKey & trustedIcacPublicKeyB, uint16_t endpointId)
{
if (jfFabricIndex == kUndefinedFabricId)
{
return CHIP_ERROR_INCORRECT_STATE;
}
ChipLogProgress(JointFabric, "FinalizeCommissioning for NodeID: 0x" ChipLogFormatX64 ", isJCM = %d, peerEndpointId = %d",
ChipLogValueX64(nodeId), isJCM, endpointId);
peerAdminJFAdminClusterEndpointId = endpointId;
peerAdminICACPubKey = trustedIcacPublicKeyB;
ScopedNodeId scopedNodeId = ScopedNodeId(nodeId, jfFabricIndex);
ConnectToNode(scopedNodeId, isJCM ? kJCMCommissioning : kStandardCommissioningComplete);
return CHIP_NO_ERROR;
}
void JFAManager::SetJFARpc(JFARpc & aJFARpc)
{
mJFARpc = &aJFARpc;
}
JFARpc * JFAManager::GetJFARpc()
{
return mJFARpc;
}
void JFAManager::HandleCommissioningCompleteEvent()
{
for (const auto & fb : mServer->GetFabricTable())
{
FabricIndex fabricIndex = fb.GetFabricIndex();
CATValues cats;
if ((jfFabricIndex == kUndefinedFabricIndex) && mServer->GetFabricTable().FetchCATs(fabricIndex, cats) == CHIP_NO_ERROR)
{
/* When JFA is commissioned, it has to be issued a NOC with Anchor CAT and Administrator CAT */
if (cats.ContainsIdentifier(kAdminCATIdentifier) && cats.ContainsIdentifier(kAnchorCATIdentifier))
{
(void) app::Clusters::JointFabricAdministrator::Attributes::AdministratorFabricIndex::Set(
kJointFabricAdminEndpointId, fabricIndex);
jfFabricIndex = fabricIndex;
// Set AnchorRootCA
uint8_t mAnchorRootCABuffer[Credentials::kMaxDERCertLength];
MutableByteSpan rootCertSpan(mAnchorRootCABuffer);
if (mServer->GetFabricTable().FetchRootCert(fabricIndex, rootCertSpan) == CHIP_NO_ERROR)
{
if (Server::GetInstance().GetJointFabricDatastore().SetAnchorRootCA(rootCertSpan) == CHIP_NO_ERROR)
{
ChipLogProgress(JointFabric, "Set AnchorRootCA (%u bytes)", static_cast<unsigned>(rootCertSpan.size()));
}
else
{
ChipLogError(JointFabric, "Failed to set AnchorRootCA");
}
}
// obtain nodeid and use it to set anchornodeid
NodeId nodeId = fb.GetNodeId();
if (nodeId != kUndefinedNodeId)
{
if (Server::GetInstance().GetJointFabricDatastore().SetAnchorNodeId(nodeId) == CHIP_NO_ERROR)
{
ChipLogProgress(JointFabric, "Set AnchorNodeId to 0x" ChipLogFormatX64, ChipLogValueX64(nodeId));
}
else
{
ChipLogError(JointFabric, "Failed to set AnchorNodeId to 0x" ChipLogFormatX64, ChipLogValueX64(nodeId));
}
}
VendorId vendorId = fb.GetVendorId();
if (vendorId != VendorId::NotSpecified)
{
if (Server::GetInstance().GetJointFabricDatastore().SetAnchorVendorId(vendorId) == CHIP_NO_ERROR)
{
ChipLogProgress(JointFabric, "Set AnchorVendorId to %d", vendorId);
}
else
{
ChipLogError(JointFabric, "Failed to set AnchorVendorId to %d", vendorId);
}
}
if (vendorId != VendorId::NotSpecified && nodeId != kUndefinedNodeId)
{
char friendlyNameBuffer[32];
int written = snprintf(friendlyNameBuffer, sizeof(friendlyNameBuffer), "jfa-0x%04X-0x" ChipLogFormatX64,
to_underlying(vendorId), ChipLogValueX64(nodeId));
CharSpan friendlyName = CharSpan(friendlyNameBuffer, static_cast<size_t>(written));
if (Server::GetInstance().GetJointFabricDatastore().SetFriendlyName(friendlyName) == CHIP_NO_ERROR)
{
ChipLogProgress(JointFabric, "Set FriendlyName to %.*s", static_cast<int>(friendlyName.size()),
friendlyName.data());
}
else
{
ChipLogError(JointFabric, "Failed to set FriendlyName to %.*s", static_cast<int>(friendlyName.size()),
friendlyName.data());
}
}
Server::GetInstance().GetJointFabricDatastore().SetStatus(
Clusters::JointFabricDatastore::DatastoreStateEnum::kPending,
static_cast<uint32_t>(System::SystemClock().GetMonotonicTimestamp().count()), 0);
Clusters::JointFabricDatastore::Structs::DatastoreGroupKeySetStruct::Type defaultGroupKeySetEntry{ 0 };
LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().AddGroupKeySetEntry(defaultGroupKeySetEntry));
LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddNodeKeySetEntry(0, nodeId));
// Add default group entry for the JFA itself
// Uses internal method that bypasses CAT restrictions since JFA setup needs both Admin and Anchor CATs
Clusters::JointFabricDatastore::Commands::AddGroup::DecodableType addGroupCommandData;
addGroupCommandData.groupID = 0;
addGroupCommandData.friendlyName = CharSpan::fromCharString("Default JFA Group");
addGroupCommandData.groupKeySetID = 0;
addGroupCommandData.groupCAT = kAdminCATIdentifier; // Use Admin CAT for default group
addGroupCommandData.groupCATVersion.SetNonNull(1);
addGroupCommandData.groupPermission =
Clusters::JointFabricDatastore::DatastoreAccessControlEntryPrivilegeEnum::kAdminister;
LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddGroup(addGroupCommandData));
addGroupCommandData.groupID = 1;
addGroupCommandData.groupCAT = kAnchorCATIdentifier; // Use Anchor CAT for second default group
LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().ForceAddGroup(addGroupCommandData));
Clusters::JointFabricDatastore::Structs::DatastoreAdministratorInformationEntryStruct::Type newAdmin;
newAdmin.nodeID = nodeId;
newAdmin.friendlyName = CharSpan::fromCharString("Default JFA Admin");
newAdmin.vendorID = vendorId;
newAdmin.icac = ByteSpan(mICACBuffer, mICACBufferLen);
LogErrorOnFailure(Server::GetInstance().GetJointFabricDatastore().AddAdmin(newAdmin));
ChipLogProgress(JointFabric, "Joint Fabric Administrator commissioned on fabric index %d", fabricIndex);
}
}
}
}
bool JFAManager::IsDeviceJFAdmin()
{
if (jfFabricIndex == kUndefinedFabricId)
{
return false;
}
CATValues cats;
if (mServer->GetFabricTable().FetchCATs(jfFabricIndex, cats) == CHIP_NO_ERROR)
{
if (cats.ContainsIdentifier(kAdminCATIdentifier))
{
return true;
}
}
return false;
}
bool JFAManager::IsDeviceJFAnchor()
{
if (jfFabricIndex == kUndefinedFabricId)
{
return false;
}
CATValues cats;
if (mServer->GetFabricTable().FetchCATs(jfFabricIndex, cats) == CHIP_NO_ERROR)
{
if (cats.ContainsIdentifier(kAnchorCATIdentifier))
{
return true;
}
}
return false;
}
void JFAManager::ReleaseSession()
{
auto optionalSessionHandle = mSessionHolder.Get();
if (optionalSessionHandle.HasValue())
{
if (optionalSessionHandle.Value()->IsActiveSession())
{
optionalSessionHandle.Value()->AsSecureSession()->MarkAsDefunct();
}
}
mSessionHolder.Release();
}
void JFAManager::ConnectToNode(ScopedNodeId scopedNodeId, OnConnectedAction onConnectedAction)
{
VerifyOrDie(mServer != nullptr);
if ((scopedNodeId.GetFabricIndex() == kUndefinedFabricIndex) || (scopedNodeId.GetNodeId() == kUndefinedNodeId))
{
ChipLogError(JointFabric, "Invalid node location!");
return;
}
// Set the action to take once connection is successfully established
mNodeId = scopedNodeId.GetNodeId();
mOnConnectedAction = onConnectedAction;
ChipLogDetail(JointFabric, "Establishing session to node ID 0x" ChipLogFormatX64 " on fabric index %d",
ChipLogValueX64(scopedNodeId.GetNodeId()), scopedNodeId.GetFabricIndex());
mCASESessionManager->FindOrEstablishSession(scopedNodeId, &mOnConnectedCallback, &mOnConnectionFailureCallback);
}
void JFAManager::OnConnected(void * context, Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
{
JFAManager * jfaManager = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManager != nullptr);
jfaManager->mSessionHolder.Grab(sessionHandle);
jfaManager->mExchangeMgr = &exchangeMgr;
ChipLogProgress(JointFabric, "Established CASE");
switch (jfaManager->mOnConnectedAction)
{
case kStandardCommissioningComplete: {
TEMPORARY_RETURN_IGNORED jfaManager->SendCommissioningComplete();
break;
}
case kJCMCommissioning: {
TEMPORARY_RETURN_IGNORED jfaManager->AnnounceJointFabricAdministrator();
break;
}
default:
break;
}
}
void JFAManager::OnConnectionFailure(void * context, const ScopedNodeId & peerId, CHIP_ERROR error)
{
JFAManager * jfaManager = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManager != nullptr);
ChipLogError(JointFabric, "Failed to establish connection to 0x" ChipLogFormatX64 " on fabric index %d",
ChipLogValueX64(peerId.GetNodeId()), peerId.GetFabricIndex());
jfaManager->ReleaseSession();
}
CHIP_ERROR JFAManager::AnnounceJointFabricAdministrator()
{
Commands::AnnounceJointFabricAdministrator::Type request;
if (!mExchangeMgr)
{
return CHIP_ERROR_UNINITIALIZED;
}
ChipLogProgress(JointFabric, "AnnounceJointFabricAdministrator: invoke cluster command.");
request.endpointID = kJointFabricAdminEndpointId;
Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), peerAdminJFAdminClusterEndpointId);
return cluster.InvokeCommand(request, this, OnAnnounceJointFabricAdministratorResponse,
OnAnnounceJointFabricAdministratorFailure);
}
void JFAManager::OnAnnounceJointFabricAdministratorResponse(void * context, const chip::app::DataModel::NullObjectType & data)
{
JFAManager * jfaManagerCore = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManagerCore != nullptr);
ChipLogProgress(JointFabric, "OnAnnounceJointFabricAdministratorResponse");
/* TODO: https://github.com/project-chip/connectedhomeip/issues/38202 */
TEMPORARY_RETURN_IGNORED jfaManagerCore->SendICACSRRequest();
}
void JFAManager::OnAnnounceJointFabricAdministratorFailure(void * context, CHIP_ERROR error)
{
JFAManager * jfaManagerCore = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManagerCore != nullptr);
jfaManagerCore->ReleaseSession();
ChipLogError(JointFabric, "OnAnnounceJointFabricAdministratorFailure: %s\n", chip::ErrorStr(error));
}
CHIP_ERROR JFAManager::SendICACSRRequest()
{
Commands::ICACCSRRequest::Type request;
if (!mExchangeMgr)
{
return CHIP_ERROR_UNINITIALIZED;
}
ChipLogProgress(JointFabric, "SendICACSRRequest: invoke cluster command.");
Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), peerAdminJFAdminClusterEndpointId);
return cluster.InvokeCommand(request, this, OnSendICACSRRequestResponse, OnSendICACSRRequestFailure);
}
void JFAManager::OnSendICACSRRequestResponse(void * context, const Commands::ICACCSRResponse::DecodableType & icaccsr)
{
JFAManager * jfaManagerCore = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManagerCore != nullptr);
P256PublicKey pubKey;
ChipLogProgress(JointFabric, "OnSendICACSRRequestResponse");
if ((CHIP_NO_ERROR == VerifyCertificateSigningRequest(icaccsr.icaccsr.data(), icaccsr.icaccsr.size(), pubKey)) &&
jfaManagerCore->peerAdminICACPubKey.Matches(pubKey))
{
ChipLogProgress(JointFabric, "OnSendICACSRRequestResponse: validated ICAC CSR");
TEMPORARY_RETURN_IGNORED jfaManagerCore->SendCommissioningComplete();
}
}
void JFAManager::OnSendICACSRRequestFailure(void * context, CHIP_ERROR error)
{
JFAManager * jfaManagerCore = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManagerCore != nullptr);
jfaManagerCore->ReleaseSession();
ChipLogError(JointFabric, "OnSendICACSRRequestFailure: %s\n", chip::ErrorStr(error));
}
CHIP_ERROR JFAManager::SendCommissioningComplete()
{
GeneralCommissioning::Commands::CommissioningComplete::Type request;
if (!mExchangeMgr)
{
return CHIP_ERROR_UNINITIALIZED;
}
ChipLogProgress(JointFabric, "SendCommissioningComplete: invoke cluster command.");
Controller::ClusterBase cluster(*mExchangeMgr, mSessionHolder.Get().Value(), kRootEndpointId);
return cluster.InvokeCommand(request, this, OnCommissioningCompleteResponse, OnCommissioningCompleteFailure);
}
void JFAManager::OnCommissioningCompleteResponse(
void * context, const GeneralCommissioning::Commands::CommissioningCompleteResponse::DecodableType & data)
{
JFAManager * jfaManager = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManager != nullptr);
ChipLogProgress(JointFabric, "OnCommissioningCompleteResponse, Code=%u", to_underlying(data.errorCode));
if (data.errorCode != GeneralCommissioning::CommissioningErrorEnum::kOk)
{
ChipLogProgress(JointFabric, "Commssioning Failed (nodeId=%ld, isJCM = %d), Code=%u", jfaManager->mNodeId,
jfaManager->mOnConnectedAction == kJCMCommissioning, to_underlying(data.errorCode));
}
else
{
switch (jfaManager->mOnConnectedAction)
{
case kStandardCommissioningComplete: {
ChipLogProgress(JointFabric, "Standard Commissioning (nodeId=%ld) success", jfaManager->mNodeId);
break;
}
case kJCMCommissioning: {
ChipLogProgress(JointFabric, "Joint Commissioning Method (nodeId=%ld) success", jfaManager->mNodeId);
break;
}
}
}
jfaManager->ReleaseSession();
}
void JFAManager::OnCommissioningCompleteFailure(void * context, CHIP_ERROR error)
{
JFAManager * jfaManagerCore = static_cast<JFAManager *>(context);
VerifyOrDie(jfaManagerCore != nullptr);
jfaManagerCore->ReleaseSession();
ChipLogError(JointFabric, "Received failure response %s\n", chip::ErrorStr(error));
}
CHIP_ERROR JFAManager::GetIcacCsr(MutableByteSpan & icacCsr)
{
JFARpc * jfaRpc = GetJFARpc();
if (jfaRpc)
{
return jfaRpc->GetICACCSRForJF(icacCsr);
}
return CHIP_ERROR_UNINITIALIZED;
}