From d32e8e205b9d1173272247b3a568f6719cd54c92 Mon Sep 17 00:00:00 2001 From: Abdul Samad Date: Fri, 19 Jul 2024 16:52:50 -0500 Subject: [PATCH] Add OCW verifier, pass params between fabric bridge & admin (#34209) * Add verifier arg to `CommissioningWindowOpener` * Update RPC, pass OCW params between fabric bridge and admin * Separate out OCW with passcode and verifier, bring back stable API Co-authored-by: Boris Zbarsky * Use `CHIP_ERROR_BUFFER_TOO_SMALL`, Avoid SuccessOrExit, args for example Co-authored-by: Andrei Litvin * Use fluent builder pattern for OCW params, and some fixes Co-authored-by: Andrei Litvin * Add arg tests for new methods in `CommissioningWindowOpener` * Move callback to params Co-authored-by: Andrei Litvin * Enforce param values, Add mDiscriminator, Update docs Co-authored-by: Boris Zbarsky * Use `HasDiscriminator()`/`HasNodeId()`, return `ERROR_INVALID_ARGUMENT` --------- Co-authored-by: Boris Zbarsky Co-authored-by: Andrei Litvin --- .../OpenCommissioningWindowCommand.cpp | 12 +- examples/common/pigweed/BUILD.gn | 1 + .../protos/fabric_admin_service.options | 2 + .../pigweed/protos/fabric_admin_service.proto | 10 +- .../common/pigweed/rpc_services/FabricAdmin.h | 3 +- .../OpenCommissioningWindowCommand.cpp | 35 +++- .../pairing/OpenCommissioningWindowCommand.h | 11 + examples/fabric-admin/rpc/RpcServer.cpp | 23 ++- .../fabric-bridge-app/linux/RpcClient.cpp | 39 +++- .../linux/include/RpcClient.h | 20 +- examples/fabric-bridge-app/linux/main.cpp | 30 ++- src/controller/BUILD.gn | 1 + src/controller/CHIPDeviceController.h | 7 +- src/controller/CommissioningWindowOpener.cpp | 131 ++++++++---- src/controller/CommissioningWindowOpener.h | 45 +++- src/controller/CommissioningWindowParams.h | 190 +++++++++++++++++ .../ChipDeviceController-ScriptBinding.cpp | 11 +- src/controller/tests/BUILD.gn | 1 + .../tests/TestCommissioningWindowOpener.cpp | 192 ++++++++++++++++++ 19 files changed, 679 insertions(+), 85 deletions(-) create mode 100644 examples/common/pigweed/protos/fabric_admin_service.options create mode 100644 src/controller/CommissioningWindowParams.h create mode 100644 src/controller/tests/TestCommissioningWindowOpener.cpp diff --git a/examples/chip-tool/commands/pairing/OpenCommissioningWindowCommand.cpp b/examples/chip-tool/commands/pairing/OpenCommissioningWindowCommand.cpp index 3d4c296e7864fb..bc80e568b2bd7f 100644 --- a/examples/chip-tool/commands/pairing/OpenCommissioningWindowCommand.cpp +++ b/examples/chip-tool/commands/pairing/OpenCommissioningWindowCommand.cpp @@ -34,10 +34,14 @@ CHIP_ERROR OpenCommissioningWindowCommand::RunCommand() if (mCommissioningWindowOption == Controller::CommissioningWindowOpener::CommissioningWindowOption::kTokenWithRandomPIN) { SetupPayload ignored; - return mWindowOpener->OpenCommissioningWindow(mNodeId, System::Clock::Seconds16(mCommissioningWindowTimeout), mIteration, - mDiscriminator, NullOptional, NullOptional, - &mOnOpenCommissioningWindowCallback, ignored, - /* readVIDPIDAttributes */ true); + return mWindowOpener->OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(mNodeId) + .SetTimeout(mCommissioningWindowTimeout) + .SetIteration(mIteration) + .SetDiscriminator(mDiscriminator) + .SetReadVIDPIDAttributes(true) + .SetCallback(&mOnOpenCommissioningWindowCallback), + ignored); } ChipLogError(chipTool, "Unknown commissioning window option: %d", to_underlying(mCommissioningWindowOption)); diff --git a/examples/common/pigweed/BUILD.gn b/examples/common/pigweed/BUILD.gn index e523bea380c03f..6252f86e637890 100644 --- a/examples/common/pigweed/BUILD.gn +++ b/examples/common/pigweed/BUILD.gn @@ -82,6 +82,7 @@ pw_proto_library("button_service") { pw_proto_library("fabric_admin_service") { sources = [ "protos/fabric_admin_service.proto" ] + inputs = [ "protos/fabric_admin_service.options" ] deps = [ "$dir_pw_protobuf:common_protos" ] strip_prefix = "protos" prefix = "fabric_admin_service" diff --git a/examples/common/pigweed/protos/fabric_admin_service.options b/examples/common/pigweed/protos/fabric_admin_service.options new file mode 100644 index 00000000000000..9a65ae8a2b61d1 --- /dev/null +++ b/examples/common/pigweed/protos/fabric_admin_service.options @@ -0,0 +1,2 @@ +chip.rpc.DeviceCommissioningWindowInfo.verifier max_size:97 // kSpake2p_VerifierSerialized_Length +chip.rpc.DeviceCommissioningWindowInfo.salt max_size:32 // kSpake2p_Max_PBKDF_Salt_Length diff --git a/examples/common/pigweed/protos/fabric_admin_service.proto b/examples/common/pigweed/protos/fabric_admin_service.proto index e52fd2951ac0d7..7f6ec4f4995b12 100644 --- a/examples/common/pigweed/protos/fabric_admin_service.proto +++ b/examples/common/pigweed/protos/fabric_admin_service.proto @@ -5,8 +5,13 @@ import 'pw_protobuf_protos/common.proto'; package chip.rpc; // Define the message for a synchronized end device with necessary fields -message DeviceInfo { +message DeviceCommissioningWindowInfo { uint64 node_id = 1; + uint32 commissioning_timeout = 2; + uint32 discriminator = 3; + uint32 iterations = 4; + bytes salt = 5; + bytes verifier = 6; } // Define the response message to convey the status of the operation @@ -15,6 +20,5 @@ message OperationStatus { } service FabricAdmin { - rpc OpenCommissioningWindow(DeviceInfo) returns (OperationStatus){} + rpc OpenCommissioningWindow(DeviceCommissioningWindowInfo) returns (OperationStatus){} } - diff --git a/examples/common/pigweed/rpc_services/FabricAdmin.h b/examples/common/pigweed/rpc_services/FabricAdmin.h index 5254b9e9054a0c..14de9d50f60673 100644 --- a/examples/common/pigweed/rpc_services/FabricAdmin.h +++ b/examples/common/pigweed/rpc_services/FabricAdmin.h @@ -34,7 +34,8 @@ class FabricAdmin : public pw_rpc::nanopb::FabricAdmin::Service public: virtual ~FabricAdmin() = default; - virtual pw::Status OpenCommissioningWindow(const chip_rpc_DeviceInfo & request, chip_rpc_OperationStatus & response) + virtual pw::Status OpenCommissioningWindow(const chip_rpc_DeviceCommissioningWindowInfo & request, + chip_rpc_OperationStatus & response) { return pw::Status::Unimplemented(); } diff --git a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp index 93d1f6f51002f8..480cfbca35a20b 100644 --- a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp +++ b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.cpp @@ -33,11 +33,31 @@ CHIP_ERROR OpenCommissioningWindowCommand::RunCommand() if (mCommissioningWindowOption == Controller::CommissioningWindowOpener::CommissioningWindowOption::kTokenWithRandomPIN) { - SetupPayload ignored; - return mWindowOpener->OpenCommissioningWindow(mNodeId, System::Clock::Seconds16(mCommissioningWindowTimeout), mIteration, - mDiscriminator, NullOptional, NullOptional, - &mOnOpenCommissioningWindowCallback, ignored, - /* readVIDPIDAttributes */ true); + if (mVerifier.HasValue()) + { + VerifyOrReturnError(mSalt.HasValue(), CHIP_ERROR_INVALID_ARGUMENT); + return mWindowOpener->OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() + .SetNodeId(mNodeId) + .SetTimeout(mCommissioningWindowTimeout) + .SetIteration(mIteration) + .SetDiscriminator(mDiscriminator) + .SetVerifier(mVerifier.Value()) + .SetSalt(mSalt.Value()) + .SetCallback(&mOnOpenCommissioningWindowVerifierCallback)); + } + else + { + SetupPayload ignored; + return mWindowOpener->OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(mNodeId) + .SetTimeout(mCommissioningWindowTimeout) + .SetIteration(mIteration) + .SetDiscriminator(mDiscriminator) + .SetSalt(mSalt) + .SetReadVIDPIDAttributes(true) + .SetCallback(&mOnOpenCommissioningWindowCallback), + ignored); + } } ChipLogError(NotSpecified, "Unknown commissioning window option: %d", to_underlying(mCommissioningWindowOption)); @@ -58,6 +78,11 @@ void OpenCommissioningWindowCommand::OnOpenCommissioningWindowResponse(void * co OnOpenBasicCommissioningWindowResponse(context, remoteId, err); } +void OpenCommissioningWindowCommand::OnOpenCommissioningWindowVerifierResponse(void * context, NodeId remoteId, CHIP_ERROR err) +{ + OnOpenBasicCommissioningWindowResponse(context, remoteId, err); +} + void OpenCommissioningWindowCommand::OnOpenBasicCommissioningWindowResponse(void * context, NodeId remoteId, CHIP_ERROR err) { LogErrorOnFailure(err); diff --git a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h index 86e1b68c3d5f3f..09788507210aaf 100644 --- a/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h +++ b/examples/fabric-admin/commands/pairing/OpenCommissioningWindowCommand.h @@ -35,6 +35,7 @@ class OpenCommissioningWindowCommand : public CHIPCommand OpenCommissioningWindowCommand(CredentialIssuerCommands * credIssuerCommands) : CHIPCommand("open-commissioning-window", credIssuerCommands), mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this), + mOnOpenCommissioningWindowVerifierCallback(OnOpenCommissioningWindowVerifierResponse, this), mOnOpenBasicCommissioningWindowCallback(OnOpenBasicCommissioningWindowResponse, this) { AddArgument("node-id", 0, UINT64_MAX, &mNodeId, "Node to send command to."); @@ -47,6 +48,12 @@ class OpenCommissioningWindowCommand : public CHIPCommand &mIteration, "Number of PBKDF iterations to use to derive the verifier. Ignored if 'option' is 0."); AddArgument("discriminator", 0, 4096, &mDiscriminator, "Discriminator to use for advertising. Ignored if 'option' is 0."); AddArgument("timeout", 0, UINT16_MAX, &mTimeout, "Time, in seconds, before this command is considered to have timed out."); + AddArgument("salt", &mSalt, + "Salt payload encoded in hexadecimal. Random salt will be generated if absent. " + "This needs to be present if verifier is provided, corresponding to salt used for generating verifier"); + AddArgument("verifier", &mVerifier, + "PAKE Passcode verifier encoded in hexadecimal format. Will be generated from random setup pin and other " + "params if absent"); } void RegisterDelegate(CommissioningWindowDelegate * delegate) { mDelegate = delegate; } @@ -69,12 +76,16 @@ class OpenCommissioningWindowCommand : public CHIPCommand uint16_t mDiscriminator; chip::Optional mTimeout; + chip::Optional mSalt; + chip::Optional mVerifier; chip::Platform::UniquePtr mWindowOpener; static void OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, chip::SetupPayload payload); + static void OnOpenCommissioningWindowVerifierResponse(void * context, NodeId deviceId, CHIP_ERROR status); static void OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status); chip::Callback::Callback mOnOpenCommissioningWindowCallback; + chip::Callback::Callback mOnOpenCommissioningWindowVerifierCallback; chip::Callback::Callback mOnOpenBasicCommissioningWindowCallback; }; diff --git a/examples/fabric-admin/rpc/RpcServer.cpp b/examples/fabric-admin/rpc/RpcServer.cpp index 51ecb1ff6c13c5..d4979e5a27c427 100644 --- a/examples/fabric-admin/rpc/RpcServer.cpp +++ b/examples/fabric-admin/rpc/RpcServer.cpp @@ -37,14 +37,27 @@ namespace { class FabricAdmin final : public rpc::FabricAdmin { public: - pw::Status OpenCommissioningWindow(const chip_rpc_DeviceInfo & request, chip_rpc_OperationStatus & response) override + pw::Status OpenCommissioningWindow(const chip_rpc_DeviceCommissioningWindowInfo & request, + chip_rpc_OperationStatus & response) override { - NodeId nodeId = request.node_id; + NodeId nodeId = request.node_id; + uint32_t commissioningTimeout = request.commissioning_timeout; + uint32_t iterations = request.iterations; + uint32_t discriminator = request.discriminator; + + char saltHex[Crypto::kSpake2p_Max_PBKDF_Salt_Length * 2 + 1]; + Encoding::BytesToHex(request.salt.bytes, request.salt.size, saltHex, sizeof(saltHex), Encoding::HexFlags::kNullTerminate); + + char verifierHex[Crypto::kSpake2p_VerifierSerialized_Length * 2 + 1]; + Encoding::BytesToHex(request.verifier.bytes, request.verifier.size, verifierHex, sizeof(verifierHex), + Encoding::HexFlags::kNullTerminate); + ChipLogProgress(NotSpecified, "Received OpenCommissioningWindow request: 0x%lx", nodeId); - char command[64]; - snprintf(command, sizeof(command), "pairing open-commissioning-window %ld %d %d %d %d %d", nodeId, kRootEndpointId, - kEnhancedCommissioningMethod, kWindowTimeout, kIteration, kDiscriminator); + char command[512]; + snprintf(command, sizeof(command), "pairing open-commissioning-window %ld %d %d %d %d %d --salt hex:%s --verifier hex:%s", + nodeId, kRootEndpointId, kEnhancedCommissioningMethod, commissioningTimeout, iterations, discriminator, saltHex, + verifierHex); PushCommand(command); diff --git a/examples/fabric-bridge-app/linux/RpcClient.cpp b/examples/fabric-bridge-app/linux/RpcClient.cpp index 02916b87aaa06a..d2aef5d1d82e5e 100644 --- a/examples/fabric-bridge-app/linux/RpcClient.cpp +++ b/examples/fabric-bridge-app/linux/RpcClient.cpp @@ -95,12 +95,9 @@ CHIP_ERROR InitRpcClient(uint16_t rpcServerPort) return rpc::client::StartPacketProcessing(); } -CHIP_ERROR OpenCommissioningWindow(NodeId nodeId) +CHIP_ERROR OpenCommissioningWindow(chip_rpc_DeviceCommissioningWindowInfo device) { - ChipLogProgress(NotSpecified, "OpenCommissioningWindow with Node Id 0x:" ChipLogFormatX64, ChipLogValueX64(nodeId)); - - chip_rpc_DeviceInfo device; - device.node_id = nodeId; + ChipLogProgress(NotSpecified, "OpenCommissioningWindow with Node Id 0x" ChipLogFormatX64, ChipLogValueX64(device.node_id)); // The RPC call is kept alive until it completes. When a response is received, it will be logged by the handler // function and the call will complete. @@ -114,3 +111,35 @@ CHIP_ERROR OpenCommissioningWindow(NodeId nodeId) return WaitForResponse(call); } + +CHIP_ERROR +OpenCommissioningWindow(chip::Controller::CommissioningWindowPasscodeParams params) +{ + chip_rpc_DeviceCommissioningWindowInfo device; + device.node_id = params.GetNodeId(); + device.commissioning_timeout = params.GetTimeout().count(); + device.discriminator = params.GetDiscriminator(); + device.iterations = params.GetIteration(); + + return OpenCommissioningWindow(device); +} + +CHIP_ERROR +OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params) +{ + chip_rpc_DeviceCommissioningWindowInfo device; + device.node_id = params.GetNodeId(); + device.commissioning_timeout = params.GetTimeout().count(); + device.discriminator = params.GetDiscriminator(); + device.iterations = params.GetIteration(); + + VerifyOrReturnError(params.GetSalt().size() <= sizeof(device.salt.bytes), CHIP_ERROR_BUFFER_TOO_SMALL); + memcpy(device.salt.bytes, params.GetSalt().data(), params.GetSalt().size()); + device.salt.size = static_cast(params.GetSalt().size()); + + VerifyOrReturnError(params.GetVerifier().size() <= sizeof(device.verifier.bytes), CHIP_ERROR_BUFFER_TOO_SMALL); + memcpy(device.verifier.bytes, params.GetVerifier().data(), params.GetVerifier().size()); + device.verifier.size = static_cast(params.GetVerifier().size()); + + return OpenCommissioningWindow(device); +} diff --git a/examples/fabric-bridge-app/linux/include/RpcClient.h b/examples/fabric-bridge-app/linux/include/RpcClient.h index 34fa5c19de9349..e7e2cc5b48505c 100644 --- a/examples/fabric-bridge-app/linux/include/RpcClient.h +++ b/examples/fabric-bridge-app/linux/include/RpcClient.h @@ -18,6 +18,7 @@ #pragma once +#include #include constexpr uint16_t kFabricAdminServerPort = 33001; @@ -33,12 +34,25 @@ constexpr uint16_t kFabricAdminServerPort = 33001; CHIP_ERROR InitRpcClient(uint16_t rpcServerPort); /** - * Opens a commissioning window for a specified node. + * Opens a commissioning window for a specified node using setup PIN (passcode). * - * @param nodeId The identifier of the node for which the commissioning window should be opened. + * @param params Params for opening the commissioning window using passcode. * @return CHIP_ERROR An error code indicating the success or failure of the operation. * - CHIP_NO_ERROR: The RPC command was successfully processed. * - CHIP_ERROR_BUSY: Another commissioning window is currently in progress. * - CHIP_ERROR_INTERNAL: An internal error occurred. */ -CHIP_ERROR OpenCommissioningWindow(chip::NodeId nodeId); +CHIP_ERROR +OpenCommissioningWindow(chip::Controller::CommissioningWindowPasscodeParams params); + +/** + * Opens a commissioning window for a specified node using pre-computed PAKE passcode verifier. + * + * @param params Params for opening the commissioning window using verifier. + * @return CHIP_ERROR An error code indicating the success or failure of the operation. + * - CHIP_NO_ERROR: The RPC command was successfully sent. + * - CHIP_ERROR_BUSY: Another commissioning window is currently in progress. + * - CHIP_ERROR_INTERNAL: An internal error occurred. + */ +CHIP_ERROR +OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams params); diff --git a/examples/fabric-bridge-app/linux/main.cpp b/examples/fabric-bridge-app/linux/main.cpp index 0aa22b8445ee56..7e0a70bc82b18b 100644 --- a/examples/fabric-bridge-app/linux/main.cpp +++ b/examples/fabric-bridge-app/linux/main.cpp @@ -69,7 +69,11 @@ void BridgePollingThread() #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE else if (ch == 'o') { - CHIP_ERROR err = OpenCommissioningWindow(0x1234); + CHIP_ERROR err = OpenCommissioningWindow(chip::Controller::CommissioningWindowPasscodeParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetDiscriminator(3840) + .SetIteration(1000)); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to call OpenCommissioningWindow RPC: %" CHIP_ERROR_FORMAT, err.Format()); @@ -115,7 +119,7 @@ void AdministratorCommissioningCommandHandler::InvokeCommand(HandlerContext & ha using Protocols::InteractionModel::Status; EndpointId endpointId = handlerContext.mRequestPath.mEndpointId; - ChipLogProgress(NotSpecified, "Received command to open commissioning window on Endpoind: %d", endpointId); + ChipLogProgress(NotSpecified, "Received command to open commissioning window on Endpoint: %d", endpointId); if (handlerContext.mRequestPath.mCommandId != Commands::OpenCommissioningWindow::Id || endpointId == kRootEndpointId) { @@ -124,23 +128,37 @@ void AdministratorCommissioningCommandHandler::InvokeCommand(HandlerContext & ha } handlerContext.SetCommandHandled(); - Status status = Status::Success; + + Commands::OpenCommissioningWindow::DecodableType commandData; + if (DataModel::Decode(handlerContext.mPayload, commandData) != CHIP_NO_ERROR) + { + handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, Status::InvalidCommand); + return; + } + + Status status = Status::Failure; #if defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE Device * device = DeviceMgr().GetDevice(endpointId); // TODO: issues:#33784, need to make OpenCommissioningWindow synchronous - if (device != nullptr && OpenCommissioningWindow(device->GetNodeId()) == CHIP_NO_ERROR) + if (device != nullptr && + OpenCommissioningWindow(chip::Controller::CommissioningWindowVerifierParams() + .SetNodeId(device->GetNodeId()) + .SetTimeout(commandData.commissioningTimeout) + .SetDiscriminator(commandData.discriminator) + .SetIteration(commandData.iterations) + .SetSalt(commandData.salt) + .SetVerifier(commandData.PAKEPasscodeVerifier)) == CHIP_NO_ERROR) { ChipLogProgress(NotSpecified, "Commissioning window is now open"); + status = Status::Success; } else { - status = Status::Failure; ChipLogProgress(NotSpecified, "Commissioning window is failed to open"); } #else - status = Status::Failure; ChipLogProgress(NotSpecified, "Commissioning window failed to open: PW_RPC_FABRIC_BRIDGE_SERVICE not defined"); #endif // defined(PW_RPC_FABRIC_BRIDGE_SERVICE) && PW_RPC_FABRIC_BRIDGE_SERVICE diff --git a/src/controller/BUILD.gn b/src/controller/BUILD.gn index b06b2defecb8f2..b4e81158d497e8 100644 --- a/src/controller/BUILD.gn +++ b/src/controller/BUILD.gn @@ -74,6 +74,7 @@ static_library("controller") { "CommandSenderAllocator.h", "CommissioneeDeviceProxy.h", "CommissioningDelegate.h", + "CommissioningWindowParams.h", "DeviceDiscoveryDelegate.h", "DevicePairingDelegate.h", "InvokeInteraction.h", diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 1ab85ca0dc182b..1c4b490faa891a 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -243,9 +243,10 @@ class DLL_EXPORT DeviceController : public AbstractDnssdDiscoveryController * An error return from this function means that neither callback has been * called yet, and neither callback will be called in the future. */ - CHIP_ERROR GetConnectedDevice(NodeId peerNodeId, Callback::Callback * onConnection, - chip::Callback::Callback * onFailure, - TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload) + virtual CHIP_ERROR + GetConnectedDevice(NodeId peerNodeId, Callback::Callback * onConnection, + Callback::Callback * onFailure, + TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload) { VerifyOrReturnError(mState == State::Initialized, CHIP_ERROR_INCORRECT_STATE); mSystemState->CASESessionMgr()->FindOrEstablishSession(ScopedNodeId(peerNodeId, GetFabricIndex()), onConnection, onFailure, diff --git a/src/controller/CommissioningWindowOpener.cpp b/src/controller/CommissioningWindowOpener.cpp index 47666972137bcc..35011e69565c4a 100644 --- a/src/controller/CommissioningWindowOpener.cpp +++ b/src/controller/CommissioningWindowOpener.cpp @@ -43,11 +43,12 @@ CHIP_ERROR CommissioningWindowOpener::OpenBasicCommissioningWindow(NodeId device // Basic commissioning does not use the setup payload. - mCommissioningWindowOption = CommissioningWindowOption::kOriginalSetupCode; - mBasicCommissioningWindowCallback = callback; - mCommissioningWindowCallback = nullptr; - mNodeId = deviceId; - mCommissioningWindowTimeout = timeout; + mCommissioningWindowOption = CommissioningWindowOption::kOriginalSetupCode; + mBasicCommissioningWindowCallback = callback; + mCommissioningWindowCallback = nullptr; + mCommissioningWindowVerifierCallback = nullptr; + mNodeId = deviceId; + mCommissioningWindowTimeout = timeout; mNextStep = Step::kOpenCommissioningWindow; return mController->GetConnectedDevice(mNodeId, &mDeviceConnected, &mDeviceConnectionFailure); @@ -59,60 +60,74 @@ CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindow(NodeId deviceId, S Callback::Callback * callback, SetupPayload & payload, bool readVIDPIDAttributes) { - VerifyOrReturnError(mNextStep == Step::kAcceptCommissioningStart, CHIP_ERROR_INCORRECT_STATE); + return OpenCommissioningWindow(CommissioningWindowPasscodeParams() + .SetNodeId(deviceId) + .SetTimeout(timeout) + .SetIteration(iteration) + .SetDiscriminator(discriminator) + .SetSetupPIN(setupPIN) + .SetSalt(salt) + .SetReadVIDPIDAttributes(readVIDPIDAttributes) + .SetCallback(callback), + payload); +} - VerifyOrReturnError(kSpake2p_Min_PBKDF_Iterations <= iteration && iteration <= kSpake2p_Max_PBKDF_Iterations, +CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindow(const CommissioningWindowPasscodeParams & params, + SetupPayload & payload) +{ + VerifyOrReturnError(mNextStep == Step::kAcceptCommissioningStart, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(params.HasNodeId(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(params.HasDiscriminator(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(kSpake2p_Min_PBKDF_Iterations <= params.GetIteration() && + params.GetIteration() <= kSpake2p_Max_PBKDF_Iterations, + CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(!params.HasSalt() || + (params.GetSalt().size() >= kSpake2p_Min_PBKDF_Salt_Length && + params.GetSalt().size() <= kSpake2p_Max_PBKDF_Salt_Length), CHIP_ERROR_INVALID_ARGUMENT); - VerifyOrReturnError( - !salt.HasValue() || - (salt.Value().size() >= kSpake2p_Min_PBKDF_Salt_Length && salt.Value().size() <= kSpake2p_Max_PBKDF_Salt_Length), - CHIP_ERROR_INVALID_ARGUMENT); mSetupPayload = SetupPayload(); - if (setupPIN.HasValue()) + if (params.HasSetupPIN()) { - if (!SetupPayload::IsValidSetupPIN(setupPIN.Value())) - { - return CHIP_ERROR_INVALID_ARGUMENT; - } - + VerifyOrReturnError(SetupPayload::IsValidSetupPIN(params.GetSetupPIN()), CHIP_ERROR_INVALID_ARGUMENT); mCommissioningWindowOption = CommissioningWindowOption::kTokenWithProvidedPIN; - mSetupPayload.setUpPINCode = setupPIN.Value(); + mSetupPayload.setUpPINCode = params.GetSetupPIN(); } else { mCommissioningWindowOption = CommissioningWindowOption::kTokenWithRandomPIN; } - if (salt.HasValue()) + mSetupPayload.version = 0; + mDiscriminator.SetLongValue(params.GetDiscriminator()); + mSetupPayload.discriminator = mDiscriminator; + mSetupPayload.rendezvousInformation.SetValue(RendezvousInformationFlag::kOnNetwork); + + if (params.HasSalt()) { - memcpy(mPBKDFSaltBuffer, salt.Value().data(), salt.Value().size()); - mPBKDFSalt = ByteSpan(mPBKDFSaltBuffer, salt.Value().size()); + memcpy(mPBKDFSaltBuffer, params.GetSalt().data(), params.GetSalt().size()); + mPBKDFSalt = ByteSpan(mPBKDFSaltBuffer, params.GetSalt().size()); } else { ReturnErrorOnFailure(DRBG_get_bytes(mPBKDFSaltBuffer, sizeof(mPBKDFSaltBuffer))); mPBKDFSalt = ByteSpan(mPBKDFSaltBuffer); } + mPBKDFIterations = params.GetIteration(); - mSetupPayload.version = 0; - mSetupPayload.discriminator.SetLongValue(discriminator); - mSetupPayload.rendezvousInformation.SetValue(RendezvousInformationFlag::kOnNetwork); - - mCommissioningWindowCallback = callback; - mBasicCommissioningWindowCallback = nullptr; - mNodeId = deviceId; - mCommissioningWindowTimeout = timeout; - mPBKDFIterations = iteration; - - bool randomSetupPIN = !setupPIN.HasValue(); + bool randomSetupPIN = !params.HasSetupPIN(); ReturnErrorOnFailure( PASESession::GeneratePASEVerifier(mVerifier, mPBKDFIterations, mPBKDFSalt, randomSetupPIN, mSetupPayload.setUpPINCode)); - payload = mSetupPayload; + payload = mSetupPayload; + mCommissioningWindowCallback = params.GetCallback(); + mBasicCommissioningWindowCallback = nullptr; + mCommissioningWindowVerifierCallback = nullptr; + mNodeId = params.GetNodeId(); + mCommissioningWindowTimeout = params.GetTimeout(); - if (readVIDPIDAttributes) + if (params.GetReadVIDPIDAttributes()) { mNextStep = Step::kReadVID; } @@ -124,6 +139,35 @@ CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindow(NodeId deviceId, S return mController->GetConnectedDevice(mNodeId, &mDeviceConnected, &mDeviceConnectionFailure); } +CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindow(const CommissioningWindowVerifierParams & params) +{ + VerifyOrReturnError(mNextStep == Step::kAcceptCommissioningStart, CHIP_ERROR_INCORRECT_STATE); + VerifyOrReturnError(params.HasNodeId(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(params.HasDiscriminator(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(kSpake2p_Min_PBKDF_Iterations <= params.GetIteration() && + params.GetIteration() <= kSpake2p_Max_PBKDF_Iterations, + CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(params.GetSalt().size() >= kSpake2p_Min_PBKDF_Salt_Length && + params.GetSalt().size() <= kSpake2p_Max_PBKDF_Salt_Length, + CHIP_ERROR_INVALID_ARGUMENT); + memcpy(mPBKDFSaltBuffer, params.GetSalt().data(), params.GetSalt().size()); + mPBKDFSalt = ByteSpan(mPBKDFSaltBuffer, params.GetSalt().size()); + + ReturnErrorOnFailure(mVerifier.Deserialize(params.GetVerifier())); + mCommissioningWindowVerifierCallback = params.GetCallback(); + mBasicCommissioningWindowCallback = nullptr; + mCommissioningWindowCallback = nullptr; + mNodeId = params.GetNodeId(); + mCommissioningWindowTimeout = params.GetTimeout(); + mPBKDFIterations = params.GetIteration(); + mCommissioningWindowOption = CommissioningWindowOption::kTokenWithProvidedPIN; + mDiscriminator.SetLongValue(params.GetDiscriminator()); + + mNextStep = Step::kOpenCommissioningWindow; + + return mController->GetConnectedDevice(mNodeId, &mDeviceConnected, &mDeviceConnectionFailure); +} + CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindowInternal(Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) { @@ -142,7 +186,7 @@ CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindowInternal(Messaging: AdministratorCommissioning::Commands::OpenCommissioningWindow::Type request; request.commissioningTimeout = mCommissioningWindowTimeout.count(); request.PAKEPasscodeVerifier = serializedVerifierSpan; - request.discriminator = mSetupPayload.discriminator.GetLongValue(); + request.discriminator = mDiscriminator.GetLongValue(); request.iterations = mPBKDFIterations; request.salt = mPBKDFSalt; @@ -230,15 +274,19 @@ void CommissioningWindowOpener::OnOpenCommissioningWindowSuccess(void * context, self->mCommissioningWindowCallback->mCall(self->mCommissioningWindowCallback->mContext, self->mNodeId, CHIP_NO_ERROR, self->mSetupPayload); - // Don't touch `self` anymore; it might have been destroyed by the - // callee. + // Don't touch `self` anymore; it might have been destroyed by the callee. + } + else if (self->mCommissioningWindowVerifierCallback != nullptr) + { + self->mCommissioningWindowVerifierCallback->mCall(self->mCommissioningWindowVerifierCallback->mContext, self->mNodeId, + CHIP_NO_ERROR); + // Don't touch `self` anymore; it might have been destroyed by the callee. } else if (self->mBasicCommissioningWindowCallback != nullptr) { self->mBasicCommissioningWindowCallback->mCall(self->mBasicCommissioningWindowCallback->mContext, self->mNodeId, CHIP_NO_ERROR); - // Don't touch `self` anymore; it might have been destroyed by the - // callee. + // Don't touch `self` anymore; it might have been destroyed by the callee. } } @@ -252,6 +300,11 @@ void CommissioningWindowOpener::OnOpenCommissioningWindowFailure(void * context, self->mCommissioningWindowCallback->mCall(self->mCommissioningWindowCallback->mContext, self->mNodeId, error, SetupPayload()); } + else if (self->mCommissioningWindowVerifierCallback != nullptr) + { + self->mCommissioningWindowVerifierCallback->mCall(self->mCommissioningWindowVerifierCallback->mContext, self->mNodeId, + error); + } else if (self->mBasicCommissioningWindowCallback != nullptr) { self->mBasicCommissioningWindowCallback->mCall(self->mBasicCommissioningWindowCallback->mContext, self->mNodeId, error); diff --git a/src/controller/CommissioningWindowOpener.h b/src/controller/CommissioningWindowOpener.h index 10547dce3a662d..b657345ca53219 100644 --- a/src/controller/CommissioningWindowOpener.h +++ b/src/controller/CommissioningWindowOpener.h @@ -20,22 +20,17 @@ #include #include #include +#include #include #include #include #include #include #include -#include namespace chip { namespace Controller { -// Passing SetupPayload by value on purpose, in case a consumer decides to reuse -// this object from inside the callback. -typedef void (*OnOpenCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload); -typedef void (*OnOpenBasicCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status); - /** * A helper class to open a commissioning window given some parameters. */ @@ -107,6 +102,38 @@ class CommissioningWindowOpener Callback::Callback * callback, SetupPayload & payload, bool readVIDPIDAttributes = false); + /** + * @brief + * Try to look up the device attached to our controller with the given + * node id and ask it to re-enter commissioning mode with a PASE verifier + * derived from the given information and the given discriminator. The + * device will exit commissioning mode after a successful commissioning, + * or after the given `timeout` time. + * + * @param[in] params The parameters required to open an enhanced commissioning window + * with the provided or generated passcode. + * @param[out] payload The setup payload, not including the VID/PID bits, + * even if those were asked for, that is generated + * based on the passed-in information. The payload + * provided to the callback function, unlike this + * out parameter, will include the VID/PID bits if + * readVIDPIDAttributes is true. + */ + CHIP_ERROR OpenCommissioningWindow(const CommissioningWindowPasscodeParams & params, SetupPayload & payload); + + /** + * @brief + * Try to look up the device attached to our controller with the given + * node id and ask it to re-enter commissioning mode with a PASE verifier + * derived from the given information and the given discriminator. The + * device will exit commissioning mode after a successful commissioning, + * or after the given `timeout` time. + * + * @param[in] params The parameters required to open an enhanced commissioning window + * with the provided PAKE passcode verifier. + */ + CHIP_ERROR OpenCommissioningWindow(const CommissioningWindowVerifierParams & params); + private: enum class Step : uint8_t { @@ -133,9 +160,11 @@ class CommissioningWindowOpener DeviceController * const mController = nullptr; Step mNextStep = Step::kAcceptCommissioningStart; - Callback::Callback * mCommissioningWindowCallback = nullptr; - Callback::Callback * mBasicCommissioningWindowCallback = nullptr; + Callback::Callback * mCommissioningWindowCallback = nullptr; + Callback::Callback * mCommissioningWindowVerifierCallback = nullptr; + Callback::Callback * mBasicCommissioningWindowCallback = nullptr; SetupPayload mSetupPayload; + SetupDiscriminator mDiscriminator{}; NodeId mNodeId = kUndefinedNodeId; System::Clock::Seconds16 mCommissioningWindowTimeout = System::Clock::kZero; CommissioningWindowOption mCommissioningWindowOption = CommissioningWindowOption::kOriginalSetupCode; diff --git a/src/controller/CommissioningWindowParams.h b/src/controller/CommissioningWindowParams.h new file mode 100644 index 00000000000000..a4f0e43fa096c2 --- /dev/null +++ b/src/controller/CommissioningWindowParams.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2024 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. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace chip { +namespace Controller { + +// Passing SetupPayload by value on purpose, in case a consumer decides to reuse +// this object from inside the callback. +typedef void (*OnOpenCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload); +typedef void (*OnOpenCommissioningWindowWithVerifier)(void * context, NodeId deviceId, CHIP_ERROR status); +typedef void (*OnOpenBasicCommissioningWindow)(void * context, NodeId deviceId, CHIP_ERROR status); + +template +class CommissioningWindowCommonParams +{ +public: + CommissioningWindowCommonParams() = default; + + bool HasNodeId() const { return mNodeId != kUndefinedNodeId; } + NodeId GetNodeId() const + { + VerifyOrDie(HasNodeId()); + return mNodeId; + } + // The node identifier of device + Derived & SetNodeId(NodeId nodeId) + { + mNodeId = nodeId; + return static_cast(*this); + } + + System::Clock::Seconds16 GetTimeout() const { return mTimeout; } + // The duration for which the commissioning window should remain open. + Derived & SetTimeout(System::Clock::Seconds16 timeout) + { + mTimeout = timeout; + return static_cast(*this); + } + Derived & SetTimeout(uint16_t timeoutSeconds) { return SetTimeout(System::Clock::Seconds16(timeoutSeconds)); } + + // The PAKE iteration count associated with the PAKE Passcode ID and + // ephemeral PAKE passcode verifier to be used for this commissioning. + uint32_t GetIteration() const { return mIteration; } + Derived & SetIteration(uint32_t iteration) + { + mIteration = iteration; + return static_cast(*this); + } + + // The long discriminator for the DNS-SD advertisement. + uint16_t GetDiscriminator() const { return mDiscriminator.Value(); } + bool HasDiscriminator() const { return mDiscriminator.HasValue(); } + Derived & SetDiscriminator(uint16_t discriminator) + { + mDiscriminator = MakeOptional(discriminator); + return static_cast(*this); + } + +private: + NodeId mNodeId = kUndefinedNodeId; + System::Clock::Seconds16 mTimeout = System::Clock::Seconds16(300); // Defaulting + uint32_t mIteration = 1000; // Defaulting + Optional mDiscriminator = NullOptional; // Using optional type to avoid picking a sentinnel in valid range +}; + +class CommissioningWindowPasscodeParams : public CommissioningWindowCommonParams +{ +public: + CommissioningWindowPasscodeParams() = default; + + bool HasSetupPIN() const { return mSetupPIN.HasValue(); } + // Get the value of setup PIN (Passcode) if present, crashes otherwise. + uint32_t GetSetupPIN() const { return mSetupPIN.Value(); } + // The setup PIN (Passcode) to use. A random one will be generated if not provided. + CommissioningWindowPasscodeParams & SetSetupPIN(uint32_t setupPIN) { return SetSetupPIN(MakeOptional(setupPIN)); } + // The setup PIN (Passcode) to use. A random one will be generated if NullOptional is used. + CommissioningWindowPasscodeParams & SetSetupPIN(Optional setupPIN) + { + mSetupPIN = setupPIN; + return *this; + } + + bool HasSalt() const { return mSalt.HasValue(); } + // Get the value of salt if present. + // Dies if absent! Make sure to check HasSalt() + ByteSpan GetSalt() const { return mSalt.Value(); } + // The salt to use. A random one will be generated if not provided. + // If provided, must be at least kSpake2p_Min_PBKDF_Salt_Length bytes + // and at most kSpake2p_Max_PBKDF_Salt_Length bytes in length. + CommissioningWindowPasscodeParams & SetSalt(ByteSpan salt) { return SetSalt(MakeOptional(salt)); } + // The salt to use. A random one will be generated if NullOptional is used. + // If provided, must be at least kSpake2p_Min_PBKDF_Salt_Length bytes + // and at most kSpake2p_Max_PBKDF_Salt_Length bytes in length. + // Note that this an overloaded optional arg function to support existing APIs. + CommissioningWindowPasscodeParams & SetSalt(Optional salt) + { + mSalt = salt; + return *this; + } + + bool GetReadVIDPIDAttributes() const { return mReadVIDPIDAttributes; } + // Should the API internally read VID and PID from the device while opening the + // commissioning window. If this argument is `true`, the API will read VID and PID + // from the device and include them in the setup payload passed to the callback. + CommissioningWindowPasscodeParams & SetReadVIDPIDAttributes(bool readVIDPIDAttributes) + { + mReadVIDPIDAttributes = readVIDPIDAttributes; + return *this; + } + + Callback::Callback * GetCallback() const { return mCallback; } + // The function to be called on success or failure of opening the commissioning window. + // This will include the SetupPayload generated from provided parameters. + CommissioningWindowPasscodeParams & SetCallback(Callback::Callback * callback) + { + mCallback = callback; + return *this; + } + +private: + Optional mSetupPIN = NullOptional; + Optional mSalt = NullOptional; + bool mReadVIDPIDAttributes = false; + Callback::Callback * mCallback = nullptr; +}; + +class CommissioningWindowVerifierParams : public CommissioningWindowCommonParams +{ +public: + CommissioningWindowVerifierParams() = default; + + ByteSpan GetVerifier() const { return mVerifier.Value(); } + // The PAKE passcode verifier generated with enclosed iterations, salt and not-enclosed passcode. + CommissioningWindowVerifierParams & SetVerifier(ByteSpan verifier) + { + mVerifier = MakeOptional(verifier); + return *this; + } + + ByteSpan GetSalt() const { return mSalt.Value(); } + // The salt that was used to generate the verifier. + // It must be at least kSpake2p_Min_PBKDF_Salt_Length bytes. + // Note: This is REQUIRED when verifier is used + CommissioningWindowVerifierParams & SetSalt(ByteSpan salt) + { + mSalt = MakeOptional(salt); + return *this; + } + + Callback::Callback * GetCallback() const { return mCallback; } + // The function to be called on success or failure of opening the + // commissioning window. This will NOT include the SetupPayload. + CommissioningWindowVerifierParams & SetCallback(Callback::Callback * callback) + { + mCallback = callback; + return *this; + } + +private: + Optional mSalt; + Optional mVerifier; + Callback::Callback * mCallback = nullptr; +}; + +} // namespace Controller +} // namespace chip diff --git a/src/controller/python/ChipDeviceController-ScriptBinding.cpp b/src/controller/python/ChipDeviceController-ScriptBinding.cpp index a98c8082d45380..556ae2c6943325 100644 --- a/src/controller/python/ChipDeviceController-ScriptBinding.cpp +++ b/src/controller/python/ChipDeviceController-ScriptBinding.cpp @@ -714,9 +714,14 @@ PyChipError pychip_DeviceController_OpenCommissioningWindow(chip::Controller::De SetupPayload payload; auto opener = Platform::New(static_cast(devCtrl)); - PyChipError err = ToPyChipError(opener->OpenCommissioningWindow(nodeid, System::Clock::Seconds16(timeout), iteration, - discriminator, NullOptional, NullOptional, - pairingDelegate->GetOpenWindowCallback(opener), payload)); + PyChipError err = + ToPyChipError(opener->OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(nodeid) + .SetTimeout(timeout) + .SetIteration(iteration) + .SetDiscriminator(discriminator) + .SetCallback(pairingDelegate->GetOpenWindowCallback(opener)), + payload)); return err; } diff --git a/src/controller/tests/BUILD.gn b/src/controller/tests/BUILD.gn index 7b3dff0e27d441..a1d7b9433075b7 100644 --- a/src/controller/tests/BUILD.gn +++ b/src/controller/tests/BUILD.gn @@ -31,6 +31,7 @@ chip_test_suite("tests") { test_sources += [ "TestReadChunking.cpp" ] test_sources += [ "TestWriteChunking.cpp" ] test_sources += [ "TestEventNumberCaching.cpp" ] + test_sources += [ "TestCommissioningWindowOpener.cpp" ] } cflags = [ "-Wconversion" ] diff --git a/src/controller/tests/TestCommissioningWindowOpener.cpp b/src/controller/tests/TestCommissioningWindowOpener.cpp new file mode 100644 index 00000000000000..01aa42fbe8b17a --- /dev/null +++ b/src/controller/tests/TestCommissioningWindowOpener.cpp @@ -0,0 +1,192 @@ +/* + * + * Copyright (c) 2024 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 + +using namespace chip; + +namespace { + +class MockDeviceController : public Controller::DeviceController +{ +public: + CHIP_ERROR + GetConnectedDevice(NodeId peerNodeId, Callback::Callback * onConnection, + Callback::Callback * onFailure, + TransportPayloadCapability transportPayloadCapability = TransportPayloadCapability::kMRPPayload) override + { + return CHIP_NO_ERROR; + } +}; + +// Valid crypto values from src/protocols/secure_channel/tests/TestPASESession.cpp +constexpr uint32_t sTestSpake2p01_PinCode = 20202021; +constexpr uint32_t sTestSpake2p01_IterationCount = 1000; +constexpr uint8_t sTestSpake2p01_Salt[] = { 0x53, 0x50, 0x41, 0x4B, 0x45, 0x32, 0x50, 0x20, + 0x4B, 0x65, 0x79, 0x20, 0x53, 0x61, 0x6C, 0x74 }; +constexpr Crypto::Spake2pVerifierSerialized sTestSpake2p01_SerializedVerifier = { + 0xB9, 0x61, 0x70, 0xAA, 0xE8, 0x03, 0x34, 0x68, 0x84, 0x72, 0x4F, 0xE9, 0xA3, 0xB2, 0x87, 0xC3, 0x03, 0x30, 0xC2, 0xA6, + 0x60, 0x37, 0x5D, 0x17, 0xBB, 0x20, 0x5A, 0x8C, 0xF1, 0xAE, 0xCB, 0x35, 0x04, 0x57, 0xF8, 0xAB, 0x79, 0xEE, 0x25, 0x3A, + 0xB6, 0xA8, 0xE4, 0x6B, 0xB0, 0x9E, 0x54, 0x3A, 0xE4, 0x22, 0x73, 0x6D, 0xE5, 0x01, 0xE3, 0xDB, 0x37, 0xD4, 0x41, 0xFE, + 0x34, 0x49, 0x20, 0xD0, 0x95, 0x48, 0xE4, 0xC1, 0x82, 0x40, 0x63, 0x0C, 0x4F, 0xF4, 0x91, 0x3C, 0x53, 0x51, 0x38, 0x39, + 0xB7, 0xC0, 0x7F, 0xCC, 0x06, 0x27, 0xA1, 0xB8, 0x57, 0x3A, 0x14, 0x9F, 0xCD, 0x1F, 0xA4, 0x66, 0xCF +}; + +static void OCWPasscodeCallback(void * context, NodeId deviceId, CHIP_ERROR status, SetupPayload payload) {} +static void OCWVerifierCallback(void * context, NodeId deviceId, CHIP_ERROR status) {} + +class TestCommissioningWindowOpener : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { Platform::MemoryShutdown(); } + +protected: + // Initialize with a null pointer for now, replace with a valid controller pointer if available + MockDeviceController mockController; + Controller::CommissioningWindowOpener opener = Controller::CommissioningWindowOpener(&mockController); +}; + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowVerifier_Success) +{ + Callback::Callback callback(OCWVerifierCallback, this); + + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSalt(ByteSpan(sTestSpake2p01_Salt)) + .SetVerifier(ByteSpan(sTestSpake2p01_SerializedVerifier)) + .SetCallback(&callback)); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowVerifier_Failure_InvalidSalt) +{ + Callback::Callback callback(OCWVerifierCallback, this); + + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSalt(ByteSpan()) + .SetVerifier(ByteSpan(sTestSpake2p01_SerializedVerifier)) + .SetCallback(&callback)); + EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowVerifier_Failure_InvalidVerifier) +{ + Callback::Callback callback(OCWVerifierCallback, this); + + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSalt(ByteSpan(sTestSpake2p01_Salt)) + .SetVerifier(ByteSpan()) + .SetCallback(&callback)); + EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowVerifier_Failure_InvalidIteration) +{ + Callback::Callback callback(OCWVerifierCallback, this); + + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(0) + .SetDiscriminator(3840) + .SetSalt(ByteSpan(sTestSpake2p01_Salt)) + .SetVerifier(ByteSpan(sTestSpake2p01_SerializedVerifier)) + .SetCallback(&callback)); + EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowPasscode_Success) +{ + SetupPayload ignored; + Callback::Callback callback(OCWPasscodeCallback, this); + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSetupPIN(sTestSpake2p01_PinCode) + .SetReadVIDPIDAttributes(true) + .SetSalt(ByteSpan(sTestSpake2p01_Salt)) + .SetCallback(&callback), + ignored); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowPasscode_Success_NoPin) +{ + SetupPayload ignored; + Callback::Callback callback(OCWPasscodeCallback, this); + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSalt(ByteSpan(sTestSpake2p01_Salt)) + .SetCallback(&callback), + ignored); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowPasscode_Success_NoSalt) +{ + SetupPayload ignored; + Callback::Callback callback(OCWPasscodeCallback, this); + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(sTestSpake2p01_IterationCount) + .SetDiscriminator(3840) + .SetSetupPIN(sTestSpake2p01_PinCode) + .SetCallback(&callback), + ignored); + EXPECT_EQ(err, CHIP_NO_ERROR); +} + +TEST_F(TestCommissioningWindowOpener, OpenCommissioningWindowPasscode_Failure_InvalidIteration) +{ + SetupPayload ignored; + Callback::Callback callback(OCWPasscodeCallback, this); + CHIP_ERROR err = opener.OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() + .SetNodeId(0x1234) + .SetTimeout(300) + .SetIteration(0) + .SetDiscriminator(3840) + .SetCallback(&callback), + ignored); + EXPECT_EQ(err, CHIP_ERROR_INVALID_ARGUMENT); +} + +// Add more test cases as needed to cover different scenarios +} // namespace