/* * * 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 "PairingManager.h" #include #include #include #include #include #include #include #include #if defined(PW_RPC_ENABLED) #include #endif using namespace ::chip; using namespace ::chip::Controller; namespace { CHIP_ERROR GetPayload(const char * setUpCode, SetupPayload & payload) { VerifyOrReturnValue(setUpCode, CHIP_ERROR_INVALID_ARGUMENT); bool isQRCode = strncmp(setUpCode, kQRCodePrefix, strlen(kQRCodePrefix)) == 0; if (isQRCode) { ReturnErrorOnFailure(QRCodeSetupPayloadParser(setUpCode).populatePayload(payload)); VerifyOrReturnError(payload.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT); } else { ReturnErrorOnFailure(ManualSetupPayloadParser(setUpCode).populatePayload(payload)); VerifyOrReturnError(payload.isValidManualCode(), CHIP_ERROR_INVALID_ARGUMENT); } return CHIP_NO_ERROR; } bool ParseAddressWithInterface(const char * addressString, Inet::IPAddress & address, Inet::InterfaceId & interfaceId) { struct addrinfo hints; struct addrinfo * result; int ret; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; ret = getaddrinfo(addressString, nullptr, &hints, &result); if (ret < 0) { ChipLogError(NotSpecified, "Invalid address: %s", addressString); return false; } if (result->ai_family == AF_INET6) { struct sockaddr_in6 * addr = reinterpret_cast(result->ai_addr); address = Inet::IPAddress::FromSockAddr(*addr); interfaceId = Inet::InterfaceId(addr->sin6_scope_id); } #if INET_CONFIG_ENABLE_IPV4 else if (result->ai_family == AF_INET) { address = Inet::IPAddress::FromSockAddr(*reinterpret_cast(result->ai_addr)); interfaceId = Inet::InterfaceId::Null(); } #endif // INET_CONFIG_ENABLE_IPV4 else { ChipLogError(NotSpecified, "Unsupported address: %s", addressString); freeaddrinfo(result); return false; } freeaddrinfo(result); return true; } } // namespace PairingManager::PairingManager() : mOnOpenCommissioningWindowCallback(OnOpenCommissioningWindowResponse, this), mOnOpenCommissioningWindowVerifierCallback(OnOpenCommissioningWindowVerifierResponse, this), mCurrentFabricRemoveCallback(OnCurrentFabricRemove, this) {} CHIP_ERROR PairingManager::Init(Controller::DeviceCommissioner * commissioner, CredentialIssuerCommands * credIssuerCmds) { VerifyOrReturnError(commissioner != nullptr, CHIP_ERROR_INCORRECT_STATE); VerifyOrReturnError(credIssuerCmds != nullptr, CHIP_ERROR_INCORRECT_STATE); mCommissioner = commissioner; mCredIssuerCmds = credIssuerCmds; return CHIP_NO_ERROR; } CHIP_ERROR PairingManager::OpenCommissioningWindow(NodeId nodeId, EndpointId endpointId, uint16_t commissioningTimeoutSec, uint32_t iterations, uint16_t discriminator, const ByteSpan & salt, const ByteSpan & verifier) { if (mCommissioner == nullptr) { ChipLogError(NotSpecified, "Commissioner is null, cannot open commissioning window"); return CHIP_ERROR_INCORRECT_STATE; } // Check if a window is already open if (mWindowOpener != nullptr) { ChipLogError(NotSpecified, "A commissioning window is already open"); return CHIP_ERROR_INCORRECT_STATE; } // Ensure salt and verifier sizes are valid if (!salt.empty() && salt.size() > chip::Crypto::kSpake2p_Max_PBKDF_Salt_Length) { ChipLogError(NotSpecified, "Salt size exceeds buffer capacity"); return CHIP_ERROR_BUFFER_TOO_SMALL; } if (!verifier.empty() && verifier.size() > chip::Crypto::kSpake2p_VerifierSerialized_Length) { ChipLogError(NotSpecified, "Verifier size exceeds buffer capacity"); return CHIP_ERROR_BUFFER_TOO_SMALL; } if (!salt.empty()) { memcpy(mSaltBuffer, salt.data(), salt.size()); mSalt = ByteSpan(mSaltBuffer, salt.size()); } else { mSalt = ByteSpan(); } if (!verifier.empty()) { memcpy(mVerifierBuffer, verifier.data(), verifier.size()); mVerifier = ByteSpan(mVerifierBuffer, verifier.size()); } else { mVerifier = ByteSpan(); } return DeviceLayer::SystemLayer().ScheduleLambda([nodeId, endpointId, commissioningTimeoutSec, iterations, discriminator]() { PairingManager & self = PairingManager::Instance(); if (self.mCommissioner == nullptr) { ChipLogError(NotSpecified, "Commissioner is null, cannot open commissioning window"); return; } self.mWindowOpener = Platform::MakeUnique(self.mCommissioner); if (!self.mVerifier.empty()) { if (self.mSalt.empty()) { ChipLogError(NotSpecified, "Salt is required when verifier is set"); self.mWindowOpener.reset(); return; } // Open the commissioning window with verifier parameters CHIP_ERROR err = self.mWindowOpener->OpenCommissioningWindow(Controller::CommissioningWindowVerifierParams() .SetNodeId(nodeId) .SetEndpointId(endpointId) .SetTimeout(commissioningTimeoutSec) .SetIteration(iterations) .SetDiscriminator(discriminator) .SetVerifier(self.mVerifier) .SetSalt(self.mSalt) .SetCallback(&self.mOnOpenCommissioningWindowVerifierCallback)); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to open commissioning window with verifier: %s", ErrorStr(err)); self.mWindowOpener.reset(); } } else { SetupPayload ignored; // Open the commissioning window with passcode parameters CHIP_ERROR err = self.mWindowOpener->OpenCommissioningWindow(Controller::CommissioningWindowPasscodeParams() .SetNodeId(nodeId) .SetEndpointId(endpointId) .SetTimeout(commissioningTimeoutSec) .SetIteration(iterations) .SetDiscriminator(discriminator) .SetSetupPIN(NullOptional) .SetSalt(NullOptional) .SetCallback(&self.mOnOpenCommissioningWindowCallback), ignored); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to open commissioning window with passcode: %s", ErrorStr(err)); self.mWindowOpener.reset(); } } }); } void PairingManager::OnOpenCommissioningWindowResponse(void * context, NodeId remoteId, CHIP_ERROR err, SetupPayload payload) { VerifyOrDie(context != nullptr); PairingManager * self = static_cast(context); if (self->mCommissioningWindowDelegate) { self->mCommissioningWindowDelegate->OnCommissioningWindowOpened(remoteId, err, payload); self->SetOpenCommissioningWindowDelegate(nullptr); } OnOpenCommissioningWindowVerifierResponse(context, remoteId, err); } void PairingManager::OnOpenCommissioningWindowVerifierResponse(void * context, NodeId remoteId, CHIP_ERROR err) { VerifyOrDie(context != nullptr); PairingManager * self = static_cast(context); LogErrorOnFailure(err); // Reset the window opener once the window operation is complete self->mWindowOpener.reset(); } void PairingManager::OnStatusUpdate(DevicePairingDelegate::Status status) { switch (status) { case DevicePairingDelegate::Status::SecurePairingSuccess: ChipLogProgress(NotSpecified, "CASE establishment successful"); break; case DevicePairingDelegate::Status::SecurePairingFailed: ChipLogError(NotSpecified, "Secure Pairing Failed"); break; } } void PairingManager::OnPairingComplete(CHIP_ERROR err) { if (err == CHIP_NO_ERROR) { ChipLogProgress(NotSpecified, "PASE establishment successful"); } else { ChipLogProgress(NotSpecified, "Pairing Failure: %s", ErrorStr(err)); } } void PairingManager::OnPairingDeleted(CHIP_ERROR err) { if (err == CHIP_NO_ERROR) { ChipLogProgress(NotSpecified, "Pairing Deleted Success"); } else { ChipLogProgress(NotSpecified, "Pairing Deleted Failure: %s", ErrorStr(err)); } } void PairingManager::OnCommissioningComplete(NodeId nodeId, CHIP_ERROR err) { if (err == CHIP_NO_ERROR) { // print to console fprintf(stderr, "New device with Node ID: " ChipLogFormatX64 " has been successfully added.\n", ChipLogValueX64(nodeId)); // mCommissioner has a lifetime that is the entire life of the application itself // so it is safe to provide to StartDeviceSynchronization. DeviceSynchronizer::Instance().StartDeviceSynchronization(mCommissioner, nodeId, mDeviceIsICD); } else { // When ICD device commissioning fails, the ICDClientInfo stored in OnICDRegistrationComplete needs to be removed. if (mDeviceIsICD) { CHIP_ERROR deleteEntryError = CHIPCommand::sICDClientStorage.DeleteEntry(ScopedNodeId(nodeId, mCommissioner->GetFabricIndex())); if (deleteEntryError != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to delete ICD entry: %s", ErrorStr(err)); } } ChipLogProgress(NotSpecified, "Device commissioning Failure: %s", ErrorStr(err)); } if (mCommissioningDelegate) { mCommissioningDelegate->OnCommissioningComplete(nodeId, err); SetCommissioningDelegate(nullptr); } } void PairingManager::OnReadCommissioningInfo(const Controller::ReadCommissioningInfo & info) { ChipLogProgress(AppServer, "OnReadCommissioningInfo - vendorId=0x%04X productId=0x%04X", info.basic.vendorId, info.basic.productId); // The string in CharSpan received from the device is not null-terminated, we use std::string here for coping and // appending a null-terminator at the end of the string. std::string userActiveModeTriggerInstruction; // Note: the callback doesn't own the buffer, should make a copy if it will be used it later. if (info.icd.userActiveModeTriggerInstruction.size() != 0) { userActiveModeTriggerInstruction = std::string(info.icd.userActiveModeTriggerInstruction.data(), info.icd.userActiveModeTriggerInstruction.size()); } if (info.icd.userActiveModeTriggerHint.HasAny()) { ChipLogProgress(AppServer, "OnReadCommissioningInfo - LIT UserActiveModeTriggerHint=0x%08x", info.icd.userActiveModeTriggerHint.Raw()); ChipLogProgress(AppServer, "OnReadCommissioningInfo - LIT UserActiveModeTriggerInstruction=%s", userActiveModeTriggerInstruction.c_str()); } ChipLogProgress(AppServer, "OnReadCommissioningInfo ICD - IdleModeDuration=%u activeModeDuration=%u activeModeThreshold=%u", info.icd.idleModeDuration, info.icd.activeModeDuration, info.icd.activeModeThreshold); } void PairingManager::OnICDRegistrationComplete(ScopedNodeId nodeId, uint32_t icdCounter) { char icdSymmetricKeyHex[Crypto::kAES_CCM128_Key_Length * 2 + 1]; Encoding::BytesToHex(mICDSymmetricKey.Value().data(), mICDSymmetricKey.Value().size(), icdSymmetricKeyHex, sizeof(icdSymmetricKeyHex), Encoding::HexFlags::kNullTerminate); app::ICDClientInfo clientInfo; clientInfo.peer_node = nodeId; clientInfo.monitored_subject = mICDMonitoredSubject.Value(); clientInfo.start_icd_counter = icdCounter; CHIP_ERROR err = CHIPCommand::sICDClientStorage.SetKey(clientInfo, mICDSymmetricKey.Value()); if (err == CHIP_NO_ERROR) { err = CHIPCommand::sICDClientStorage.StoreEntry(clientInfo); } if (err != CHIP_NO_ERROR) { CHIPCommand::sICDClientStorage.RemoveKey(clientInfo); ChipLogError(NotSpecified, "Failed to persist symmetric key for " ChipLogFormatX64 ": %s", ChipLogValueX64(nodeId.GetNodeId()), err.AsString()); return; } mDeviceIsICD = true; ChipLogProgress(NotSpecified, "Saved ICD Symmetric key for " ChipLogFormatX64, ChipLogValueX64(nodeId.GetNodeId())); ChipLogProgress(NotSpecified, "ICD Registration Complete for device " ChipLogFormatX64 " / Check-In NodeID: " ChipLogFormatX64 " / Monitored Subject: " ChipLogFormatX64 " / Symmetric Key: %s / ICDCounter %u", ChipLogValueX64(nodeId.GetNodeId()), ChipLogValueX64(mICDCheckInNodeId.Value()), ChipLogValueX64(mICDMonitoredSubject.Value()), icdSymmetricKeyHex, icdCounter); } void PairingManager::OnICDStayActiveComplete(ScopedNodeId deviceId, uint32_t promisedActiveDuration) { ChipLogProgress(NotSpecified, "ICD Stay Active Complete for device " ChipLogFormatX64 " / promisedActiveDuration: %u", ChipLogValueX64(deviceId.GetNodeId()), promisedActiveDuration); } void PairingManager::OnDiscoveredDevice(const Dnssd::CommissionNodeData & nodeData) { // Ignore nodes with closed commissioning window VerifyOrReturn(nodeData.commissioningMode != 0); auto & resolutionData = nodeData; const uint16_t port = resolutionData.port; char buf[Inet::IPAddress::kMaxStringLength]; resolutionData.ipAddress[0].ToString(buf); ChipLogProgress(NotSpecified, "Discovered Device: %s:%u", buf, port); // Stop Mdns discovery. auto err = mCommissioner->StopCommissionableDiscovery(); // Some platforms does not implement a mechanism to stop mdns browse, so // we just ignore CHIP_ERROR_NOT_IMPLEMENTED instead of bailing out. if (CHIP_NO_ERROR != err && CHIP_ERROR_NOT_IMPLEMENTED != err) { return; } mCommissioner->RegisterDeviceDiscoveryDelegate(nullptr); auto interfaceId = resolutionData.ipAddress[0].IsIPv6LinkLocal() ? resolutionData.interfaceId : Inet::InterfaceId::Null(); auto peerAddress = Transport::PeerAddress::UDP(resolutionData.ipAddress[0], port, interfaceId); err = Pair(mNodeId, peerAddress); if (CHIP_NO_ERROR != err) { ChipLogProgress(NotSpecified, "Failed to pair device: " ChipLogFormatX64 " %s", ChipLogValueX64(mNodeId), ErrorStr(err)); } } Optional PairingManager::FailSafeExpiryTimeoutSecs() const { // No manual input, so do not need to extend. return Optional(); } bool PairingManager::ShouldWaitAfterDeviceAttestation() { // If there is a vendor ID and product ID, request OnDeviceAttestationCompleted(). // Currently this is added in the case that the example is performing reverse commissioning, // but it would be an improvement to store that explicitly. // TODO: Issue #35297 - [Fabric Sync] Improve where we get VID and PID when validating CCTRL CommissionNode command SetupPayload payload; CHIP_ERROR err = GetPayload(mOnboardingPayload, payload); return err == CHIP_NO_ERROR && (payload.vendorID != 0 || payload.productID != 0); } void PairingManager::OnDeviceAttestationCompleted(Controller::DeviceCommissioner * deviceCommissioner, DeviceProxy * device, const Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, Credentials::AttestationVerificationResult attestationResult) { SetupPayload payload; CHIP_ERROR parse_error = GetPayload(mOnboardingPayload, payload); if (parse_error == CHIP_NO_ERROR && (payload.vendorID != 0 || payload.productID != 0)) { if (payload.vendorID == 0 || payload.productID == 0) { ChipLogProgress(NotSpecified, "Failed validation: vendorID or productID must not be 0." "Requested VID: %u, Requested PID: %u.", payload.vendorID, payload.productID); deviceCommissioner->ContinueCommissioningAfterDeviceAttestation( device, Credentials::AttestationVerificationResult::kInvalidArgument); return; } if (payload.vendorID != info.BasicInformationVendorId() || payload.productID != info.BasicInformationProductId()) { ChipLogProgress(NotSpecified, "Failed validation of vendorID or productID." "Requested VID: %u, Requested PID: %u," "Detected VID: %u, Detected PID %u.", payload.vendorID, payload.productID, info.BasicInformationVendorId(), info.BasicInformationProductId()); deviceCommissioner->ContinueCommissioningAfterDeviceAttestation( device, payload.vendorID == info.BasicInformationVendorId() ? Credentials::AttestationVerificationResult::kDacProductIdMismatch : Credentials::AttestationVerificationResult::kDacVendorIdMismatch); return; } // NOTE: This will log errors even if the attestion was successful. CHIP_ERROR err = deviceCommissioner->ContinueCommissioningAfterDeviceAttestation(device, attestationResult); if (CHIP_NO_ERROR != err) { ChipLogError(NotSpecified, "Failed to continue commissioning after device attestation, error: %s", ErrorStr(err)); } return; } // Don't bypass attestation, continue with error. CHIP_ERROR err = deviceCommissioner->ContinueCommissioningAfterDeviceAttestation(device, attestationResult); if (CHIP_NO_ERROR != err) { ChipLogError(NotSpecified, "Failed to continue commissioning after device attestation, error: %s", ErrorStr(err)); } } CommissioningParameters PairingManager::GetCommissioningParameters() { auto params = CommissioningParameters(); params.SetSkipCommissioningComplete(false); params.SetDeviceAttestationDelegate(this); if (mICDRegistration.ValueOr(false)) { params.SetICDRegistrationStrategy(ICDRegistrationStrategy::kBeforeComplete); if (!mICDSymmetricKey.HasValue()) { Crypto::DRBG_get_bytes(mRandomGeneratedICDSymmetricKey, sizeof(mRandomGeneratedICDSymmetricKey)); mICDSymmetricKey.SetValue(ByteSpan(mRandomGeneratedICDSymmetricKey)); } if (!mICDCheckInNodeId.HasValue()) { mICDCheckInNodeId.SetValue(mCommissioner->GetNodeId()); } if (!mICDMonitoredSubject.HasValue()) { mICDMonitoredSubject.SetValue(mICDCheckInNodeId.Value()); } if (!mICDClientType.HasValue()) { mICDClientType.SetValue(app::Clusters::IcdManagement::ClientTypeEnum::kPermanent); } // These Optionals must have values now. // The commissioner will verify these values. params.SetICDSymmetricKey(mICDSymmetricKey.Value()); if (mICDStayActiveDurationMsec.HasValue()) { params.SetICDStayActiveDurationMsec(mICDStayActiveDurationMsec.Value()); } params.SetICDCheckInNodeId(mICDCheckInNodeId.Value()); params.SetICDMonitoredSubject(mICDMonitoredSubject.Value()); params.SetICDClientType(mICDClientType.Value()); } return params; } CHIP_ERROR PairingManager::Pair(NodeId remoteId, Transport::PeerAddress address) { auto params = RendezvousParameters().SetSetupPINCode(mSetupPINCode).SetDiscriminator(mDiscriminator).SetPeerAddress(address); CHIP_ERROR err = CHIP_NO_ERROR; auto commissioningParams = GetCommissioningParameters(); err = CurrentCommissioner().PairDevice(remoteId, params, commissioningParams); return err; } void PairingManager::OnCurrentFabricRemove(void * context, NodeId nodeId, CHIP_ERROR err) { PairingManager * self = reinterpret_cast(context); VerifyOrReturn(self != nullptr, ChipLogError(NotSpecified, "OnCurrentFabricRemove: context is null")); if (err == CHIP_NO_ERROR) { // print to console fprintf(stderr, "Device with Node ID: " ChipLogFormatX64 "has been successfully removed.\n", ChipLogValueX64(nodeId)); #if defined(PW_RPC_ENABLED) FabricIndex fabricIndex = self->CurrentCommissioner().GetFabricIndex(); app::InteractionModelEngine::GetInstance()->ShutdownSubscriptions(fabricIndex, nodeId); ScopedNodeId scopedNodeId(nodeId, fabricIndex); RemoveSynchronizedDevice(scopedNodeId); #endif } else { ChipLogProgress(NotSpecified, "Device unpair Failure: " ChipLogFormatX64 " %s", ChipLogValueX64(nodeId), ErrorStr(err)); } } void PairingManager::InitPairingCommand() { mCommissioner->RegisterPairingDelegate(this); // Clear the CATs in OperationalCredentialsIssuer mCredIssuerCmds->SetCredentialIssuerCATValues(kUndefinedCATs); mDeviceIsICD = false; } CHIP_ERROR PairingManager::PairDeviceWithCode(NodeId nodeId, const char * payload) { if (payload == nullptr || strlen(payload) > kMaxManualCodeLength + 1) { ChipLogError(NotSpecified, "PairDeviceWithCode failed: Invalid pairing payload"); return CHIP_ERROR_INVALID_STRING_LENGTH; } Platform::CopyString(mOnboardingPayload, sizeof(mOnboardingPayload), payload); return DeviceLayer::SystemLayer().ScheduleLambda([nodeId]() { PairingManager & self = PairingManager::Instance(); self.InitPairingCommand(); CommissioningParameters commissioningParams = self.GetCommissioningParameters(); auto discoveryType = DiscoveryType::kDiscoveryNetworkOnly; self.mNodeId = nodeId; CHIP_ERROR err = self.mCommissioner->PairDevice(nodeId, self.mOnboardingPayload, commissioningParams, discoveryType); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to pair device with code, error: %s", ErrorStr(err)); } }); } CHIP_ERROR PairingManager::PairDevice(chip::NodeId nodeId, uint32_t setupPINCode, const char * deviceRemoteIp, uint16_t deviceRemotePort) { if (deviceRemoteIp == nullptr || strlen(deviceRemoteIp) > Inet::IPAddress::kMaxStringLength) { ChipLogError(NotSpecified, "PairDevice failed: Invalid device remote IP address"); return CHIP_ERROR_INVALID_STRING_LENGTH; } Platform::CopyString(mRemoteIpAddr, sizeof(mRemoteIpAddr), deviceRemoteIp); return DeviceLayer::SystemLayer().ScheduleLambda([nodeId, setupPINCode, deviceRemotePort]() { PairingManager & self = PairingManager::Instance(); self.InitPairingCommand(); self.mSetupPINCode = setupPINCode; Inet::IPAddress address; Inet::InterfaceId interfaceId; if (!ParseAddressWithInterface(self.mRemoteIpAddr, address, interfaceId)) { ChipLogError(NotSpecified, "Invalid IP address: %s", self.mRemoteIpAddr); return; } CHIP_ERROR err = self.Pair(nodeId, Transport::PeerAddress::UDP(address, deviceRemotePort, interfaceId)); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to pair device, error: %s", ErrorStr(err)); } }); } CHIP_ERROR PairingManager::UnpairDevice(NodeId nodeId) { return DeviceLayer::SystemLayer().ScheduleLambda([nodeId]() { PairingManager & self = PairingManager::Instance(); self.InitPairingCommand(); self.mCurrentFabricRemover = Platform::MakeUnique(self.mCommissioner); if (!self.mCurrentFabricRemover) { ChipLogError(NotSpecified, "Failed to unpair device, mCurrentFabricRemover is null"); return; } CHIP_ERROR err = self.mCurrentFabricRemover->RemoveCurrentFabric(nodeId, &self.mCurrentFabricRemoveCallback); if (err != CHIP_NO_ERROR) { ChipLogError(NotSpecified, "Failed to unpair device, error: %s", ErrorStr(err)); } }); }