diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 04765b096a8a8c..f7d26e5fb6da14 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -1117,6 +1117,27 @@ void DeviceCommissioner::OnDeviceNOCChainGeneration(void * context, CHIP_ERROR s commissioner->CommissioningStageComplete(status, report); } +CHIP_ERROR DeviceCommissioner::IssueNOCChain(const ByteSpan & NOCSRElements, NodeId nodeId, + chip::Callback::Callback * callback) +{ + MATTER_TRACE_EVENT_SCOPE("IssueNOCChain", "DeviceCommissioner"); + VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE); + + ChipLogProgress(Controller, "Getting certificate chain for the device on fabric idx %u", + static_cast(mFabricInfo->GetFabricIndex())); + + mOperationalCredentialsDelegate->SetNodeIdForNextNOCRequest(nodeId); + + if (mFabricInfo != nullptr) + { + mOperationalCredentialsDelegate->SetFabricIdForNextNOCRequest(mFabricInfo->GetFabricId()); + } + + // Note: attestationSignature, attestationChallenge, DAC, PAI are not used by existing OperationalCredentialsIssuer. + return mOperationalCredentialsDelegate->GenerateChipNOCChain(NOCSRElements, ByteSpan(), ByteSpan(), ByteSpan(), ByteSpan(), + ByteSpan(), callback); +} + CHIP_ERROR DeviceCommissioner::ProcessCSR(DeviceProxy * proxy, const ByteSpan & NOCSRElements, const ByteSpan & AttestationSignature, const ByteSpan & dac, const ByteSpan & pai, const ByteSpan & csrNonce) diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 5e38c42905457a..7880e22ca89a34 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -611,6 +611,12 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, // Commissioner will establish new device connections after PASE. OperationalDeviceProxy * GetDeviceSession(const PeerId & peerId) override; + // Issue an NOC chain using the associated OperationalCredentialsDelegate. + // NOTE: This is only valid assuming that `mOperationalCredentialsDelegate` is what is desired + // to issue the NOC chain. + CHIP_ERROR IssueNOCChain(const ByteSpan & NOCSRElements, NodeId nodeId, + chip::Callback::Callback * callback); + private: DevicePairingDelegate * mPairingDelegate; diff --git a/src/controller/ExampleOperationalCredentialsIssuer.cpp b/src/controller/ExampleOperationalCredentialsIssuer.cpp index 14ad78463ce225..0ce6f497c8733f 100644 --- a/src/controller/ExampleOperationalCredentialsIssuer.cpp +++ b/src/controller/ExampleOperationalCredentialsIssuer.cpp @@ -263,6 +263,103 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan return CHIP_NO_ERROR; } +CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateChipNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, + const ByteSpan & attestationSignature, + const ByteSpan & attestationChallenge, const ByteSpan & DAC, + const ByteSpan & PAI, + Callback::Callback * onCompletion) +{ + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + // At this point, Credential issuer may wish to validate the CSR information + (void) attestationChallenge; + (void) csrNonce; + + NodeId assignedId; + if (mNodeIdRequested) + { + assignedId = mNextRequestedNodeId; + mNodeIdRequested = false; + } + else + { + assignedId = mNextAvailableNodeId++; + } + + ChipLogProgress(Controller, "Verifying Certificate Signing Request"); + TLVReader reader; + reader.Init(csrElements); + + if (reader.GetType() == kTLVType_NotSpecified) + { + ReturnErrorOnFailure(reader.Next()); + } + + VerifyOrReturnError(reader.GetType() == kTLVType_Structure, CHIP_ERROR_WRONG_TLV_TYPE); + VerifyOrReturnError(reader.GetTag() == AnonymousTag(), CHIP_ERROR_UNEXPECTED_TLV_ELEMENT); + + TLVType containerType; + ReturnErrorOnFailure(reader.EnterContainer(containerType)); + ReturnErrorOnFailure(reader.Next(kTLVType_ByteString, TLV::ContextTag(1))); + + ByteSpan csr(reader.GetReadPoint(), reader.GetLength()); + reader.ExitContainer(containerType); + + P256PublicKey pubkey; + ReturnErrorOnFailure(VerifyCertificateSigningRequest(csr.data(), csr.size(), pubkey)); + + chip::Platform::ScopedMemoryBuffer noc; + ReturnErrorCodeIf(!noc.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan nocSpan(noc.Get(), kMaxCHIPDERCertLength); + + chip::Platform::ScopedMemoryBuffer icac; + ReturnErrorCodeIf(!icac.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan icacSpan(icac.Get(), kMaxCHIPDERCertLength); + + chip::Platform::ScopedMemoryBuffer rcac; + ReturnErrorCodeIf(!rcac.Alloc(kMaxCHIPDERCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan rcacSpan(rcac.Get(), kMaxCHIPDERCertLength); + + ReturnErrorOnFailure( + GenerateNOCChainAfterValidation(assignedId, mNextFabricId, chip::kUndefinedCATs, pubkey, rcacSpan, icacSpan, nocSpan)); + + // TODO(#13825): Should always generate some IPK. Using a temporary fixed value until APIs are plumbed in to set it end-to-end + // TODO: Force callers to set IPK if used before GenerateNOCChain will succeed. + ByteSpan defaultIpkSpan = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + + // The below static assert validates a key assumption in types used (needed for public API conformance) + static_assert(CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES == kAES_CCM128_Key_Length, "IPK span sizing must match"); + + // Prepare IPK to be sent back. A more fully-fledged operational credentials delegate + // would obtain a suitable key per fabric. + uint8_t ipkValue[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES]; + Crypto::AesCcm128KeySpan ipkSpan(ipkValue); + + ReturnErrorCodeIf(defaultIpkSpan.size() != sizeof(ipkValue), CHIP_ERROR_INTERNAL); + memcpy(&ipkValue[0], defaultIpkSpan.data(), defaultIpkSpan.size()); + + chip::Platform::ScopedMemoryBuffer chipNoc; + ReturnErrorCodeIf(!chipNoc.Alloc(Credentials::kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan chipNocSpan(chipNoc.Get(), Credentials::kMaxCHIPCertLength); + + chip::Platform::ScopedMemoryBuffer chipIcac; + ReturnErrorCodeIf(!chipIcac.Alloc(Credentials::kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan chipIcacSpan(chipIcac.Get(), Credentials::kMaxCHIPCertLength); + + chip::Platform::ScopedMemoryBuffer chipRcac; + ReturnErrorCodeIf(!chipRcac.Alloc(Credentials::kMaxCHIPCertLength), CHIP_ERROR_NO_MEMORY); + MutableByteSpan chipRcacSpan(chipRcac.Get(), Credentials::kMaxCHIPCertLength); + + ReturnErrorOnFailure(ConvertX509CertToChipCert(nocSpan, chipNocSpan)); + ReturnErrorOnFailure(ConvertX509CertToChipCert(icacSpan, chipIcacSpan)); + ReturnErrorOnFailure(ConvertX509CertToChipCert(rcacSpan, chipRcacSpan)); + + // Callback onto commissioner. + ChipLogProgress(Controller, "Providing certificate chain to the commissioner"); + onCompletion->mCall(onCompletion->mContext, CHIP_NO_ERROR, chipNocSpan, chipIcacSpan, chipRcacSpan, MakeOptional(ipkSpan), + Optional()); + return CHIP_NO_ERROR; +} + CHIP_ERROR ExampleOperationalCredentialsIssuer::GetRandomOperationalNodeId(NodeId * aNodeId) { for (int i = 0; i < 10; ++i) diff --git a/src/controller/ExampleOperationalCredentialsIssuer.h b/src/controller/ExampleOperationalCredentialsIssuer.h index a85684cf6957e2..009b7423732be5 100644 --- a/src/controller/ExampleOperationalCredentialsIssuer.h +++ b/src/controller/ExampleOperationalCredentialsIssuer.h @@ -59,6 +59,10 @@ class DLL_EXPORT ExampleOperationalCredentialsIssuer : public OperationalCredent const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, Callback::Callback * onCompletion) override; + CHIP_ERROR GenerateChipNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, const ByteSpan & attestationSignature, + const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion) override; + void SetNodeIdForNextNOCRequest(NodeId nodeId) override { mNextRequestedNodeId = nodeId; diff --git a/src/controller/OperationalCredentialsDelegate.h b/src/controller/OperationalCredentialsDelegate.h index e34b728b1ee9da..d4f80bb7e7a117 100644 --- a/src/controller/OperationalCredentialsDelegate.h +++ b/src/controller/OperationalCredentialsDelegate.h @@ -66,6 +66,34 @@ class DLL_EXPORT OperationalCredentialsDelegate const ByteSpan & DAC, const ByteSpan & PAI, Callback::Callback * onCompletion) = 0; + /** + * @brief + * This function generates an operational certificate chain for a remote device that is being commissioned. + * The API generates the certificate in CHIP cert form. + * + * The delegate is expected to use the certificate authority whose certificate + * is returned in `GetRootCACertificate()` API call. + * + * The delegate will call `onCompletion` when the NOC certificate chain is ready. + * + * @param[in] csrElements CSR elements as per specifications section 11.18.5.6. NOCSR Elements. + * @param[in] csrNonce CSR nonce as described in 6.4.6.1 + * @param[in] attestationSignature Attestation signature as per specifications section 11.22.7.6. CSRResponse Command. + * @param[in] attestationChallenge Attestation challenge as per 11.18.5.7 + * @param[in] DAC Device attestation certificate received from the device being commissioned + * @param[in] PAI Product Attestation Intermediate certificate + * @param[in] onCompletion Callback handler to provide generated NOC chain to the caller of GenerateNOCChain() + * + * @return CHIP_ERROR CHIP_NO_ERROR on success, or corresponding error code. + */ + virtual CHIP_ERROR GenerateChipNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, + const ByteSpan & attestationSignature, const ByteSpan & attestationChallenge, + const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion) + { + return CHIP_ERROR_NOT_IMPLEMENTED; + } + /** * This function sets the node ID for which the next NOC Chain would be requested. The node ID is * provided as a hint, and the delegate implementation may chose to ignore it and pick node ID of diff --git a/src/controller/python/BUILD.gn b/src/controller/python/BUILD.gn index 46e67a6bc98896..5b90275de38dc6 100644 --- a/src/controller/python/BUILD.gn +++ b/src/controller/python/BUILD.gn @@ -47,6 +47,7 @@ shared_library("ChipDeviceCtrl") { if (chip_controller) { sources += [ "ChipCommissionableNodeController-ScriptBinding.cpp", + "ChipDeviceController-IssueNocChain.cpp", "ChipDeviceController-ScriptBinding.cpp", "ChipDeviceController-ScriptDevicePairingDelegate.cpp", "ChipDeviceController-ScriptDevicePairingDelegate.h", diff --git a/src/controller/python/ChipDeviceController-IssueNocChain.cpp b/src/controller/python/ChipDeviceController-IssueNocChain.cpp new file mode 100644 index 00000000000000..73df98d2d7b179 --- /dev/null +++ b/src/controller/python/ChipDeviceController-IssueNocChain.cpp @@ -0,0 +1,73 @@ +/* + * + * Copyright (c) 2022 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 +#include + +#include +#include +#include + +typedef void PyObject; + +using namespace chip; + +extern "C" { + +typedef void (*pychip_DeviceController_IssueNOCChainCallbackPythonCallback)( + PyObject * context, ChipError::StorageType status, const uint8_t * noc, size_t nocLen, const uint8_t * icac, size_t icacLen, + const uint8_t * rcac, size_t rcacLen, const uint8_t * ipk, size_t ipkLen, NodeId adminSubject); + +static pychip_DeviceController_IssueNOCChainCallbackPythonCallback pychip_DeviceController_IssueNOCChainCallbackPythonCallbackFunct; + +void pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback( + pychip_DeviceController_IssueNOCChainCallbackPythonCallback callback) +{ + pychip_DeviceController_IssueNOCChainCallbackPythonCallbackFunct = callback; +} + +ChipError::StorageType pychip_DeviceController_IssueNOCChain(chip::Controller::DeviceCommissioner * devCtrl, + PyObject * pythonContext, uint8_t * NOCSRElements, + size_t NOCSRElementsLen, NodeId nodeId); +} + +void pychip_DeviceController_IssueNOCChainCallback(void * context, CHIP_ERROR status, const ByteSpan & noc, const ByteSpan & icac, + const ByteSpan & rcac, Optional ipk, + Optional adminSubject) +{ + if (pychip_DeviceController_IssueNOCChainCallbackPythonCallbackFunct != nullptr) + { + Crypto::AesCcm128KeySpan ipkData; + ipkData = ipk.ValueOr(Crypto::AesCcm128KeySpan()); + pychip_DeviceController_IssueNOCChainCallbackPythonCallbackFunct( + context, status.AsInteger(), noc.data(), noc.size(), icac.data(), icac.size(), rcac.data(), rcac.size(), ipkData.data(), + ipk.HasValue() ? ipkData.size() : 0, adminSubject.ValueOr(kUndefinedNodeId)); + } +} + +ChipError::StorageType pychip_DeviceController_IssueNOCChain(chip::Controller::DeviceCommissioner * devCtrl, + PyObject * pythonContext, uint8_t * NOCSRElements, + size_t NOCSRElementsLen, NodeId nodeId) +{ + return devCtrl + ->IssueNOCChain( + ByteSpan(NOCSRElements, NOCSRElementsLen), nodeId, + /* Note: Memory leak here. This is a quick and a bit dirty PoC */ + new Callback::Callback(pychip_DeviceController_IssueNOCChainCallback, pythonContext)) + .AsInteger(); +} diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index dfeee36db313cc..1e18fbe0e10de6 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -115,6 +115,7 @@ ChipError::StorageType pychip_DeviceController_GetAddressAndPort(chip::Controlle ChipError::StorageType pychip_DeviceController_GetCompressedFabricId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outFabricId); ChipError::StorageType pychip_DeviceController_GetFabricId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outFabricId); +ChipError::StorageType pychip_DeviceController_GetNodeId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outNodeId); // Rendezvous ChipError::StorageType pychip_DeviceController_ConnectBLE(chip::Controller::DeviceCommissioner * devCtrl, uint16_t discriminator, @@ -274,6 +275,12 @@ ChipError::StorageType pychip_DeviceController_GetFabricId(chip::Controller::Dev return CHIP_NO_ERROR.AsInteger(); } +ChipError::StorageType pychip_DeviceController_GetNodeId(chip::Controller::DeviceCommissioner * devCtrl, uint64_t * outNodeId) +{ + *outNodeId = devCtrl->GetNodeId(); + return CHIP_NO_ERROR.AsInteger(); +} + const char * pychip_DeviceController_ErrorToString(ChipError::StorageType err) { return chip::ErrorStr(CHIP_ERROR(err)); diff --git a/src/controller/python/OpCredsBinding.cpp b/src/controller/python/OpCredsBinding.cpp index bea10d93dcd21d..f049fac4798322 100644 --- a/src/controller/python/OpCredsBinding.cpp +++ b/src/controller/python/OpCredsBinding.cpp @@ -86,6 +86,14 @@ class OperationalCredentialsAdapter : public OperationalCredentialsDelegate onCompletion); } + CHIP_ERROR GenerateChipNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, const ByteSpan & attestationSignature, + const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion) override + { + return mExampleOpCredsIssuer.GenerateChipNOCChain(csrElements, csrNonce, attestationSignature, attestationChallenge, DAC, + PAI, onCompletion); + } + void SetNodeIdForNextNOCRequest(NodeId nodeId) override { mExampleOpCredsIssuer.SetNodeIdForNextNOCRequest(nodeId); } void SetFabricIdForNextNOCRequest(FabricId fabricId) override { mExampleOpCredsIssuer.SetFabricIdForNextNOCRequest(fabricId); } diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index 6e90b396b2aafb..8b3d6c00465f0c 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -38,6 +38,7 @@ from .clusters import ClusterObjects as ClusterObjects from .clusters import Objects as GeneratedObjects from .clusters.CHIPClusters import * +from . import clusters as Clusters import enum import threading import typing @@ -60,6 +61,20 @@ _DeviceAvailableFunct = CFUNCTYPE(None, c_void_p, c_uint32) +_IssueNOCChainCallbackPythonCallbackFunct = CFUNCTYPE( + None, py_object, c_uint32, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_uint64) + + +@_IssueNOCChainCallbackPythonCallbackFunct +def _IssueNOCChainCallbackPythonCallback(devCtrl, status: int, noc: c_void_p, nocLen: int, icac: c_void_p, icacLen: int, rcac: c_void_p, rcacLen: int, ipk: c_void_p, ipkLen: int, adminSubject: int): + nocBytes = string_at(noc, nocLen)[:] + icacBytes = string_at(icac, icacLen)[:] + rcacBytes = string_at(rcac, rcacLen)[:] + ipkBytes = None + if ipkLen > 0: + ipkBytes = string_at(ipk, ipkLen)[:] + devCtrl.NOCChainCallback(status, nocBytes, icacBytes, rcacBytes, ipkBytes, adminSubject) + # This is a fix for WEAV-429. Jay Logue recommends revisiting this at a later # date to allow for truly multiple instances so this is temporary. @@ -95,6 +110,8 @@ def __init__(self, opCredsContext: ctypes.c_void_p, fabricId: int, fabricIndex: self._InitLib() + self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback(_IssueNOCChainCallbackPythonCallback) + devCtrl = c_void_p(None) res = self._ChipStack.Call( @@ -328,6 +345,11 @@ def CommissionIP(self, ipaddr, setupPinCode, nodeid): return False return self._ChipStack.commissioningEventRes == 0 + def NOCChainCallback(self, status, noc, icac, rcac, ipk, adminSubject): + self._ChipStack.callbackRes = (noc, icac, rcac, ipk, adminSubject) + self._ChipStack.completeEvent.set() + return + def CommissionThread(self, discriminator, setupPinCode, nodeId, threadOperationalDataset: bytes): ''' Commissions a Thread device over BLE ''' @@ -488,6 +510,21 @@ def GetFabricId(self): else: raise self._ChipStack.ErrorToException(res) + def GetNodeId(self): + self.CheckIsActive() + + nodeid = c_uint64(0) + + res = self._ChipStack.Call( + lambda: self._dmLib.pychip_DeviceController_GetNodeId( + self.devCtrl, pointer(nodeid)) + ) + + if res == 0: + return nodeid.value + else: + raise self._ChipStack.ErrorToException(res) + def GetClusterHandler(self): self.CheckIsActive() @@ -940,6 +977,14 @@ def SetBlockingCB(self, blockingCB): self._ChipStack.blockingCB = blockingCB + def IssueNOCChain(self, csr: Clusters.OperationalCredentials.Commands.CSRResponse, nodeId: int): + self.CheckIsActive() + + return self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_IssueNOCChain( + self.devCtrl, py_object(self), csr.NOCSRElements, len(csr.NOCSRElements), nodeId) + ) + # ----- Private Members ----- def _InitLib(self): if self._dmLib is None: @@ -1068,3 +1113,12 @@ def _InitLib(self): self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport.argtypes = [ c_uint8] self._dmLib.pychip_SetTestCommissionerSimulateFailureOnReport.restype = c_bool + + self._dmLib.pychip_DeviceController_IssueNOCChain.argtypes = [ + c_void_p, py_object, c_char_p, c_size_t, c_uint64 + ] + self._dmLib.pychip_DeviceController_IssueNOCChain.restype = c_uint32 + + self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback.argtypes = [ + _IssueNOCChainCallbackPythonCallbackFunct] + self._dmLib.pychip_DeviceController_SetIssueNOCChainCallbackPythonCallback.restype = None