Skip to content

Commit

Permalink
Allow for split commissioner / admin topology (#14261)
Browse files Browse the repository at this point in the history
It is possible to implement Matter commissioning such that a commissioner
node is not on any fabric, and delegates operational communication and
invocation of the CommissioningComplete command to a different
administrator node. In this configuration, the commissioner only
communicates by PASE and may not ever have received operational
credentials. However, such an architecture is not currently supported, as
commissioner instances must self-commission to a fabric as a part of
initialization.

To support such a split commissioner / admin architecture, this amends the
commissioner to accept initialization without operational credentials. In
this configuration, the commissioner is capable of conducting the
commissioning procedure until these are needed. When it is found that
credentials aren't available, the commissioner gracefully discontinues
commissioning.

As part of this change set, fabric index is also no longer specified as
part of controller or commissioner initialization.  chip-tool had 
previously specified a fabric index and reused this as fabric ID.  That
is brittle though because it is not always practical for chip-tool to
know the fabric index ahead of time before initialization.

Instead, the operational key pair and NOC chain are optionally passed to
commissioner / controller initialization.  If passed, these are added or
merged into the fabric table.  Merge works by identifying an existing,
matching fabric by comparing root public key and fabric ID.  Once the new
certificates are merged or added, the controller / commissioner extracts
the fabric index from the fabric table.  

Fixes #13501
  • Loading branch information
msandstedt authored and pull[bot] committed Feb 10, 2024
1 parent 251b565 commit 8266660
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 88 deletions.
72 changes: 44 additions & 28 deletions examples/chip-tool/commands/common/CHIPCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

using DeviceControllerFactory = chip::Controller::DeviceControllerFactory;

constexpr chip::FabricId kIdentityNullFabricId = chip::kUndefinedFabricId;
constexpr chip::FabricId kIdentityAlphaFabricId = 1;
constexpr chip::FabricId kIdentityBetaFabricId = 2;
constexpr chip::FabricId kIdentityGammaFabricId = 3;
Expand All @@ -48,9 +49,10 @@ CHIP_ERROR CHIPCommand::Run()

chip::Controller::FactoryInitParams factoryInitParams;
factoryInitParams.fabricStorage = &mFabricStorage;
factoryInitParams.listenPort = static_cast<uint16_t>(mDefaultStorage.GetListenPort() + CurrentCommissionerIndex());
factoryInitParams.listenPort = static_cast<uint16_t>(mDefaultStorage.GetListenPort() + CurrentCommissionerId());
ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().Init(factoryInitParams));

ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityNull, kIdentityNullFabricId));
ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityAlpha, kIdentityAlphaFabricId));
ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityBeta, kIdentityBetaFabricId));
ReturnLogErrorOnFailure(InitializeCommissioner(kIdentityGamma, kIdentityGammaFabricId));
Expand All @@ -65,6 +67,7 @@ CHIP_ERROR CHIPCommand::Run()
// since the CHIP thread and event queue have been stopped, preventing any thread
// races.
//
ReturnLogErrorOnFailure(ShutdownCommissioner(kIdentityNull));
ReturnLogErrorOnFailure(ShutdownCommissioner(kIdentityAlpha));
ReturnLogErrorOnFailure(ShutdownCommissioner(kIdentityBeta));
ReturnLogErrorOnFailure(ShutdownCommissioner(kIdentityGamma));
Expand Down Expand Up @@ -99,7 +102,8 @@ void CHIPCommand::StopTracing()
void CHIPCommand::SetIdentity(const char * identity)
{
std::string name = std::string(identity);
if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0)
if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0 &&
name.compare(kIdentityNull) != 0)
{
ChipLogError(chipTool, "Unknown commissioner name: %s. Supported names are [%s, %s, %s]", name.c_str(), kIdentityAlpha,
kIdentityBeta, kIdentityGamma);
Expand All @@ -112,7 +116,8 @@ void CHIPCommand::SetIdentity(const char * identity)
std::string CHIPCommand::GetIdentity()
{
std::string name = mCommissionerName.HasValue() ? mCommissionerName.Value() : kIdentityAlpha;
if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0)
if (name.compare(kIdentityAlpha) != 0 && name.compare(kIdentityBeta) != 0 && name.compare(kIdentityGamma) != 0 &&
name.compare(kIdentityNull) != 0)
{
ChipLogError(chipTool, "Unknown commissioner name: %s. Supported names are [%s, %s, %s]", name.c_str(), kIdentityAlpha,
kIdentityBeta, kIdentityGamma);
Expand All @@ -122,27 +127,34 @@ std::string CHIPCommand::GetIdentity()
return name;
}

uint16_t CHIPCommand::CurrentCommissionerIndex()
chip::FabricId CHIPCommand::CurrentCommissionerId()
{
uint16_t index = 0;
chip::FabricId id;

std::string name = GetIdentity();
if (name.compare(kIdentityAlpha) == 0)
{
index = kIdentityAlphaFabricId;
id = kIdentityAlphaFabricId;
}
else if (name.compare(kIdentityBeta) == 0)
{
index = kIdentityBetaFabricId;
id = kIdentityBetaFabricId;
}
else if (name.compare(kIdentityGamma) == 0)
{
index = kIdentityGammaFabricId;
id = kIdentityGammaFabricId;
}
else if (name.compare(kIdentityNull) == 0)
{
id = kIdentityNullFabricId;
}
else
{
VerifyOrDieWithMsg(false, chipTool, "Unknown commissioner name: %s. Supported names are [%s, %s, %s]", name.c_str(),
kIdentityAlpha, kIdentityBeta, kIdentityGamma);
}

VerifyOrDieWithMsg(index != 0, chipTool, "Unknown commissioner name: %s. Supported names are [%s, %s, %s]", name.c_str(),
kIdentityAlpha, kIdentityBeta, kIdentityGamma);
return index;
return id;
}

chip::Controller::DeviceCommissioner & CHIPCommand::CurrentCommissioner()
Expand Down Expand Up @@ -172,28 +184,32 @@ CHIP_ERROR CHIPCommand::InitializeCommissioner(std::string key, chip::FabricId f
VerifyOrReturnError(icac.Alloc(chip::Controller::kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);
VerifyOrReturnError(rcac.Alloc(chip::Controller::kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY);

chip::MutableByteSpan nocSpan(noc.Get(), chip::Controller::kMaxCHIPDERCertLength);
chip::MutableByteSpan icacSpan(icac.Get(), chip::Controller::kMaxCHIPDERCertLength);
chip::MutableByteSpan rcacSpan(rcac.Get(), chip::Controller::kMaxCHIPDERCertLength);

chip::Crypto::P256Keypair ephemeralKey;
ReturnLogErrorOnFailure(ephemeralKey.Initialize());

// TODO - OpCreds should only be generated for pairing command
// store the credentials in persistent storage, and
// generate when not available in the storage.
ReturnLogErrorOnFailure(mCommissionerStorage.Init(key.c_str()));
ReturnLogErrorOnFailure(mCredIssuerCmds->InitializeCredentialsIssuer(mCommissionerStorage));
ReturnLogErrorOnFailure(mCredIssuerCmds->GenerateControllerNOCChain(mCommissionerStorage.GetLocalNodeId(), fabricId,
ephemeralKey, rcacSpan, icacSpan, nocSpan));
if (fabricId != chip::kUndefinedFabricId)
{

// TODO - OpCreds should only be generated for pairing command
// store the credentials in persistent storage, and
// generate when not available in the storage.
ReturnLogErrorOnFailure(mCommissionerStorage.Init(key.c_str()));
ReturnLogErrorOnFailure(mCredIssuerCmds->InitializeCredentialsIssuer(mCommissionerStorage));

chip::MutableByteSpan nocSpan(noc.Get(), chip::Controller::kMaxCHIPDERCertLength);
chip::MutableByteSpan icacSpan(icac.Get(), chip::Controller::kMaxCHIPDERCertLength);
chip::MutableByteSpan rcacSpan(rcac.Get(), chip::Controller::kMaxCHIPDERCertLength);

ReturnLogErrorOnFailure(ephemeralKey.Initialize());
ReturnLogErrorOnFailure(mCredIssuerCmds->GenerateControllerNOCChain(mCommissionerStorage.GetLocalNodeId(), fabricId,
ephemeralKey, rcacSpan, icacSpan, nocSpan));
commissionerParams.operationalKeypair = &ephemeralKey;
commissionerParams.controllerRCAC = rcacSpan;
commissionerParams.controllerICAC = icacSpan;
commissionerParams.controllerNOC = nocSpan;
}

commissionerParams.storageDelegate = &mCommissionerStorage;
commissionerParams.fabricIndex = static_cast<chip::FabricIndex>(fabricId);
commissionerParams.operationalCredentialsDelegate = mCredIssuerCmds->GetCredentialIssuer();
commissionerParams.operationalKeypair = &ephemeralKey;
commissionerParams.controllerRCAC = rcacSpan;
commissionerParams.controllerICAC = icacSpan;
commissionerParams.controllerNOC = nocSpan;
commissionerParams.controllerVendorId = chip::VendorId::TestVendor1;

ReturnLogErrorOnFailure(DeviceControllerFactory::GetInstance().SetupCommissioner(commissionerParams, *(commissioner.get())));
Expand Down
12 changes: 11 additions & 1 deletion examples/chip-tool/commands/common/CHIPCommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ class PersistentStorage;
constexpr const char kIdentityAlpha[] = "alpha";
constexpr const char kIdentityBeta[] = "beta";
constexpr const char kIdentityGamma[] = "gamma";
// The null fabric commissioner is a commissioner that isn't on a fabric.
// This is a legal configuration in which the commissioner delegates
// operational communication and invocation of the commssioning complete
// command to a separate on-fabric administrator node.
//
// The null-fabric-commissioner identity is provided here to demonstrate the
// commissioner portion of such an architecture. The null-fabric-commissioner
// can carry a commissioning flow up until the point of operational channel
// (CASE) communcation.
constexpr const char kIdentityNull[] = "null-fabric-commissioner";

class CHIPCommand : public Command
{
Expand Down Expand Up @@ -98,7 +108,7 @@ class CHIPCommand : public Command
private:
CHIP_ERROR InitializeCommissioner(std::string key, chip::FabricId fabricId);
CHIP_ERROR ShutdownCommissioner(std::string key);
uint16_t CurrentCommissionerIndex();
chip::FabricId CurrentCommissionerId();
std::map<std::string, std::unique_ptr<ChipDeviceCommissioner>> mCommissioners;
chip::Optional<char *> mCommissionerName;

Expand Down
1 change: 1 addition & 0 deletions src/app/CASESessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void CASESessionManager::ReleaseSession(PeerId peerId)

CHIP_ERROR CASESessionManager::ResolveDeviceAddress(FabricInfo * fabric, NodeId nodeId)
{
VerifyOrReturnError(fabric != nullptr, CHIP_ERROR_INCORRECT_STATE);
return mConfig.dnsResolver->ResolveNodeId(fabric->GetPeerIdForNode(nodeId), Inet::IPAddressType::kAny,
Dnssd::Resolver::CacheBypass::On);
}
Expand Down
84 changes: 55 additions & 29 deletions src/controller/CHIPDeviceController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ CHIP_ERROR DeviceController::Init(ControllerInitParams params)
VerifyOrReturnError(params.operationalCredentialsDelegate != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mOperationalCredentialsDelegate = params.operationalCredentialsDelegate;

mFabricIndex = params.fabricIndex;
ReturnErrorOnFailure(ProcessControllerNOCChain(params));

mFabricInfo = params.systemState->Fabrics()->FindFabricWithIndex(mFabricIndex);
VerifyOrReturnError(mFabricInfo != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
mVendorId = params.controllerVendorId;
if (params.operationalKeypair != nullptr || !params.controllerNOC.empty() || !params.controllerRCAC.empty())
{
ReturnErrorOnFailure(ProcessControllerNOCChain(params));
}

DeviceProxyInitParams deviceInitParams = {
.sessionManager = params.systemState->SessionMgr(),
Expand Down Expand Up @@ -179,12 +179,13 @@ CHIP_ERROR DeviceController::Init(ControllerInitParams params)
CHIP_ERROR DeviceController::ProcessControllerNOCChain(const ControllerInitParams & params)
{
FabricInfo newFabric;

ReturnErrorCodeIf(params.operationalKeypair == nullptr, CHIP_ERROR_INVALID_ARGUMENT);
newFabric.SetOperationalKeypair(params.operationalKeypair);

constexpr uint32_t chipCertAllocatedLen = kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> chipCert;
Credentials::P256PublicKeySpan rootPublicKey;
FabricId fabricId;

ReturnErrorOnFailure(newFabric.SetOperationalKeypair(params.operationalKeypair));
newFabric.SetVendorId(params.controllerVendorId);

ReturnErrorCodeIf(!chipCert.Alloc(chipCertAllocatedLen), CHIP_ERROR_NO_MEMORY);
MutableByteSpan chipCertSpan(chipCert.Get(), chipCertAllocatedLen);
Expand All @@ -208,19 +209,27 @@ CHIP_ERROR DeviceController::ProcessControllerNOCChain(const ControllerInitParam

ReturnErrorOnFailure(ConvertX509CertToChipCert(params.controllerNOC, chipCertSpan));
ReturnErrorOnFailure(newFabric.SetNOCCert(chipCertSpan));
newFabric.SetVendorId(params.controllerVendorId);
ReturnErrorOnFailure(ExtractFabricIdFromCert(chipCertSpan, &fabricId));

FabricInfo * fabric = params.systemState->Fabrics()->FindFabricWithIndex(mFabricIndex);
ReturnErrorCodeIf(fabric == nullptr, CHIP_ERROR_INCORRECT_STATE);

ReturnErrorOnFailure(fabric->SetFabricInfo(newFabric));
mLocalId = fabric->GetPeerId();
mVendorId = fabric->GetVendorId();
ReturnErrorOnFailure(newFabric.GetRootPubkey(rootPublicKey));
mFabricInfo = params.systemState->Fabrics()->FindFabric(rootPublicKey, fabricId);
if (mFabricInfo != nullptr)
{
ReturnErrorOnFailure(mFabricInfo->SetFabricInfo(newFabric));
}
else
{
FabricIndex fabricIndex;
ReturnErrorOnFailure(params.systemState->Fabrics()->AddNewFabric(newFabric, &fabricIndex));
mFabricInfo = params.systemState->Fabrics()->FindFabricWithIndex(fabricIndex);
ReturnErrorCodeIf(mFabricInfo == nullptr, CHIP_ERROR_INCORRECT_STATE);
}

mFabricId = fabric->GetFabricId();
mLocalId = mFabricInfo->GetPeerId();
mFabricId = mFabricInfo->GetFabricId();

ChipLogProgress(Controller, "Joined the fabric at index %d. Compressed fabric ID is: 0x" ChipLogFormatX64, mFabricIndex,
ChipLogValueX64(GetCompressedFabricId()));
ChipLogProgress(Controller, "Joined the fabric at index %d. Compressed fabric ID is: 0x" ChipLogFormatX64,
mFabricInfo->GetFabricIndex(), ChipLogValueX64(GetCompressedFabricId()));

return CHIP_NO_ERROR;
}
Expand All @@ -235,7 +244,10 @@ CHIP_ERROR DeviceController::Shutdown()

mStorageDelegate = nullptr;

mSystemState->Fabrics()->ReleaseFabricIndex(mFabricIndex);
if (mFabricInfo != nullptr)
{
mFabricInfo->Reset();
}
mSystemState->Release();
mSystemState = nullptr;

Expand Down Expand Up @@ -263,7 +275,7 @@ bool DeviceController::DoesDevicePairingExist(const PeerId & deviceId)

void DeviceController::ReleaseOperationalDevice(NodeId remoteDeviceId)
{
VerifyOrReturn(mState == State::Initialized,
VerifyOrReturn(mState == State::Initialized && mFabricInfo != nullptr,
ChipLogError(Controller, "ReleaseOperationalDevice was called in incorrect state"));
mCASESessionManager->ReleaseSession(mFabricInfo->GetPeerIdForNode(remoteDeviceId));
}
Expand All @@ -273,7 +285,13 @@ void DeviceController::OnFirstMessageDeliveryFailed(const SessionHandle & sessio
VerifyOrReturn(mState == State::Initialized,
ChipLogError(Controller, "OnFirstMessageDeliveryFailed was called in incorrect state"));
VerifyOrReturn(session->GetSessionType() == Transport::Session::SessionType::kSecure);
UpdateDevice(session->AsSecureSession()->GetPeerNodeId());
CHIP_ERROR err = UpdateDevice(session->AsSecureSession()->GetPeerNodeId());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller,
"OnFirstMessageDeliveryFailed was called, but UpdateDevice did not succeed (%" CHIP_ERROR_FORMAT ")",
err.Format());
}
}

CHIP_ERROR DeviceController::InitializePairedDeviceList()
Expand Down Expand Up @@ -773,11 +791,8 @@ CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, Re

uint16_t keyID = 0;

FabricInfo * fabric = mSystemState->Fabrics()->FindFabricWithIndex(mFabricIndex);

VerifyOrExit(mState == State::Initialized, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(mDeviceBeingCommissioned == nullptr, err = CHIP_ERROR_INCORRECT_STATE);
VerifyOrExit(fabric != nullptr, err = CHIP_ERROR_INCORRECT_STATE);

err = InitializePairedDeviceList();
SuccessOrExit(err);
Expand Down Expand Up @@ -808,7 +823,10 @@ CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, Re

mIsIPRendezvous = (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle);

device->Init(GetControllerDeviceInitParams(), remoteDeviceId, peerAddress, fabric->GetFabricIndex());
{
FabricIndex fabricIndex = mFabricInfo != nullptr ? mFabricInfo->GetFabricIndex() : kUndefinedFabricIndex;
device->Init(GetControllerDeviceInitParams(), remoteDeviceId, peerAddress, fabricIndex);
}

if (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle)
{
Expand Down Expand Up @@ -1221,8 +1239,10 @@ CHIP_ERROR DeviceCommissioner::ProcessOpCSR(DeviceProxy * proxy, const ByteSpan

mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(proxy->GetDeviceId());

FabricInfo * fabric = mSystemState->Fabrics()->FindFabricWithIndex(mFabricIndex);
mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(fabric->GetFabricId());
if (mFabricInfo != nullptr)
{
mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(mFabricInfo->GetFabricId());
}

return mOperationalCredentialsDelegate->GenerateNOCChain(NOCSRElements, AttestationSignature, dac, ByteSpan(), ByteSpan(),
&mDeviceNOCChainCallback);
Expand Down Expand Up @@ -1821,7 +1841,13 @@ void DeviceCommissioner::PerformCommissioningStep(DeviceProxy * proxy, Commissio
}
break;
case CommissioningStage::kFindOperational: {
UpdateDevice(proxy->GetDeviceId());
CHIP_ERROR err = UpdateDevice(proxy->GetDeviceId());
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Unable to proceed to operational discovery\n");
CommissioningStageComplete(err);
return;
}
}
break;
case CommissioningStage::kSendComplete: {
Expand Down
20 changes: 9 additions & 11 deletions src/controller/CHIPDeviceController.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,6 @@ struct ControllerInitParams
ByteSpan controllerRCAC;

uint16_t controllerVendorId;

FabricIndex fabricIndex = kMinValidFabricIndex;
FabricId fabricId = kUndefinedFabricId;
};

class DLL_EXPORT DevicePairingDelegate
Expand Down Expand Up @@ -220,7 +217,7 @@ class DLL_EXPORT DeviceController : public SessionRecoveryDelegate,
virtual CHIP_ERROR GetConnectedDevice(NodeId deviceId, Callback::Callback<OnDeviceConnected> * onConnection,
Callback::Callback<OnDeviceConnectionFailure> * onFailure)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mState == State::Initialized && mFabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE);
return mCASESessionManager->FindOrEstablishSession(mFabricInfo->GetPeerIdForNode(deviceId), onConnection, onFailure);
}

Expand All @@ -234,7 +231,7 @@ class DLL_EXPORT DeviceController : public SessionRecoveryDelegate,
*/
CHIP_ERROR UpdateDevice(NodeId deviceId)
{
VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mState == State::Initialized && mFabricInfo != nullptr, CHIP_ERROR_INCORRECT_STATE);
return mCASESessionManager->ResolveDeviceAddress(mFabricInfo, deviceId);
}

Expand Down Expand Up @@ -351,9 +348,9 @@ class DLL_EXPORT DeviceController : public SessionRecoveryDelegate,
SerializableU64Set<kNumMaxPairedDevices> mPairedDevices;
bool mPairedDevicesInitialized;

PeerId mLocalId = PeerId();
FabricId mFabricId = kUndefinedFabricId;
FabricInfo * mFabricInfo;
PeerId mLocalId = PeerId();
FabricId mFabricId = kUndefinedFabricId;
FabricInfo * mFabricInfo = nullptr;

PersistentStorageDelegate * mStorageDelegate = nullptr;
#if CHIP_DEVICE_CONFIG_ENABLE_DNSSD
Expand All @@ -372,8 +369,6 @@ class DLL_EXPORT DeviceController : public SessionRecoveryDelegate,

void PersistNextKeyId();

FabricIndex mFabricIndex = kMinValidFabricIndex;

OperationalCredentialsDelegate * mOperationalCredentialsDelegate;

SessionIDAllocator mIDAllocator;
Expand Down Expand Up @@ -404,7 +399,10 @@ class DLL_EXPORT DeviceController : public SessionRecoveryDelegate,

CHIP_ERROR OpenCommissioningWindowInternal();

PeerId GetPeerIdWithCommissioningWindowOpen() { return mFabricInfo->GetPeerIdForNode(mDeviceWithCommissioningWindowOpen); }
PeerId GetPeerIdWithCommissioningWindowOpen()
{
return mFabricInfo ? mFabricInfo->GetPeerIdForNode(mDeviceWithCommissioningWindowOpen) : PeerId();
}

// TODO - Support opening commissioning window simultaneously on multiple devices
Callback::Callback<OnOpenCommissioningWindow> * mCommissioningWindowCallback = nullptr;
Expand Down
Loading

0 comments on commit 8266660

Please sign in to comment.