From ea8208042c175c7cd44ff40b976bd131cd429340 Mon Sep 17 00:00:00 2001 From: Jeff Tung <100387939+jtung-apple@users.noreply.github.com> Date: Wed, 31 Aug 2022 12:39:10 -0700 Subject: [PATCH] Issue 22318 - commissioner attestation delegate should be able to override success --- src/controller/CHIPDeviceController.cpp | 66 +++++++++++++------ src/controller/CHIPDeviceController.h | 48 ++++++++++---- src/controller/CommissioningDelegate.h | 1 + .../DacOnlyPartialAttestationVerifier.cpp | 2 +- .../DefaultDeviceAttestationVerifier.cpp | 2 +- .../DeviceAttestationDelegate.h | 21 +++++- .../DeviceAttestationVerifier.cpp | 39 +++++++++++ .../DeviceAttestationVerifier.h | 25 +++++-- .../CHIPTool.xcodeproj/project.pbxproj | 12 ++-- .../CHIP/MTRDeviceAttestationDelegate.h | 24 +++++++ .../CHIP/MTRDeviceAttestationDelegate.mm | 33 ++++++++++ .../CHIP/MTRDeviceAttestationDelegateBridge.h | 9 ++- .../MTRDeviceAttestationDelegateBridge.mm | 28 +++++++- .../MTRDeviceAttestationDelegate_Internal.h | 33 ++++++++++ .../Framework/CHIP/MTRDeviceController.mm | 2 +- .../Matter.xcodeproj/project.pbxproj | 14 +++- 16 files changed, 303 insertions(+), 56 deletions(-) create mode 100644 src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.mm create mode 100644 src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate_Internal.h diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 0bf9ee911359d9..1fcfd783da2042 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -796,8 +796,8 @@ CHIP_ERROR DeviceCommissioner::Commission(NodeId remoteDeviceId) } CHIP_ERROR -DeviceCommissioner::ContinueCommissioningAfterDeviceAttestationFailure(DeviceProxy * device, - Credentials::AttestationVerificationResult attestationResult) +DeviceCommissioner::ContinueCommissioningAfterDeviceAttestation(DeviceProxy * device, + Credentials::AttestationVerificationResult attestationResult) { MATTER_TRACE_EVENT_SCOPE("continueCommissioningDevice", "DeviceCommissioner"); if (device == nullptr || device != mDeviceBeingCommissioned) @@ -993,7 +993,8 @@ void DeviceCommissioner::OnAttestationResponse(void * context, commissioner->CommissioningStageComplete(CHIP_NO_ERROR, report); } -void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * context, AttestationVerificationResult result) +void DeviceCommissioner::OnDeviceAttestationInformationVerification( + void * context, const Credentials::DeviceAttestationVerifier::AttestationInfo & info, AttestationVerificationResult result) { MATTER_TRACE_EVENT_SCOPE("OnDeviceAttestationInformationVerification", "DeviceCommissioner"); DeviceCommissioner * commissioner = reinterpret_cast(context); @@ -1004,6 +1005,9 @@ void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * conte return; } + auto & params = commissioner->mDefaultCommissioner->GetCommissioningParameters(); + Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate(); + if (result != AttestationVerificationResult::kSuccess) { CommissioningDelegate::CommissioningReport report; @@ -1024,14 +1028,11 @@ void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * conte // Go look at AttestationVerificationResult enum in src/credentials/attestation_verifier/DeviceAttestationVerifier.h to // understand the errors. - auto & params = commissioner->mDefaultCommissioner->GetCommissioningParameters(); - Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate(); - // If a device attestation status delegate is installed, delegate handling of failure to the client and let them // decide on whether to proceed further or not. if (deviceAttestationDelegate) { - commissioner->ExtendArmFailSafeForFailedDeviceAttestation(result); + commissioner->ExtendArmFailSafeForDeviceAttestation(info, result); } else { @@ -1040,15 +1041,22 @@ void DeviceCommissioner::OnDeviceAttestationInformationVerification(void * conte } else { - ChipLogProgress(Controller, "Successfully validated 'Attestation Information' command received from the device."); - commissioner->CommissioningStageComplete(CHIP_NO_ERROR); + if (deviceAttestationDelegate && deviceAttestationDelegate->ShouldWaitAfterDeviceAttestation()) + { + commissioner->ExtendArmFailSafeForDeviceAttestation(info, result); + } + else + { + ChipLogProgress(Controller, "Successfully validated 'Attestation Information' command received from the device."); + commissioner->CommissioningStageComplete(CHIP_NO_ERROR); + } } } -void DeviceCommissioner::OnArmFailSafeExtendedForFailedDeviceAttestation( +void DeviceCommissioner::OnArmFailSafeExtendedForDeviceAttestation( void * context, const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data) { - // If this function starts using "data", need to fix ExtendArmFailSafeForFailedDeviceAttestation accordingly. + // If this function starts using "data", need to fix ExtendArmFailSafeForDeviceAttestation accordingly. DeviceCommissioner * commissioner = static_cast(context); if (!commissioner->mDeviceBeingCommissioned) @@ -1061,8 +1069,9 @@ void DeviceCommissioner::OnArmFailSafeExtendedForFailedDeviceAttestation( if (deviceAttestationDelegate) { ChipLogProgress(Controller, "Device attestation failed, delegating error handling to client"); - deviceAttestationDelegate->OnDeviceAttestationFailed(commissioner, commissioner->mDeviceBeingCommissioned, - commissioner->mAttestationResult); + deviceAttestationDelegate->OnDeviceAttestationCompleted(commissioner, commissioner->mDeviceBeingCommissioned, + *commissioner->mAttestationDeviceInfo, + commissioner->mAttestationResult); } else { @@ -1073,7 +1082,7 @@ void DeviceCommissioner::OnArmFailSafeExtendedForFailedDeviceAttestation( } } -void DeviceCommissioner::OnFailedToExtendedArmFailSafeFailedDeviceAttestation(void * context, CHIP_ERROR error) +void DeviceCommissioner::OnFailedToExtendedArmFailSafeDeviceAttestation(void * context, CHIP_ERROR error) { ChipLogProgress(Controller, "Failed to extend fail-safe timer to handle attestation failure %s", chip::ErrorStr(error)); DeviceCommissioner * commissioner = static_cast(context); @@ -1083,13 +1092,29 @@ void DeviceCommissioner::OnFailedToExtendedArmFailSafeFailedDeviceAttestation(vo commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report); } -void DeviceCommissioner::ExtendArmFailSafeForFailedDeviceAttestation(AttestationVerificationResult result) +void DeviceCommissioner::ClearAttestationDeviceInfo() +{ + if (mAttestationDeviceInfo != nullptr) + { + chip::Platform::Delete(mAttestationDeviceInfo); + mAttestationDeviceInfo = nullptr; + } +} + +void DeviceCommissioner::ExtendArmFailSafeForDeviceAttestation(const Credentials::DeviceAttestationVerifier::AttestationInfo & info, + Credentials::AttestationVerificationResult result) { mAttestationResult = result; auto & params = mDefaultCommissioner->GetCommissioningParameters(); Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate(); - auto expiryLengthSeconds = deviceAttestationDelegate->FailSafeExpiryTimeoutSecs(); + + if (deviceAttestationDelegate->ShouldWaitAfterDeviceAttestation()) + { + mAttestationDeviceInfo = chip::Platform::New(info); + } + + auto expiryLengthSeconds = deviceAttestationDelegate->FailSafeExpiryTimeoutSecs(); if (expiryLengthSeconds.HasValue()) { GeneralCommissioning::Commands::ArmFailSafe::Type request; @@ -1098,8 +1123,8 @@ void DeviceCommissioner::ExtendArmFailSafeForFailedDeviceAttestation(Attestation ChipLogProgress(Controller, "Changing fail-safe timer to %u seconds to handle DA failure", request.expiryLengthSeconds); // Per spec, anything we do with the fail-safe armed must not time out // in less than kMinimumCommissioningStepTimeout. - SendCommand(mDeviceBeingCommissioned, request, OnArmFailSafeExtendedForFailedDeviceAttestation, - OnFailedToExtendedArmFailSafeFailedDeviceAttestation, + SendCommand(mDeviceBeingCommissioned, request, OnArmFailSafeExtendedForDeviceAttestation, + OnFailedToExtendedArmFailSafeDeviceAttestation, MakeOptional(kMinimumCommissioningStepTimeout)); } else @@ -1107,7 +1132,7 @@ void DeviceCommissioner::ExtendArmFailSafeForFailedDeviceAttestation(Attestation ChipLogProgress(Controller, "Proceeding without changing fail-safe timer value as delegate has not set it"); // Callee does not use data argument. const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType data; - OnArmFailSafeExtendedForFailedDeviceAttestation(this, data); + OnArmFailSafeExtendedForDeviceAttestation(this, data); } } @@ -1474,6 +1499,9 @@ void OnBasicFailure(void * context, CHIP_ERROR error) void DeviceCommissioner::CleanupCommissioning(DeviceProxy * proxy, NodeId nodeId, const CompletionStatus & completionStatus) { commissioningCompletionStatus = completionStatus; + + ClearAttestationDeviceInfo(); + if (completionStatus.err == CHIP_NO_ERROR) { diff --git a/src/controller/CHIPDeviceController.h b/src/controller/CHIPDeviceController.h index 62f97c0c30e80d..924eebe8ef6f4f 100644 --- a/src/controller/CHIPDeviceController.h +++ b/src/controller/CHIPDeviceController.h @@ -487,15 +487,14 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, /** * @brief * This function instructs the commissioner to proceed to the next stage of commissioning after - * attestation failure is reported to an installed attestation delegate. + * attestation is reported to an installed attestation delegate. * * @param[in] device The device being commissioned. * @param[in] attestationResult The attestation result to use instead of whatever the device * attestation verifier came up with. May be a success or an error result. */ CHIP_ERROR - ContinueCommissioningAfterDeviceAttestationFailure(DeviceProxy * device, - Credentials::AttestationVerificationResult attestationResult); + ContinueCommissioningAfterDeviceAttestation(DeviceProxy * device, Credentials::AttestationVerificationResult attestationResult); CHIP_ERROR GetDeviceBeingCommissioned(NodeId deviceId, CommissioneeDeviceProxy ** device); @@ -565,7 +564,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, * @brief * This function returns the current CommissioningStage for this commissioner. */ - CommissioningStage GetCommissioningStage() { return mCommissioningStage; } + CommissioningStage GetCommissioningStage() + { + return mCommissioningStage; + } #if CONFIG_NETWORK_LAYER_BLE #if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE @@ -607,7 +609,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, * Returns the max number of commissionable nodes this commissioner can track mdns information for. * @return int The max number of commissionable nodes supported */ - int GetMaxCommissionableNodesSupported() { return kMaxCommissionableNodes; } + int GetMaxCommissionableNodesSupported() + { + return kMaxCommissionableNodes; + } #if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY // make this commissioner discoverable /** @@ -626,7 +631,10 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, * Return the UDC Server instance * */ - UserDirectedCommissioningServer * GetUserDirectedCommissioningServer() { return mUdcServer; } + UserDirectedCommissioningServer * GetUserDirectedCommissioningServer() + { + return mUdcServer; + } #endif // CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY /** @@ -638,8 +646,14 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, */ void OnNodeDiscovered(const chip::Dnssd::DiscoveredNodeData & nodeData) override; - void RegisterPairingDelegate(DevicePairingDelegate * pairingDelegate) { mPairingDelegate = pairingDelegate; } - DevicePairingDelegate * GetPairingDelegate() const { return mPairingDelegate; } + void RegisterPairingDelegate(DevicePairingDelegate * pairingDelegate) + { + mPairingDelegate = pairingDelegate; + } + DevicePairingDelegate * GetPairingDelegate() const + { + return mPairingDelegate; + } // ClusterStateCache::Callback impl void OnDone(app::ReadClient *) override; @@ -715,7 +729,9 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, /* Callback when the previously sent CSR request results in failure */ static void OnCSRFailureResponse(void * context, CHIP_ERROR error); - void ExtendArmFailSafeForFailedDeviceAttestation(Credentials::AttestationVerificationResult result); + void ClearAttestationDeviceInfo(); + void ExtendArmFailSafeForDeviceAttestation(const Credentials::DeviceAttestationVerifier::AttestationInfo & info, + Credentials::AttestationVerificationResult result); static void OnCertificateChainFailureResponse(void * context, CHIP_ERROR error); static void OnCertificateChainResponse( void * context, const app::Clusters::OperationalCredentials::Commands::CertificateChainResponse::DecodableType & response); @@ -754,7 +770,9 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, static void OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle); static void OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR error); - static void OnDeviceAttestationInformationVerification(void * context, Credentials::AttestationVerificationResult result); + static void OnDeviceAttestationInformationVerification(void * context, + const Credentials::DeviceAttestationVerifier::AttestationInfo & info, + Credentials::AttestationVerificationResult result); static void OnDeviceNOCChainGeneration(void * context, CHIP_ERROR status, const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, Optional ipk, Optional adminSubject); @@ -779,9 +797,9 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, const app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data); static void OnDisarmFailsafeFailure(void * context, CHIP_ERROR error); void DisarmDone(); - static void OnArmFailSafeExtendedForFailedDeviceAttestation( + static void OnArmFailSafeExtendedForDeviceAttestation( void * context, const chip::app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data); - static void OnFailedToExtendedArmFailSafeFailedDeviceAttestation(void * context, CHIP_ERROR error); + static void OnFailedToExtendedArmFailSafeDeviceAttestation(void * context, CHIP_ERROR error); /** * @brief @@ -860,7 +878,8 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, chip::Callback::Callback mOnDeviceConnectedCallback; chip::Callback::Callback mOnDeviceConnectionFailureCallback; - chip::Callback::Callback mDeviceAttestationInformationVerificationCallback; + chip::Callback::Callback + mDeviceAttestationInformationVerificationCallback; chip::Callback::Callback mDeviceNOCChainCallback; SetUpCodePairer mSetUpCodePairer; @@ -874,7 +893,8 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController, Platform::UniquePtr mAttributeCache; Platform::UniquePtr mReadClient; Credentials::AttestationVerificationResult mAttestationResult; - Credentials::DeviceAttestationVerifier * mDeviceAttestationVerifier = nullptr; + Credentials::DeviceAttestationVerifier::AttestationDeviceInfo * mAttestationDeviceInfo = nullptr; + Credentials::DeviceAttestationVerifier * mDeviceAttestationVerifier = nullptr; }; } // namespace Controller diff --git a/src/controller/CommissioningDelegate.h b/src/controller/CommissioningDelegate.h index 1e3fdef1276e44..628f096a00b2e8 100644 --- a/src/controller/CommissioningDelegate.h +++ b/src/controller/CommissioningDelegate.h @@ -414,6 +414,7 @@ class CommissioningParameters CompletionStatus completionStatus; Credentials::DeviceAttestationDelegate * mDeviceAttestationDelegate = nullptr; // Delegate to handle device attestation failures during commissioning + Optional mShouldWaitPostDeviceAttestation; Optional mAttemptWiFiNetworkScan; Optional mAttemptThreadNetworkScan; // This automatically gets set to false when a ThreadOperationalDataset is set Optional mSkipCommissioningComplete; diff --git a/src/credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.cpp b/src/credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.cpp index 4dd6ab7d35a8f2..0d40f240fdcec4 100644 --- a/src/credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DacOnlyPartialAttestationVerifier.cpp @@ -148,7 +148,7 @@ void PartialDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer } exit: - onCompletion->mCall(onCompletion->mContext, attestationError); + onCompletion->mCall(onCompletion->mContext, info, attestationError); } } // namespace Credentials diff --git a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp index 07db1d3726d9b9..f60d4b2d70d325 100644 --- a/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DefaultDeviceAttestationVerifier.cpp @@ -277,7 +277,7 @@ void DefaultDACVerifier::VerifyAttestationInformation(const DeviceAttestationVer } exit: - onCompletion->mCall(onCompletion->mContext, attestationError); + onCompletion->mCall(onCompletion->mContext, info, attestationError); } AttestationVerificationResult DefaultDACVerifier::ValidateCertificationDeclarationSignature(const ByteSpan & cmsEnvelopeBuffer, diff --git a/src/credentials/attestation_verifier/DeviceAttestationDelegate.h b/src/credentials/attestation_verifier/DeviceAttestationDelegate.h index 41ad284932abcf..874ca533244656 100644 --- a/src/credentials/attestation_verifier/DeviceAttestationDelegate.h +++ b/src/credentials/attestation_verifier/DeviceAttestationDelegate.h @@ -48,14 +48,29 @@ class DeviceAttestationDelegate /** * @brief * This method is invoked when device attestation fails for a device that is being commissioned. The client - * handling the failure has the option to continue commissionning or fail the operation. + * handling the failure has the option to continue commissioning or fail the operation. + * + * If ShouldWaitAfterDeviceAttestation returns false, then in the case attestationResult is successful, the + * commissioner would finish commissioning the device after OnDeviceAttestationCompleted returns. + * + * If ShouldWaitAfterDeviceAttestation returns true, then the commissioner will always wait for a + * ContinueCommissioningAfterDeviceAttestation call after calling OnDeviceAttestationCompleted. * * @param deviceCommissioner The commissioner object that is commissioning the device * @param device The proxy represent the device being commissioned + * @param info The structure holding device infor for additional verification by the application * @param attestationResult The failure code for the device attestation validation operation */ - virtual void OnDeviceAttestationFailed(Controller::DeviceCommissioner * deviceCommissioner, DeviceProxy * device, - AttestationVerificationResult attestationResult) = 0; + virtual void OnDeviceAttestationCompleted(Controller::DeviceCommissioner * deviceCommissioner, DeviceProxy * device, + const DeviceAttestationVerifier::AttestationDeviceInfo & info, + AttestationVerificationResult attestationResult) = 0; + + /** + * @brief + * Override this method to return whether the attestation delegate wants the commissioner to wait for a + * ContinueCommissioningAfterDeviceAttestation call in the case attestationResult is successful. + */ + virtual bool ShouldWaitAfterDeviceAttestation() { return false; }; }; } // namespace Credentials diff --git a/src/credentials/attestation_verifier/DeviceAttestationVerifier.cpp b/src/credentials/attestation_verifier/DeviceAttestationVerifier.cpp index 68c69136893e19..de13abfe3b5e22 100644 --- a/src/credentials/attestation_verifier/DeviceAttestationVerifier.cpp +++ b/src/credentials/attestation_verifier/DeviceAttestationVerifier.cpp @@ -16,7 +16,9 @@ */ #include "DeviceAttestationVerifier.h" +#include #include +#include using namespace chip::Crypto; @@ -111,5 +113,42 @@ void SetDeviceAttestationVerifier(DeviceAttestationVerifier * verifier) gDacVerifier = verifier; } +ByteSpan DupByteSpanHelper(const ByteSpan & span_to_dup) +{ + uint8_t * bytes = (uint8_t *) chip::Platform::MemoryAlloc(span_to_dup.size()); + memcpy(bytes, span_to_dup.data(), span_to_dup.size()); + return ByteSpan(bytes, span_to_dup.size()); +} + +DeviceAttestationVerifier::AttestationDeviceInfo::AttestationDeviceInfo(const AttestationInfo & attestationInfo) : + mPaiDerBuffer(DupByteSpanHelper(attestationInfo.paiDerBuffer)), mDacDerBuffer(DupByteSpanHelper(attestationInfo.dacDerBuffer)) +{ + mAttestationElementsBufferCopy = chip::Platform::MemoryAlloc(attestationInfo.attestationElementsBuffer.size()); + memcpy(mAttestationElementsBufferCopy, attestationInfo.attestationElementsBuffer.data(), + attestationInfo.attestationElementsBuffer.size()); + + ByteSpan certificationDeclarationSpan; + ByteSpan attestationNonceSpan; + uint32_t timestampDeconstructed; + ByteSpan firmwareInfoSpan; + DeviceAttestationVendorReservedDeconstructor vendorReserved; + + ByteSpan attestationElementsBufferSpan = + ByteSpan((uint8_t *) mAttestationElementsBufferCopy, attestationInfo.attestationElementsBuffer.size()); + + if (DeconstructAttestationElements(attestationElementsBufferSpan, certificationDeclarationSpan, attestationNonceSpan, + timestampDeconstructed, firmwareInfoSpan, vendorReserved) == CHIP_NO_ERROR) + { + mCdBuffer = MakeOptional(certificationDeclarationSpan); + } +} + +DeviceAttestationVerifier::AttestationDeviceInfo::~AttestationDeviceInfo() +{ + chip::Platform::MemoryFree(mAttestationElementsBufferCopy); + chip::Platform::MemoryFree((void *) mPaiDerBuffer.data()); + chip::Platform::MemoryFree((void *) mPaiDerBuffer.data()); +} + } // namespace Credentials } // namespace chip diff --git a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h index 756759d32ebe03..4ef5cfcd8bd11f 100644 --- a/src/credentials/attestation_verifier/DeviceAttestationVerifier.h +++ b/src/credentials/attestation_verifier/DeviceAttestationVerifier.h @@ -108,8 +108,6 @@ struct DeviceInfoForAttestation uint8_t paaSKID[Crypto::kSubjectKeyIdentifierLength] = { 0 }; }; -typedef void (*OnAttestationInformationVerification)(void * context, AttestationVerificationResult result); - /** * @brief Helper utility to model a basic trust store usable for device attestation verifiers. * @@ -125,7 +123,7 @@ class AttestationTrustStore virtual ~AttestationTrustStore() = default; // Not copyable - AttestationTrustStore(const AttestationTrustStore &) = delete; + AttestationTrustStore(const AttestationTrustStore &) = delete; AttestationTrustStore & operator=(const AttestationTrustStore &) = delete; /** @@ -198,7 +196,7 @@ class DeviceAttestationVerifier virtual ~DeviceAttestationVerifier() = default; // Not copyable - DeviceAttestationVerifier(const DeviceAttestationVerifier &) = delete; + DeviceAttestationVerifier(const DeviceAttestationVerifier &) = delete; DeviceAttestationVerifier & operator=(const DeviceAttestationVerifier &) = delete; struct AttestationInfo @@ -222,6 +220,25 @@ class DeviceAttestationVerifier uint16_t productId; }; + // Copies the bytes passed to it, and holds the PAI, DAC, and CD for additional verification step + class AttestationDeviceInfo + { + public: + AttestationDeviceInfo(const AttestationInfo & attestationInfo); + AttestationDeviceInfo(const ByteSpan & attestationElementsBuffer, const ByteSpan paiDerBuffer, const ByteSpan dacDerBuffer); + + ~AttestationDeviceInfo(); + + ByteSpan mPaiDerBuffer; // Buffer containing the PAI certificate from device in DER format. + ByteSpan mDacDerBuffer; // Buffer containing the DAC certificate from device in DER format. + Optional mCdBuffer; // Buffer containing the certificate declaration from device. + private: + void * mAttestationElementsBufferCopy; + }; + + typedef void (*OnAttestationInformationVerification)(void * context, const AttestationInfo & info, + AttestationVerificationResult result); + /** * @brief Verify an attestation information payload against a DAC/PAI chain. * diff --git a/src/darwin/CHIPTool/CHIPTool.xcodeproj/project.pbxproj b/src/darwin/CHIPTool/CHIPTool.xcodeproj/project.pbxproj index 659474185dd30e..f74be0ae716123 100644 --- a/src/darwin/CHIPTool/CHIPTool.xcodeproj/project.pbxproj +++ b/src/darwin/CHIPTool/CHIPTool.xcodeproj/project.pbxproj @@ -473,6 +473,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; @@ -498,7 +499,7 @@ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; }; name = Debug; }; @@ -534,6 +535,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -555,7 +557,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 13.4; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; VALIDATE_PRODUCT = YES; }; name = Release; @@ -565,7 +567,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = CHIPTool/CHIPTool.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; @@ -578,7 +579,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.matter.CHIPTool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -588,7 +589,6 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = CHIPTool/CHIPTool.entitlements; - CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; @@ -601,7 +601,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.matter.CHIPTool; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.h b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.h index 42ffa6212e9697..63e0672c2edd9d 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.h +++ b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.h @@ -21,12 +21,36 @@ NS_ASSUME_NONNULL_BEGIN @class MTRDeviceController; +@interface MTRDeviceAttestationDeviceInfo : NSObject +@property (nonatomic, readonly) NSData * dacCertificate; +@property (nonatomic, readonly) NSData * dacPAICertificate; +@property (nonatomic, readonly) NSData * certificateDeclaration; +@end + /** * The protocol definition for the MTRDeviceAttestationDelegate * * All delegate methods will be called on the callers queue. */ @protocol MTRDeviceAttestationDelegate +@optional +/** + * Notify the delegate when device attestation completed with device info for additional verification. If + * this callback is implemented, continueCommissioningDevice on MTRDeviceController is expected + * to be called if commisioning should continue. + * + * This allows the delegate to stop commissioning after examining the device info (DAC, PAI, CD). + * + * @param controller Controller corresponding to the commissioning process + * @param device Handle of device being commissioned + * @param attestationDeviceInfo Attestation information for the device + * @param error NSError representing the error code for the failure + */ +- (void)deviceAttestation:(MTRDeviceController *)controller + completedForDevice:(void *)device + attestationDeviceInfo:(MTRDeviceAttestationDeviceInfo *)attestationDeviceInfo + error:(NSError * _Nonnull)error; + /** * Notify the delegate when device attestation fails * diff --git a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.mm b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.mm new file mode 100644 index 00000000000000..83cd8d8a6288ab --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate.mm @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2022 Project CHIP Authors + * + * 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. + */ + +#import +#import + +@implementation MTRDeviceAttestationDeviceInfo +- (instancetype)initWithDACCertificate:(NSData *)dacCertificate + dacPAICertificate:(NSData *)dacPAICertificate + certificateDeclaration:(NSData *)certificateDeclaration +{ + if (self = [super init]) { + _dacCertificate = [dacCertificate copy]; + _dacPAICertificate = [dacPAICertificate copy]; + _certificateDeclaration = [certificateDeclaration copy]; + } + return self; +} +@end diff --git a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.h b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.h index cc44ef3584c91f..121b97f11b4a24 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.h +++ b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.h @@ -29,12 +29,13 @@ class MTRDeviceAttestationDelegateBridge : public chip::Credentials::DeviceAttes public: MTRDeviceAttestationDelegateBridge(MTRDeviceController * deviceController, id deviceAttestationDelegate, dispatch_queue_t queue, - chip::Optional expiryTimeoutSecs) + chip::Optional expiryTimeoutSecs, bool shouldWaitAfterDeviceAttestation = false) : mResult(chip::Credentials::AttestationVerificationResult::kSuccess) , mDeviceController(deviceController) , mDeviceAttestationDelegate(deviceAttestationDelegate) , mQueue(queue) , mExpiryTimeoutSecs(expiryTimeoutSecs) + , mShouldWaitAfterDeviceAttestation(shouldWaitAfterDeviceAttestation) { } @@ -42,9 +43,12 @@ class MTRDeviceAttestationDelegateBridge : public chip::Credentials::DeviceAttes chip::Optional FailSafeExpiryTimeoutSecs() const override { return mExpiryTimeoutSecs; } - void OnDeviceAttestationFailed(chip::Controller::DeviceCommissioner * deviceCommissioner, chip::DeviceProxy * device, + void OnDeviceAttestationCompleted(chip::Controller::DeviceCommissioner * deviceCommissioner, chip::DeviceProxy * device, + const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, chip::Credentials::AttestationVerificationResult attestationResult) override; + bool ShouldWaitAfterDeviceAttestation() override { return mShouldWaitAfterDeviceAttestation; } + chip::Credentials::AttestationVerificationResult attestationVerificationResult() const { return mResult; } private: @@ -53,6 +57,7 @@ class MTRDeviceAttestationDelegateBridge : public chip::Credentials::DeviceAttes id mDeviceAttestationDelegate; dispatch_queue_t mQueue; chip::Optional mExpiryTimeoutSecs; + bool mShouldWaitAfterDeviceAttestation; }; NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.mm b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.mm index db8f277ca8a144..2ce9ccc3df9349 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegateBridge.mm @@ -17,9 +17,11 @@ #import "MTRDeviceAttestationDelegateBridge.h" #import "MTRError_Internal.h" +#import -void MTRDeviceAttestationDelegateBridge::OnDeviceAttestationFailed(chip::Controller::DeviceCommissioner * deviceCommissioner, - chip::DeviceProxy * device, chip::Credentials::AttestationVerificationResult attestationResult) +void MTRDeviceAttestationDelegateBridge::OnDeviceAttestationCompleted(chip::Controller::DeviceCommissioner * deviceCommissioner, + chip::DeviceProxy * device, const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, + chip::Credentials::AttestationVerificationResult attestationResult) { dispatch_async(mQueue, ^{ NSLog(@"MTRDeviceAttestationDelegateBridge::OnDeviceAttestationFailed failed with result: %hu", attestationResult); @@ -27,7 +29,27 @@ mResult = attestationResult; id strongDelegate = mDeviceAttestationDelegate; - if ([strongDelegate respondsToSelector:@selector(deviceAttestation:failedForDevice:error:)]) { + if ([strongDelegate respondsToSelector:@selector(deviceAttestation:completedForDevice:attestationDeviceInfo:error:)]) { + MTRDeviceController * strongController = mDeviceController; + if (strongController) { + NSData * dacData = [NSData dataWithBytes:info.mDacDerBuffer.data() length:info.mDacDerBuffer.size()]; + NSData * paiData = [NSData dataWithBytes:info.mPaiDerBuffer.data() length:info.mPaiDerBuffer.size()]; + NSData * cdData = info.mCdBuffer.HasValue() + ? [NSData dataWithBytes:info.mCdBuffer.Value().data() length:info.mCdBuffer.Value().size()] + : nil; + MTRDeviceAttestationDeviceInfo * deviceInfo = + [[MTRDeviceAttestationDeviceInfo alloc] initWithDACCertificate:dacData + dacPAICertificate:paiData + certificateDeclaration:cdData]; + NSError * error = (attestationResult == chip::Credentials::AttestationVerificationResult::kSuccess) + ? nil + : [MTRError errorForCHIPErrorCode:CHIP_ERROR_INTEGRITY_CHECK_FAILED]; + [strongDelegate deviceAttestation:mDeviceController + completedForDevice:device + attestationDeviceInfo:deviceInfo + error:error]; + } + } else if ([strongDelegate respondsToSelector:@selector(deviceAttestation:failedForDevice:error:)]) { MTRDeviceController * strongController = mDeviceController; if (strongController) { diff --git a/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate_Internal.h b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate_Internal.h new file mode 100644 index 00000000000000..aa6eec6917a989 --- /dev/null +++ b/src/darwin/Framework/CHIP/MTRDeviceAttestationDelegate_Internal.h @@ -0,0 +1,33 @@ +/** + * + * Copyright (c) 2022 Project CHIP Authors + * + * 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MTRDeviceAttestationDeviceInfo () +- (instancetype)initWithDACCertificate:(NSData *)dacCertificate + dacPAICertificate:(NSData *)dacPAICertificate + certificateDeclaration:(NSData *)certificateDeclaration; + +//@property (nonatomic, readonly) NSData * dacCertificate; +//@property (nonatomic, readonly) NSData * dacPAICertificate; +//@property (nonatomic, readonly) NSData * certificateDeclaration; + +@end + +NS_ASSUME_NONNULL_END diff --git a/src/darwin/Framework/CHIP/MTRDeviceController.mm b/src/darwin/Framework/CHIP/MTRDeviceController.mm index d122d28f93e16f..d97ab24c7d6114 100644 --- a/src/darwin/Framework/CHIP/MTRDeviceController.mm +++ b/src/darwin/Framework/CHIP/MTRDeviceController.mm @@ -472,7 +472,7 @@ - (BOOL)continueCommissioningDevice:(void *)device : chip::Credentials::AttestationVerificationResult::kSuccess; auto deviceProxy = static_cast(device); - auto errorCode = self.cppCommissioner->ContinueCommissioningAfterDeviceAttestationFailure(deviceProxy, + auto errorCode = self.cppCommissioner->ContinueCommissioningAfterDeviceAttestation(deviceProxy, ignoreAttestationFailure ? chip::Credentials::AttestationVerificationResult::kSuccess : lastAttestationResult); success = ![self checkForError:errorCode logMsg:kErrorPairDevice error:error]; }); diff --git a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj index 7102d13c7be998..3f81c4f79aab23 100644 --- a/src/darwin/Framework/Matter.xcodeproj/project.pbxproj +++ b/src/darwin/Framework/Matter.xcodeproj/project.pbxproj @@ -81,6 +81,8 @@ 5ACDDD7D27CD16D200EFD68A /* MTRAttributeCacheContainer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5ACDDD7C27CD16D200EFD68A /* MTRAttributeCacheContainer.mm */; }; 5ACDDD7E27CD3F3A00EFD68A /* MTRAttributeCacheContainer_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 5ACDDD7B27CD14AF00EFD68A /* MTRAttributeCacheContainer_Internal.h */; }; 5AE6D4E427A99041001F2493 /* MTRDeviceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AE6D4E327A99041001F2493 /* MTRDeviceTests.m */; }; + 7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7534F12628BFF20300390851 /* MTRDeviceAttestationDelegate.mm */; }; + 7534F12928BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7534F12728BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h */; }; 754F3DF427FBB94B00E60580 /* MTREventTLVValueDecoder_Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 754F3DF327FBB94B00E60580 /* MTREventTLVValueDecoder_Internal.h */; }; 7560FD1C27FBBD3F005E85B3 /* MTREventTLVValueDecoder.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7560FD1B27FBBD3F005E85B3 /* MTREventTLVValueDecoder.mm */; }; 7596A83E28751220004DAE0E /* MTRBaseClusters_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 7596A83D28751220004DAE0E /* MTRBaseClusters_internal.h */; }; @@ -217,6 +219,8 @@ 5ACDDD7B27CD14AF00EFD68A /* MTRAttributeCacheContainer_Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTRAttributeCacheContainer_Internal.h; sourceTree = ""; }; 5ACDDD7C27CD16D200EFD68A /* MTRAttributeCacheContainer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRAttributeCacheContainer.mm; sourceTree = ""; }; 5AE6D4E327A99041001F2493 /* MTRDeviceTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MTRDeviceTests.m; sourceTree = ""; }; + 7534F12628BFF20300390851 /* MTRDeviceAttestationDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MTRDeviceAttestationDelegate.mm; sourceTree = ""; }; + 7534F12728BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTRDeviceAttestationDelegate_Internal.h; sourceTree = ""; }; 754F3DF327FBB94B00E60580 /* MTREventTLVValueDecoder_Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTREventTLVValueDecoder_Internal.h; sourceTree = ""; }; 7560FD1B27FBBD3F005E85B3 /* MTREventTLVValueDecoder.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MTREventTLVValueDecoder.mm; path = "zap-generated/MTREventTLVValueDecoder.mm"; sourceTree = ""; }; 7596A83D28751220004DAE0E /* MTRBaseClusters_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MTRBaseClusters_internal.h; path = "zap-generated/MTRBaseClusters_internal.h"; sourceTree = ""; }; @@ -355,6 +359,8 @@ 27A53C1527FBC6920053F131 /* MTRAttestationTrustStoreBridge.h */, 27A53C1627FBC6920053F131 /* MTRAttestationTrustStoreBridge.mm */, 88EBF8CB27FABDD500686BC1 /* MTRDeviceAttestationDelegate.h */, + 7534F12728BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h */, + 7534F12628BFF20300390851 /* MTRDeviceAttestationDelegate.mm */, 88EBF8CD27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.h */, 88EBF8CC27FABDD500686BC1 /* MTRDeviceAttestationDelegateBridge.mm */, 513DDB852761F69300DAA01A /* MTRAttributeTLVValueDecoder_Internal.h */, @@ -500,6 +506,7 @@ 99D466E12798936D0089A18F /* MTRCommissioningParameters.h in Headers */, 5136661528067D550025EDAE /* MTRControllerFactory_Internal.h in Headers */, 515C1C70284F9FFB00A48F0C /* MTRMemory.h in Headers */, + 7534F12928BFF20300390851 /* MTRDeviceAttestationDelegate_Internal.h in Headers */, D4772A46285AE98400383630 /* MTRClusterConstants.h in Headers */, B289D4212639C0D300D4E314 /* MTROnboardingPayloadParser.h in Headers */, 513DDB862761F69300DAA01A /* MTRAttributeTLVValueDecoder_Internal.h in Headers */, @@ -680,6 +687,7 @@ 5136661428067D550025EDAE /* MTRControllerFactory.mm in Sources */, 51B22C2A2740CB47008D5055 /* MTRCommandPayloadsObjc.mm in Sources */, AF5F90FF2878D351005503FA /* MTROTAProviderDelegateBridge.mm in Sources */, + 7534F12828BFF20300390851 /* MTRDeviceAttestationDelegate.mm in Sources */, 2C5EEEF7268A85C400CAE3D3 /* MTRDeviceConnectionBridge.mm in Sources */, 51B22C262740CB32008D5055 /* MTRStructsObjc.mm in Sources */, 2C222AD1255C620600E446B9 /* MTRBaseDevice.mm in Sources */, @@ -781,6 +789,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -806,7 +815,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-Wl,-unexported_symbol,\"__Z*\""; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SUPPORTS_TEXT_BASED_API = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -934,6 +943,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; ENABLE_NS_ASSERTIONS = NO; @@ -952,7 +962,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-Wl,-unexported_symbol,\"__Z*\""; - SDKROOT = iphoneos; + SDKROOT = iphoneos.internal; SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx"; SUPPORTS_TEXT_BASED_API = YES; TARGETED_DEVICE_FAMILY = "1,2";