| /* |
| * 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 <app-common/zap-generated/cluster-objects.h> |
| #include <controller/CommissioningWindowOpener.h> |
| #include <lib/core/CHIPSafeCasts.h> |
| #include <lib/support/CHIPMem.h> |
| #include <protocols/secure_channel/PASESession.h> |
| #include <setup_payload/ManualSetupPayloadGenerator.h> |
| #include <setup_payload/QRCodeSetupPayloadGenerator.h> |
| |
| using namespace chip::app::Clusters; |
| using namespace chip::System::Clock; |
| using namespace chip::Crypto; |
| |
| namespace { |
| // TODO: What should the timed invoke timeout here be? |
| constexpr uint16_t kTimedInvokeTimeoutMs = 10000; |
| } // anonymous namespace |
| |
| namespace chip { |
| namespace Controller { |
| |
| CHIP_ERROR CommissioningWindowOpener::OpenBasicCommissioningWindow(NodeId deviceId, Seconds16 timeout, |
| Callback::Callback<OnOpenBasicCommissioningWindow> * callback) |
| { |
| VerifyOrReturnError(mNextStep == Step::kAcceptCommissioningStart, CHIP_ERROR_INCORRECT_STATE); |
| mSetupPayload = SetupPayload(); |
| |
| // Basic commissioning does not use the setup payload. |
| |
| mCommissioningWindowOption = CommissioningWindowOption::kOriginalSetupCode; |
| mBasicCommissioningWindowCallback = callback; |
| mCommissioningWindowCallback = nullptr; |
| mCommissioningWindowVerifierCallback = nullptr; |
| mNodeId = deviceId; |
| mCommissioningWindowTimeout = timeout; |
| |
| mNextStep = Step::kOpenCommissioningWindow; |
| return mController->GetConnectedDevice(mNodeId, &mDeviceConnected, &mDeviceConnectionFailure); |
| } |
| |
| CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindow(NodeId deviceId, Seconds16 timeout, uint32_t iteration, |
| uint16_t discriminator, Optional<uint32_t> setupPIN, |
| Optional<ByteSpan> salt, |
| Callback::Callback<OnOpenCommissioningWindow> * callback, |
| SetupPayload & payload, bool readVIDPIDAttributes) |
| { |
| return OpenCommissioningWindow(CommissioningWindowPasscodeParams() |
| .SetNodeId(deviceId) |
| .SetTimeout(timeout) |
| .SetIteration(iteration) |
| .SetDiscriminator(discriminator) |
| .SetSetupPIN(setupPIN) |
| .SetSalt(salt) |
| .SetReadVIDPIDAttributes(readVIDPIDAttributes) |
| .SetCallback(callback), |
| payload); |
| } |
| |
| 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); |
| |
| mSetupPayload = SetupPayload(); |
| |
| if (params.HasSetupPIN()) |
| { |
| VerifyOrReturnError(SetupPayload::IsValidSetupPIN(params.GetSetupPIN()), CHIP_ERROR_INVALID_ARGUMENT); |
| mCommissioningWindowOption = CommissioningWindowOption::kTokenWithProvidedPIN; |
| mSetupPayload.setUpPINCode = params.GetSetupPIN(); |
| } |
| else |
| { |
| mCommissioningWindowOption = CommissioningWindowOption::kTokenWithRandomPIN; |
| } |
| |
| mSetupPayload.version = 0; |
| mDiscriminator.SetLongValue(params.GetDiscriminator()); |
| mSetupPayload.discriminator = mDiscriminator; |
| mSetupPayload.rendezvousInformation.SetValue(RendezvousInformationFlag::kOnNetwork); |
| |
| if (params.HasSalt()) |
| { |
| 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(); |
| |
| bool randomSetupPIN = !params.HasSetupPIN(); |
| ReturnErrorOnFailure( |
| PASESession::GeneratePASEVerifier(mVerifier, mPBKDFIterations, mPBKDFSalt, randomSetupPIN, mSetupPayload.setUpPINCode)); |
| |
| payload = mSetupPayload; |
| mCommissioningWindowCallback = params.GetCallback(); |
| mBasicCommissioningWindowCallback = nullptr; |
| mCommissioningWindowVerifierCallback = nullptr; |
| mNodeId = params.GetNodeId(); |
| mCommissioningWindowTimeout = params.GetTimeout(); |
| mTargetEndpointId = params.GetEndpointId(); |
| |
| if (params.GetReadVIDPIDAttributes()) |
| { |
| mNextStep = Step::kReadVID; |
| } |
| else |
| { |
| mNextStep = Step::kOpenCommissioningWindow; |
| } |
| |
| 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()); |
| mTargetEndpointId = params.GetEndpointId(); |
| |
| mNextStep = Step::kOpenCommissioningWindow; |
| |
| return mController->GetConnectedDevice(mNodeId, &mDeviceConnected, &mDeviceConnectionFailure); |
| } |
| |
| CHIP_ERROR CommissioningWindowOpener::OpenCommissioningWindowInternal(Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & sessionHandle) |
| { |
| ChipLogProgress(Controller, "OpenCommissioningWindow for device ID 0x" ChipLogFormatX64, ChipLogValueX64(mNodeId)); |
| |
| ClusterBase cluster(exchangeMgr, sessionHandle, mTargetEndpointId); |
| |
| if (mCommissioningWindowOption != CommissioningWindowOption::kOriginalSetupCode) |
| { |
| Spake2pVerifierSerialized serializedVerifier; |
| MutableByteSpan serializedVerifierSpan(serializedVerifier); |
| ReturnErrorOnFailure(mVerifier.Serialize(serializedVerifierSpan)); |
| |
| AdministratorCommissioning::Commands::OpenCommissioningWindow::Type request; |
| request.commissioningTimeout = mCommissioningWindowTimeout.count(); |
| request.PAKEPasscodeVerifier = serializedVerifierSpan; |
| request.discriminator = mDiscriminator.GetLongValue(); |
| request.iterations = mPBKDFIterations; |
| request.salt = mPBKDFSalt; |
| |
| ReturnErrorOnFailure(cluster.InvokeCommand(request, this, OnOpenCommissioningWindowSuccess, |
| OnOpenCommissioningWindowFailure, MakeOptional(kTimedInvokeTimeoutMs))); |
| } |
| else |
| { |
| AdministratorCommissioning::Commands::OpenBasicCommissioningWindow::Type request; |
| request.commissioningTimeout = mCommissioningWindowTimeout.count(); |
| ReturnErrorOnFailure(cluster.InvokeCommand(request, this, OnOpenCommissioningWindowSuccess, |
| OnOpenCommissioningWindowFailure, MakeOptional(kTimedInvokeTimeoutMs))); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| void CommissioningWindowOpener::OnPIDReadResponse(void * context, uint16_t value) |
| { |
| ChipLogProgress(Controller, "Received PID for the device. Value %d", value); |
| auto * self = static_cast<CommissioningWindowOpener *>(context); |
| self->mSetupPayload.productID = value; |
| |
| self->mNextStep = Step::kOpenCommissioningWindow; |
| |
| CHIP_ERROR err = self->mController->GetConnectedDevice(self->mNodeId, &self->mDeviceConnected, &self->mDeviceConnectionFailure); |
| if (err != CHIP_NO_ERROR) |
| { |
| OnOpenCommissioningWindowFailure(context, err); |
| } |
| } |
| |
| void CommissioningWindowOpener::OnVIDReadResponse(void * context, VendorId value) |
| { |
| ChipLogProgress(Controller, "Received VID for the device. Value %d", to_underlying(value)); |
| |
| auto * self = static_cast<CommissioningWindowOpener *>(context); |
| |
| self->mSetupPayload.vendorID = value; |
| |
| self->mNextStep = Step::kReadPID; |
| CHIP_ERROR err = self->mController->GetConnectedDevice(self->mNodeId, &self->mDeviceConnected, &self->mDeviceConnectionFailure); |
| if (err != CHIP_NO_ERROR) |
| { |
| OnOpenCommissioningWindowFailure(context, err); |
| } |
| } |
| |
| void CommissioningWindowOpener::OnVIDPIDReadFailureResponse(void * context, CHIP_ERROR error) |
| { |
| ChipLogProgress(Controller, "Failed to read VID/PID for the device. error %" CHIP_ERROR_FORMAT, error.Format()); |
| OnOpenCommissioningWindowFailure(context, error); |
| } |
| |
| void CommissioningWindowOpener::OnOpenCommissioningWindowSuccess(void * context, const chip::app::DataModel::NullObjectType &) |
| { |
| ChipLogProgress(Controller, "Successfully opened pairing window on the device"); |
| auto * self = static_cast<CommissioningWindowOpener *>(context); |
| self->mNextStep = Step::kAcceptCommissioningStart; |
| if (self->mCommissioningWindowCallback != nullptr) |
| { |
| char payloadBuffer[QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength + 1]; |
| |
| MutableCharSpan manualCode(payloadBuffer); |
| CHIP_ERROR err = ManualSetupPayloadGenerator(self->mSetupPayload).payloadDecimalStringRepresentation(manualCode); |
| if (err == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(Controller, "Manual pairing code: [%s]", payloadBuffer); |
| } |
| else |
| { |
| ChipLogError(Controller, "Unable to generate manual code for setup payload: %" CHIP_ERROR_FORMAT, err.Format()); |
| } |
| |
| MutableCharSpan QRCode(payloadBuffer); |
| err = QRCodeBasicSetupPayloadGenerator(self->mSetupPayload).payloadBase38Representation(QRCode); |
| if (err == CHIP_NO_ERROR) |
| { |
| ChipLogProgress(Controller, "SetupQRCode: [%s]", payloadBuffer); |
| } |
| else |
| { |
| ChipLogError(Controller, "Unable to generate QR code for setup payload: %" CHIP_ERROR_FORMAT, err.Format()); |
| } |
| |
| 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. |
| } |
| 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. |
| } |
| } |
| |
| void CommissioningWindowOpener::OnOpenCommissioningWindowFailure(void * context, CHIP_ERROR error) |
| { |
| ChipLogError(Controller, "Failed to open pairing window on the device. Status %" CHIP_ERROR_FORMAT, error.Format()); |
| auto * self = static_cast<CommissioningWindowOpener *>(context); |
| self->mNextStep = Step::kAcceptCommissioningStart; |
| if (self->mCommissioningWindowCallback != nullptr) |
| { |
| 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); |
| } |
| } |
| |
| void CommissioningWindowOpener::OnDeviceConnectedCallback(void * context, Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & sessionHandle) |
| { |
| auto * self = static_cast<CommissioningWindowOpener *>(context); |
| |
| #if CHIP_ERROR_LOGGING |
| const char * messageIfError = nullptr; |
| #endif // CHIP_ERROR_LOGGING |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| switch (self->mNextStep) |
| { |
| case Step::kReadVID: { |
| ClusterBase cluster(exchangeMgr, sessionHandle, kRootEndpointId); |
| err = cluster.ReadAttribute<BasicInformation::Attributes::VendorID::TypeInfo>(context, OnVIDReadResponse, |
| OnVIDPIDReadFailureResponse); |
| #if CHIP_ERROR_LOGGING |
| messageIfError = "Could not read VID for opening commissioning window"; |
| #endif // CHIP_ERROR_LOGGING |
| break; |
| } |
| case Step::kReadPID: { |
| ClusterBase cluster(exchangeMgr, sessionHandle, kRootEndpointId); |
| err = cluster.ReadAttribute<BasicInformation::Attributes::ProductID::TypeInfo>(context, OnPIDReadResponse, |
| OnVIDPIDReadFailureResponse); |
| #if CHIP_ERROR_LOGGING |
| messageIfError = "Could not read PID for opening commissioning window"; |
| #endif // CHIP_ERROR_LOGGING |
| break; |
| } |
| case Step::kOpenCommissioningWindow: { |
| err = self->OpenCommissioningWindowInternal(exchangeMgr, sessionHandle); |
| #if CHIP_ERROR_LOGGING |
| messageIfError = "Could not connect to open commissioning window"; |
| #endif // CHIP_ERROR_LOGGING |
| break; |
| } |
| case Step::kAcceptCommissioningStart: { |
| err = CHIP_ERROR_INCORRECT_STATE; |
| #if CHIP_ERROR_LOGGING |
| messageIfError = "Just got a connected device; how can we be done?"; |
| #endif // CHIP_ERROR_LOGGING |
| break; |
| } |
| } |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Controller, "%s: %" CHIP_ERROR_FORMAT, messageIfError, err.Format()); |
| OnOpenCommissioningWindowFailure(context, err); |
| } |
| } |
| |
| void CommissioningWindowOpener::OnDeviceConnectionFailureCallback(void * context, const ScopedNodeId & peerId, CHIP_ERROR error) |
| { |
| OnOpenCommissioningWindowFailure(context, error); |
| } |
| |
| AutoCommissioningWindowOpener::AutoCommissioningWindowOpener(DeviceController * controller) : |
| CommissioningWindowOpener(controller), mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this), |
| mOnOpenBasicCommissioningWindowCallback(OnOpenBasicCommissioningWindowResponse, this) |
| {} |
| |
| CHIP_ERROR AutoCommissioningWindowOpener::OpenBasicCommissioningWindow(DeviceController * controller, NodeId deviceId, |
| Seconds16 timeout) |
| { |
| // Not using Platform::New because we want to keep our constructor private. |
| auto * opener = new (std::nothrow) AutoCommissioningWindowOpener(controller); |
| if (opener == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| CHIP_ERROR err = opener->CommissioningWindowOpener::OpenBasicCommissioningWindow( |
| deviceId, timeout, &opener->mOnOpenBasicCommissioningWindowCallback); |
| if (err != CHIP_NO_ERROR) |
| { |
| delete opener; |
| } |
| // Else will clean up when the callback is called. |
| return err; |
| } |
| |
| CHIP_ERROR AutoCommissioningWindowOpener::OpenCommissioningWindow(DeviceController * controller, NodeId deviceId, Seconds16 timeout, |
| uint32_t iteration, uint16_t discriminator, |
| Optional<uint32_t> setupPIN, Optional<ByteSpan> salt, |
| SetupPayload & payload, bool readVIDPIDAttributes) |
| { |
| // Not using Platform::New because we want to keep our constructor private. |
| auto * opener = new (std::nothrow) AutoCommissioningWindowOpener(controller); |
| if (opener == nullptr) |
| { |
| return CHIP_ERROR_NO_MEMORY; |
| } |
| |
| CHIP_ERROR err = opener->CommissioningWindowOpener::OpenCommissioningWindow( |
| deviceId, timeout, iteration, discriminator, setupPIN, salt, &opener->mOnOpenCommissioningWindowCallback, payload, |
| readVIDPIDAttributes); |
| if (err != CHIP_NO_ERROR) |
| { |
| delete opener; |
| } |
| // Else will clean up when the callback is called. |
| return err; |
| } |
| |
| void AutoCommissioningWindowOpener::OnOpenCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status, |
| chip::SetupPayload payload) |
| { |
| auto * self = static_cast<AutoCommissioningWindowOpener *>(context); |
| delete self; |
| } |
| void AutoCommissioningWindowOpener::OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status) |
| { |
| auto * self = static_cast<AutoCommissioningWindowOpener *>(context); |
| delete self; |
| } |
| |
| } // namespace Controller |
| } // namespace chip |