/* * 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 #include #include 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 * 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 setupPIN, Optional salt, Callback::Callback * 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(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(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(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(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(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(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(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 setupPIN, Optional 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(context); delete self; } void AutoCommissioningWindowOpener::OnOpenBasicCommissioningWindowResponse(void * context, NodeId deviceId, CHIP_ERROR status) { auto * self = static_cast(context); delete self; } } // namespace Controller } // namespace chip