diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 1157992b489109..6d4857e02ba217 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -761,6 +761,20 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, const char * se CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & params) { + CommissioningParameters commissioningParams; + return PairDevice(remoteDeviceId, params, commissioningParams); +} + +CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams, + CommissioningParameters & commissioningParams) +{ + ReturnErrorOnFailure(EstablishPASEConnection(remoteDeviceId, rendezvousParams)); + return Commission(remoteDeviceId, commissioningParams); +} + +CHIP_ERROR DeviceCommissioner::EstablishPASEConnection(NodeId remoteDeviceId, RendezvousParameters & params) +{ + CHIP_ERROR err = CHIP_NO_ERROR; CommissioneeDeviceProxy * device = nullptr; Transport::PeerAddress peerAddress = Transport::PeerAddress::UDP(Inet::IPAddress::Any); @@ -804,30 +818,6 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam mDeviceBeingCommissioned = device; - // If the CSRNonce is passed in, using that else using a random one.. - if (params.HasCSRNonce()) - { - ReturnErrorOnFailure(device->SetCSRNonce(params.GetCSRNonce().Value())); - } - else - { - uint8_t mCSRNonce[kOpCSRNonceLength]; - Crypto::DRBG_get_bytes(mCSRNonce, sizeof(mCSRNonce)); - ReturnErrorOnFailure(device->SetCSRNonce(ByteSpan(mCSRNonce))); - } - - // If the AttestationNonce is passed in, using that else using a random one.. - if (params.HasAttestationNonce()) - { - ReturnErrorOnFailure(device->SetAttestationNonce(params.GetAttestationNonce().Value())); - } - else - { - uint8_t mAttestationNonce[kAttestationNonceLength]; - Crypto::DRBG_get_bytes(mAttestationNonce, sizeof(mAttestationNonce)); - ReturnErrorOnFailure(device->SetAttestationNonce(ByteSpan(mAttestationNonce))); - } - mIsIPRendezvous = (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle); device->Init(GetControllerDeviceInitParams(), remoteDeviceId, peerAddress, fabric->GetFabricIndex()); @@ -835,8 +825,6 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam err = device->GetPairing().MessageDispatch().Init(mSystemState->SessionMgr()); SuccessOrExit(err); - mSystemState->SystemLayer()->StartTimer(chip::System::Clock::Milliseconds32(kSessionEstablishmentTimeout), - OnSessionEstablishmentTimeoutCallback, this); if (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle) { device->SetAddress(params.GetPeerAddress().GetIPAddress()); @@ -874,9 +862,10 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam err = device->GetPairing().Pair(params.GetPeerAddress(), params.GetSetupPINCode(), keyID, exchangeCtxt, this); SuccessOrExit(err); - // Immediately persist the updted mNextKeyID value + // Immediately persist the updated mNextKeyID value // TODO maybe remove FreeRendezvousSession() since mNextKeyID is always persisted immediately PersistNextKeyId(); + mCommissioningStage = kSecurePairing; exit: if (err != CHIP_NO_ERROR) @@ -897,6 +886,58 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam return err; } +CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId, CommissioningParameters & params) +{ + // TODO(cecille): Can we get rid of mDeviceBeingCommissioned and use the remote id instead? Would require storing the + // commissioning stage in the device. + CommissioneeDeviceProxy * device = mDeviceBeingCommissioned; + if (device->GetDeviceId() != remoteDeviceId || (!device->IsSecureConnected() && !device->IsSessionSetupInProgress())) + { + ChipLogError(Controller, "Invalid device for commissioning" ChipLogFormatX64, ChipLogValueX64(remoteDeviceId)); + return CHIP_ERROR_INCORRECT_STATE; + } + if (mCommissioningStage != CommissioningStage::kSecurePairing) + { + ChipLogError(Controller, "Commissioning already in progress - not restarting"); + return CHIP_ERROR_INCORRECT_STATE; + } + // If the CSRNonce is passed in, using that else using a random one.. + if (params.HasCSRNonce()) + { + ReturnErrorOnFailure(device->SetCSRNonce(params.GetCSRNonce().Value())); + } + else + { + uint8_t mCSRNonce[kOpCSRNonceLength]; + Crypto::DRBG_get_bytes(mCSRNonce, sizeof(mCSRNonce)); + ReturnErrorOnFailure(device->SetCSRNonce(ByteSpan(mCSRNonce))); + } + + // If the AttestationNonce is passed in, using that else using a random one.. + if (params.HasAttestationNonce()) + { + ReturnErrorOnFailure(device->SetAttestationNonce(params.GetAttestationNonce().Value())); + } + else + { + uint8_t mAttestationNonce[kAttestationNonceLength]; + Crypto::DRBG_get_bytes(mAttestationNonce, sizeof(mAttestationNonce)); + ReturnErrorOnFailure(device->SetAttestationNonce(ByteSpan(mAttestationNonce))); + } + + mSystemState->SystemLayer()->StartTimer(chip::System::Clock::Milliseconds32(kSessionEstablishmentTimeout), + OnSessionEstablishmentTimeoutCallback, this); + if (device->IsSecureConnected()) + { + AdvanceCommissioningStage(CHIP_NO_ERROR); + } + else + { + mRunCommissioningAfterConnection = true; + } + return CHIP_NO_ERROR; +} + CHIP_ERROR DeviceCommissioner::StopPairing(NodeId remoteDeviceId) { VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE); @@ -981,21 +1022,29 @@ void DeviceCommissioner::OnSessionEstablished() // TODO: Add code to receive OpCSR from the device, and process the signing request // For IP rendezvous, this is sent as part of the state machine. - bool usingLegacyFlowWithImmediateStart = !mIsIPRendezvous; - - if (usingLegacyFlowWithImmediateStart) + if (mRunCommissioningAfterConnection) { - err = SendCertificateChainRequestCommand(mDeviceBeingCommissioned, CertificateType::kPAI); - if (err != CHIP_NO_ERROR) + mRunCommissioningAfterConnection = false; + bool usingLegacyFlowWithImmediateStart = !mIsIPRendezvous; + if (usingLegacyFlowWithImmediateStart) { - ChipLogError(Ble, "Failed in sending 'Certificate Chain request' command to the device: err %s", ErrorStr(err)); - OnSessionEstablishmentError(err); - return; + err = SendCertificateChainRequestCommand(mDeviceBeingCommissioned, CertificateType::kPAI); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Ble, "Failed in sending 'Certificate Chain request' command to the device: err %s", ErrorStr(err)); + OnSessionEstablishmentError(err); + return; + } + } + else + { + AdvanceCommissioningStage(CHIP_NO_ERROR); } } else { - AdvanceCommissioningStage(CHIP_NO_ERROR); + ChipLogProgress(Controller, "OnPairingComplete"); + mPairingDelegate->OnPairingComplete(CHIP_NO_ERROR); } } diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 32606dc11a4dcd..998bfd9b5ffa75 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -490,9 +491,46 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, * in the Init() call. * * @param[in] remoteDeviceId The remote device Id. - * @param[in] params The Rendezvous connection parameters + * @param[in] rendezvousParams The Rendezvous connection parameters + * @param[in] commssioningParams The commissioning parameters (uses defualt if not supplied) */ - CHIP_ERROR PairDevice(NodeId remoteDeviceId, RendezvousParameters & params); + CHIP_ERROR PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams); + CHIP_ERROR PairDevice(NodeId remoteDeviceId, RendezvousParameters & rendezvousParams, + CommissioningParameters & commissioningParams); + + /** + * @brief + * Start establishing a PASE connection with a node for the purposes of commissioning. + * Commissioners that wish to use the auto-commissioning functions should use the + * supplied "PairDevice" functions above to automatically establish a connection then + * perform commissioning. This function is intended to be use by commissioners that + * are not using the supplied auto-commissioner. + * + * This function is non-blocking. PASE is established once the DevicePairingDelegate + * receives the OnPairingComplete call. + * + * PASE connections can only be established with nodes that have their commissioning + * window open. The PASE connection will fail if this window is not open and the + * OnPairingComplete will be called with an error. + * + * @param[in] remoteDeviceId The remote device Id. + * @param[in] rendezvousParams The Rendezvous connection parameters + */ + CHIP_ERROR EstablishPASEConnection(NodeId remoteDeviceId, RendezvousParameters & params); + + /** + * @brief + * Start the auto-commissioning process on a node after establishing a PASE connection. + * This function is intended to be used in conjunction with the EstablishPASEConnection + * function. It can be called either before or after the DevicePairingDelegate receives + * the OnPairingComplete call. Commissioners that want to perform simple auto-commissioning + * should use the supplied "PairDevice" functions above, which will establish the PASE + * connection and commission automatically. + * + * @param[in] remoteDeviceId The remote device Id. + * @param[in] params The commissioning parameters + */ + CHIP_ERROR Commission(NodeId remoteDeviceId, CommissioningParameters & params); CHIP_ERROR GetDeviceBeingCommissioned(NodeId deviceId, CommissioneeDeviceProxy ** device); @@ -628,6 +666,7 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, bool mPairedDevicesUpdated; CommissioningStage mCommissioningStage = CommissioningStage::kSecurePairing; + bool mRunCommissioningAfterConnection = false; BitMapObjectPool mCommissioneeDevicePool; diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 2c5a8d7af593c4..5364aceebd9698 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -185,18 +185,19 @@ JNI_METHOD(void, pairDevice) ChipLogProgress(Controller, "pairDevice() called with device ID, connection object, and pincode"); - RendezvousParameters params = RendezvousParameters() - .SetSetupPINCode(pinCode) + RendezvousParameters rendezvousParams = RendezvousParameters() + .SetSetupPINCode(pinCode) #if CONFIG_NETWORK_LAYER_BLE - .SetConnectionObject(reinterpret_cast(connObj)) + .SetConnectionObject(reinterpret_cast(connObj)) #endif - .SetPeerAddress(Transport::PeerAddress::BLE()); + .SetPeerAddress(Transport::PeerAddress::BLE()); + CommissioningParameters commissioningParams = CommissioningParameters(); if (csrNonce != nullptr) { JniByteArray jniCsrNonce(env, csrNonce); - params.SetCSRNonce(jniCsrNonce.byteSpan()); + commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } - err = wrapper->Controller()->PairDevice(deviceId, params); + err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); if (err != CHIP_NO_ERROR) { @@ -221,16 +222,17 @@ JNI_METHOD(void, pairDeviceWithAddress) ChipLogError(Controller, "Failed to parse IP address."), JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, CHIP_ERROR_INVALID_ARGUMENT)); - RendezvousParameters params = RendezvousParameters() - .SetDiscriminator(discriminator) - .SetSetupPINCode(pinCode) - .SetPeerAddress(Transport::PeerAddress::UDP(addr, port)); + RendezvousParameters rendezvousParams = RendezvousParameters() + .SetDiscriminator(discriminator) + .SetSetupPINCode(pinCode) + .SetPeerAddress(Transport::PeerAddress::UDP(addr, port)); + CommissioningParameters commissioningParams = CommissioningParameters(); if (csrNonce != nullptr) { JniByteArray jniCsrNonce(env, csrNonce); - params.SetCSRNonce(jniCsrNonce.byteSpan()); + commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } - err = wrapper->Controller()->PairDevice(deviceId, params); + err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); if (err != CHIP_NO_ERROR) { diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index 789e5855d92289..c16ea817b4c092 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -112,6 +112,10 @@ ChipError::StorageType pychip_DeviceController_ConnectBLE(chip::Controller::Devi ChipError::StorageType pychip_DeviceController_ConnectIP(chip::Controller::DeviceCommissioner * devCtrl, const char * peerAddrStr, uint32_t setupPINCode, chip::NodeId nodeid); ChipError::StorageType pychip_DeviceController_CloseSession(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid); +ChipError::StorageType pychip_DeviceController_EstablishPASESessionIP(chip::Controller::DeviceCommissioner * devCtrl, + const char * peerAddrStr, uint32_t setupPINCode, + chip::NodeId nodeid); +ChipError::StorageType pychip_DeviceController_Commission(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid); ChipError::StorageType pychip_DeviceController_DiscoverCommissionableNodesLongDiscriminator(chip::Controller::DeviceCommissioner * devCtrl, @@ -346,6 +350,23 @@ ChipError::StorageType pychip_DeviceController_CloseSession(chip::Controller::De { return pychip_GetConnectedDeviceByNodeId(devCtrl, nodeid, CloseSessionCallback); } +ChipError::StorageType pychip_DeviceController_EstablishPASESessionIP(chip::Controller::DeviceCommissioner * devCtrl, + const char * peerAddrStr, uint32_t setupPINCode, + chip::NodeId nodeid) +{ + chip::Inet::IPAddress peerAddr; + chip::Transport::PeerAddress addr; + RendezvousParameters params = chip::RendezvousParameters().SetSetupPINCode(setupPINCode); + VerifyOrReturnError(chip::Inet::IPAddress::FromString(peerAddrStr, peerAddr), CHIP_ERROR_INVALID_ARGUMENT.AsInteger()); + addr.SetTransportType(chip::Transport::Type::kUdp).SetIPAddress(peerAddr); + params.SetPeerAddress(addr).SetDiscriminator(0); + return devCtrl->EstablishPASEConnection(nodeid, params).AsInteger(); +} +ChipError::StorageType pychip_DeviceController_Commission(chip::Controller::DeviceCommissioner * devCtrl, chip::NodeId nodeid) +{ + CommissioningParameters params; + return devCtrl->Commission(nodeid, params).AsInteger(); +} ChipError::StorageType pychip_DeviceController_DiscoverAllCommissionableNodes(chip::Controller::DeviceCommissioner * devCtrl) { diff --git a/src/controller/python/chip-device-ctrl.py b/src/controller/python/chip-device-ctrl.py index 03b31548d1c2e0..54bfef54b96b42 100755 --- a/src/controller/python/chip-device-ctrl.py +++ b/src/controller/python/chip-device-ctrl.py @@ -200,6 +200,8 @@ def __init__(self, rendezvousAddr=None, controllerNodeId=0, bluetoothAdapter=Non "close-ble", "close-session", "resolve", + "paseonly", + "commission", "zcl", "zclread", "zclsubscribe", @@ -491,6 +493,58 @@ def ConnectFromSetupPayload(self, setupPayload, nodeid): print(f"Unable to connect: {ex}") return -1 + def do_paseonly(self, line): + """ + paseonly -ip [] + + TODO: Add more methods to connect to device (like cert for auth, and IP + for connection) + """ + + try: + args = shlex.split(line) + if len(args) <= 1: + print("Usage:") + self.do_help("paseonly") + return + + nodeid = random.randint(1, 1000000) # Just a random number + if len(args) == 4: + nodeid = int(args[3]) + print("Device is assigned with nodeid = {}".format(nodeid)) + + if args[0] == "-ip" and len(args) >= 3: + self.devCtrl.EstablishPASESessionIP(args[1].encode( + "utf-8"), int(args[2]), nodeid) + else: + print("Usage:") + self.do_help("paseonly") + return + print( + "Device temporary node id (**this does not match spec**): {}".format(nodeid)) + except Exception as ex: + print(str(ex)) + return + + def do_commission(self, line): + """ + commission nodeid + + Runs commissioning on a device that has been connected with paseonly + """ + try: + args = shlex.split(line) + if len(args) != 1: + print("Usage:") + self.do_help("commission") + return + + nodeid = int(args[0]) + self.devCtrl.Commission(nodeid) + except Exception as ex: + print(str(ex)) + return + def do_connect(self, line): """ connect -ip [] diff --git a/src/controller/python/chip/ChipDeviceCtrl.py b/src/controller/python/chip/ChipDeviceCtrl.py index a993c2e2d4760e..b0fed9d9a99f04 100644 --- a/src/controller/python/chip/ChipDeviceCtrl.py +++ b/src/controller/python/chip/ChipDeviceCtrl.py @@ -199,6 +199,26 @@ def CloseSession(self, nodeid): self.devCtrl, nodeid) ) + def EstablishPASESessionIP(self, ipaddr, setupPinCode, nodeid): + self.state = DCState.RENDEZVOUS_ONGOING + return self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_EstablishPASESessionIP( + self.devCtrl, ipaddr, setupPinCode, nodeid) + ) + + def Commission(self, nodeid): + self._ChipStack.CallAsync( + lambda: self._dmLib.pychip_DeviceController_Commission( + self.devCtrl, nodeid) + ) + # Wait up to 5 additional seconds for the commissioning complete event + if not self._ChipStack.commissioningCompleteEvent.isSet(): + self._ChipStack.commissioningCompleteEvent.wait(5.0) + if not self._ChipStack.commissioningCompleteEvent.isSet(): + # Error 50 is a timeout + return False + return self._ChipStack.commissioningEventRes == 0 + def ConnectIP(self, ipaddr, setupPinCode, nodeid): # IP connection will run through full commissioning, so we need to wait # for the commissioning complete event, not just any callback. @@ -653,6 +673,11 @@ def _InitLib(self): self._dmLib.pychip_DeviceController_ConnectIP.argtypes = [ c_void_p, c_char_p, c_uint32, c_uint64] + + self._dmLib.pychip_DeviceController_Commission.argtypes = [ + c_void_p, c_uint64] + self._dmLib.pychip_DeviceController_Commission.restype = c_uint32 + self._dmLib.pychip_DeviceController_DiscoverAllCommissionableNodes.argtypes = [ c_void_p] self._dmLib.pychip_DeviceController_DiscoverAllCommissionableNodes.restype = c_uint32 @@ -677,6 +702,12 @@ def _InitLib(self): c_void_p] self._dmLib.pychip_DeviceController_DiscoverCommissionableNodesCommissioningEnabled.restype = c_uint32 + self._dmLib.pychip_DeviceController_EstablishPASESessionIP.argtypes = [ + c_void_p, c_char_p, c_uint32, c_uint64] + self._dmLib.pychip_DeviceController_EstablishPASESessionIP.restype = c_uint32 + + self._dmLib.pychip_DeviceController_DiscoverAllCommissionableNodes.argtypes = [ + c_void_p] self._dmLib.pychip_DeviceController_PrintDiscoveredDevices.argtypes = [ c_void_p] diff --git a/src/protocols/secure_channel/RendezvousParameters.h b/src/protocols/secure_channel/RendezvousParameters.h index 295daff2644ff5..043ad223626059 100644 --- a/src/protocols/secure_channel/RendezvousParameters.h +++ b/src/protocols/secure_channel/RendezvousParameters.h @@ -48,28 +48,12 @@ class RendezvousParameters bool HasPeerAddress() const { return mPeerAddress.IsInitialized(); } Transport::PeerAddress GetPeerAddress() const { return mPeerAddress; } - const Optional GetCSRNonce() const { return mCSRNonce; } - const Optional GetAttestationNonce() const { return mAttestationNonce; } RendezvousParameters & SetPeerAddress(const Transport::PeerAddress & peerAddress) { mPeerAddress = peerAddress; return *this; } - // The lifetime of the buffer csrNonce is pointing to, should exceed the lifetime of RendezvousParameter object. - RendezvousParameters & SetCSRNonce(ByteSpan csrNonce) - { - mCSRNonce.SetValue(csrNonce); - return *this; - } - - // The lifetime of the buffer attestationNonce is pointing to, should exceed the lifetime of RendezvousParameter object. - RendezvousParameters & SetAttestationNonce(ByteSpan attestationNonce) - { - mAttestationNonce.SetValue(attestationNonce); - return *this; - } - bool HasDiscriminator() const { return mDiscriminator <= kMaxRendezvousDiscriminatorValue; } uint16_t GetDiscriminator() const { return mDiscriminator; } RendezvousParameters & SetDiscriminator(uint16_t discriminator) @@ -79,8 +63,6 @@ class RendezvousParameters } bool HasPASEVerifier() const { return mHasPASEVerifier; } - bool HasCSRNonce() const { return mCSRNonce.HasValue(); } - bool HasAttestationNonce() const { return mAttestationNonce.HasValue(); } const PASEVerifier & GetPASEVerifier() const { return mPASEVerifier; } RendezvousParameters & SetPASEVerifier(PASEVerifier & verifier) { @@ -113,8 +95,6 @@ class RendezvousParameters Transport::PeerAddress mPeerAddress; ///< the peer node address uint32_t mSetupPINCode = 0; ///< the target peripheral setup PIN Code uint16_t mDiscriminator = UINT16_MAX; ///< the target peripheral discriminator - Optional mCSRNonce; ///< CSR Nonce passed by the commissioner - Optional mAttestationNonce; ///< Attestation Nonce passed by the commissioner PASEVerifier mPASEVerifier; bool mHasPASEVerifier = false; @@ -125,4 +105,31 @@ class RendezvousParameters #endif // CONFIG_NETWORK_LAYER_BLE }; +class CommissioningParameters +{ +public: + bool HasCSRNonce() const { return mCSRNonce.HasValue(); } + bool HasAttestationNonce() const { return mAttestationNonce.HasValue(); } + const Optional GetCSRNonce() const { return mCSRNonce; } + const Optional GetAttestationNonce() const { return mAttestationNonce; } + + // The lifetime of the buffer csrNonce is pointing to, should exceed the lifetime of CommissioningParameters object. + CommissioningParameters & SetCSRNonce(ByteSpan csrNonce) + { + mCSRNonce.SetValue(csrNonce); + return *this; + } + + // The lifetime of the buffer attestationNonce is pointing to, should exceed the lifetime of CommissioningParameters object. + CommissioningParameters & SetAttestationNonce(ByteSpan attestationNonce) + { + mAttestationNonce.SetValue(attestationNonce); + return *this; + } + +private: + Optional mCSRNonce; ///< CSR Nonce passed by the commissioner + Optional mAttestationNonce; ///< Attestation Nonce passed by the commissioner +}; + } // namespace chip