From 120942116f1c48bf51ba935e87b8d1fe3ed11d0d Mon Sep 17 00:00:00 2001 From: panliming-tuya Date: Thu, 12 Jan 2023 11:46:30 +0800 Subject: [PATCH] [Android] Commissioner attestation delegate should be able to override success and failure (#23173) * [Android] Added mechanism to override device attestation failure based on client/user; Commissioner attestation delegate should be able to override success * platform jar keep name of method parameters * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * Restyled by gn * fix copyright * fix and modify comments * Use setters instead of adding parameters to methods * fix NetworkCredentials NPE * Do not expose deviceController raw pointer * add sample * Create AttestationTrustStoreBridge when we know we have PAA certs. * fix jni method * fix certs loss of scope and add some comments * implement destructor * fix destructor crash * revoke vscode setting change * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * fix conflict * add unit of failSafeExpiryTimeout * add sample code * Restyled by whitespace * Restyled by google-java-format * Restyled by clang-format * fix comments * remove android attestation trust store * restyle * Fix compile error in java-matter-controller Co-authored-by: Restyled.io Co-authored-by: panliming-tuya Co-authored-by: Yufeng Wang --- .../DeviceProvisioningFragment.kt | 41 +++++- .../java/AndroidDeviceControllerWrapper.cpp | 6 + .../java/AndroidDeviceControllerWrapper.h | 22 +++- src/controller/java/BUILD.gn | 8 +- .../java/CHIPDeviceController-JNI.cpp | 86 +++++++++++- .../java/DeviceAttestationDelegateBridge.cpp | 124 ++++++++++++++++++ .../java/DeviceAttestationDelegateBridge.h | 51 +++++++ .../devicecontroller/AttestationInfo.java | 6 + .../ChipDeviceController.java | 59 ++++++++- .../DeviceAttestationDelegate.java | 53 ++++++++ src/platform/android/BUILD.gn | 5 +- src/setup_payload/java/BUILD.gn | 5 +- 12 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 src/controller/java/DeviceAttestationDelegateBridge.cpp create mode 100644 src/controller/java/DeviceAttestationDelegateBridge.h create mode 100644 src/controller/java/src/chip/devicecontroller/DeviceAttestationDelegate.java diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt index 8dd548c5e44cab..62135d93eca831 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt @@ -19,14 +19,19 @@ package com.google.chip.chiptool.provisioning import android.bluetooth.BluetoothGatt +import android.content.DialogInterface import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import chip.devicecontroller.AttestationInfo +import chip.devicecontroller.DeviceAttestationDelegate.DeviceAttestationCompletionCallback +import chip.devicecontroller.DeviceAttestationDelegate.DeviceAttestationFailureCallback import chip.devicecontroller.NetworkCredentials import com.google.chip.chiptool.NetworkCredentialsParcelable import com.google.chip.chiptool.ChipClient @@ -38,7 +43,9 @@ import com.google.chip.chiptool.util.DeviceIdUtil import com.google.chip.chiptool.util.FragmentUtil import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Runnable import kotlinx.coroutines.launch +import java.lang.IllegalArgumentException @ExperimentalCoroutinesApi class DeviceProvisioningFragment : Fragment() { @@ -132,7 +139,32 @@ class DeviceProvisioningFragment : Fragment() { if (thread != null) { network = NetworkCredentials.forThread(NetworkCredentials.ThreadCredentials(thread.operationalDataset)) } - + deviceController.setDeviceAttestationFailureCallback(DEVICE_ATTESTATION_FAILED_TIMEOUT + ) { devicePtr, errorCode -> + Log.i(TAG, "Device attestation errorCode: $errorCode, " + + "Look at 'src/credentials/attestation_verifier/DeviceAttestationVerifier.h' " + + "AttestationVerificationResult enum to understand the errors") + requireActivity().runOnUiThread(Runnable { + val alertDialog: AlertDialog? = activity?.let { + val builder = AlertDialog.Builder(it) + builder.apply { + setPositiveButton("Continue", + DialogInterface.OnClickListener { dialog, id -> + deviceController.continueCommissioning(devicePtr, true) + }) + setNegativeButton("No", + DialogInterface.OnClickListener { dialog, id -> + deviceController.continueCommissioning(devicePtr, false) + }) + } + builder.setTitle("Device Attestation") + builder.setMessage("Device Attestation failed for device under commissioning. Do you wish to continue pairing?") + // Create the AlertDialog + builder.create() + } + alertDialog?.show() + }) + } deviceController.pairDevice(gatt, connId, deviceId, deviceInfo.setupPinCode, network) DeviceIdUtil.setNextAvailableId(requireContext(), deviceId + 1) } @@ -203,6 +235,13 @@ class DeviceProvisioningFragment : Fragment() { private const val ARG_NETWORK_CREDENTIALS = "network_credentials" private const val STATUS_PAIRING_SUCCESS = 0 + /** + * Set for the fail-safe timer before onDeviceAttestationFailed is invoked. + * + * This time depends on the Commissioning timeout of your app. + */ + private const val DEVICE_ATTESTATION_FAILED_TIMEOUT = 600 + /** * Return a new instance of [DeviceProvisioningFragment]. [networkCredentialsParcelable] can be null for * IP commissioning. diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 0d479a845c1260..701d6d6683aad7 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -61,6 +61,12 @@ AndroidDeviceControllerWrapper::~AndroidDeviceControllerWrapper() mKeypairBridge = nullptr; } #endif // JAVA_MATTER_CONTROLLER_TEST + + if (mDeviceAttestationDelegateBridge != nullptr) + { + delete mDeviceAttestationDelegateBridge; + mDeviceAttestationDelegateBridge = nullptr; + } } void AndroidDeviceControllerWrapper::SetJavaObjectRef(JavaVM * vm, jobject obj) diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index 518596b356f96f..3267f7fc4519b5 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -35,11 +35,13 @@ #include #include #else -#include "AndroidOperationalCredentialsIssuer.h" #include #include #endif // JAVA_MATTER_CONTROLLER_TEST +#include "AndroidOperationalCredentialsIssuer.h" +#include "DeviceAttestationDelegateBridge.h" + /** * This class contains all relevant information for the JNI view of CHIPDeviceController * to handle all controller-related processing. @@ -178,6 +180,22 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel return mOpCredsIssuer.get(); } + void SetDeviceAttestationDelegateBridge(DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge) + { + mDeviceAttestationDelegateBridge = deviceAttestationDelegateBridge; + } + + DeviceAttestationDelegateBridge * GetDeviceAttestationDelegateBridge() { return mDeviceAttestationDelegateBridge; } + + void ClearDeviceAttestationDelegateBridge() + { + if (mDeviceAttestationDelegateBridge != nullptr) + { + delete mDeviceAttestationDelegateBridge; + mDeviceAttestationDelegateBridge = nullptr; + } + } + private: using ChipDeviceControllerPtr = std::unique_ptr; @@ -214,6 +232,8 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::Credentials::PartialDACVerifier mPartialDACVerifier; + DeviceAttestationDelegateBridge * mDeviceAttestationDelegateBridge = nullptr; + AndroidDeviceControllerWrapper(ChipDeviceControllerPtr controller, #ifdef JAVA_MATTER_CONTROLLER_TEST ExampleOperationalCredentialsIssuerPtr opCredsIssuer diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 979f69122bf3ab..cf1c5fae0c64ff 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -43,6 +43,8 @@ shared_library("jni") { "CHIPDefaultCallbacks.cpp", "CHIPDefaultCallbacks.h", "CHIPDeviceController-JNI.cpp", + "DeviceAttestationDelegateBridge.cpp", + "DeviceAttestationDelegateBridge.h", "zap-generated/CHIPAttributeTLVValueDecoder.cpp", "zap-generated/CHIPClustersWrite-JNI.cpp", "zap-generated/CHIPEventTLVValueDecoder.cpp", @@ -108,6 +110,7 @@ android_library("java") { "src/chip/devicecontroller/ChipDeviceController.java", "src/chip/devicecontroller/ChipDeviceControllerException.java", "src/chip/devicecontroller/ControllerParams.java", + "src/chip/devicecontroller/DeviceAttestationDelegate.java", "src/chip/devicecontroller/DiscoveredDevice.java", "src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java", "src/chip/devicecontroller/KeypairDelegate.java", @@ -151,7 +154,10 @@ android_library("java") { data_deps += [ "${chip_root}/build/chip/java:shared_cpplib" ] } - javac_flags = [ "-Xlint:deprecation" ] + javac_flags = [ + "-Xlint:deprecation", + "-parameters", # Store infomation about method parameters + ] # TODO: add classpath support (we likely need to add something like # ..../platforms/android-21/android.jar to access BLE items) diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 5716efe361ac0e..3345995b64cab3 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -86,6 +86,9 @@ static CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndp static CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector & outEventPathParamsList); static CHIP_ERROR ParseEventPath(jobject eventPath, EndpointId & outEndpointId, ClusterId & outClusterId, EventId & outEventId); static CHIP_ERROR IsWildcardChipPathId(jobject chipPathId, bool & isWildcard); +static CHIP_ERROR CreateDeviceAttestationDelegateBridge(JNIEnv * env, jlong handle, jobject deviceAttestationDelegate, + jint failSafeExpiryTimeoutSecs, + DeviceAttestationDelegateBridge ** deviceAttestationDelegateBridge); namespace { @@ -410,6 +413,31 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr return result; } +JNI_METHOD(void, setDeviceAttestationDelegate) +(JNIEnv * env, jobject self, jlong handle, jint failSafeExpiryTimeoutSecs, jobject deviceAttestationDelegate) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + + ChipLogProgress(Controller, "setDeviceAttestationDelegate() called"); + if (deviceAttestationDelegate != nullptr) + { + wrapper->ClearDeviceAttestationDelegateBridge(); + DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge = nullptr; + err = CreateDeviceAttestationDelegateBridge(env, handle, deviceAttestationDelegate, failSafeExpiryTimeoutSecs, + &deviceAttestationDelegateBridge); + VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + wrapper->SetDeviceAttestationDelegateBridge(deviceAttestationDelegateBridge); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to set device attestation delegate."); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } +} + JNI_METHOD(void, commissionDevice) (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jbyteArray csrNonce, jobject networkCredentials) { @@ -425,7 +453,10 @@ JNI_METHOD(void, commissionDevice) err = wrapper->ApplyNetworkCredentials(commissioningParams, networkCredentials); VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_ERROR_INVALID_ARGUMENT); } - + if (wrapper->GetDeviceAttestationDelegateBridge() != nullptr) + { + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } if (csrNonce != nullptr) { JniByteArray jniCsrNonce(env, csrNonce); @@ -471,6 +502,10 @@ JNI_METHOD(void, pairDevice) JniByteArray jniCsrNonce(env, csrNonce); commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } + if (wrapper->GetDeviceAttestationDelegateBridge() != nullptr) + { + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); if (err != CHIP_NO_ERROR) @@ -510,6 +545,10 @@ JNI_METHOD(void, pairDeviceWithAddress) JniByteArray jniCsrNonce(env, csrNonce); commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } + if (wrapper->GetDeviceAttestationDelegateBridge() != nullptr) + { + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); if (err != CHIP_NO_ERROR) @@ -576,6 +615,27 @@ JNI_METHOD(void, establishPaseConnectionByAddress) } } +JNI_METHOD(void, continueCommissioning) +(JNIEnv * env, jobject self, jlong handle, jlong devicePtr, jboolean ignoreAttestationFailure) +{ + chip::DeviceLayer::StackLock lock; + ChipLogProgress(Controller, "continueCommissioning() called."); + CHIP_ERROR err = CHIP_NO_ERROR; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge = wrapper->GetDeviceAttestationDelegateBridge(); + auto lastAttestationResult = deviceAttestationDelegateBridge ? deviceAttestationDelegateBridge->attestationVerificationResult() + : chip::Credentials::AttestationVerificationResult::kSuccess; + chip::DeviceProxy * deviceProxy = reinterpret_cast(devicePtr); + err = wrapper->Controller()->ContinueCommissioningAfterDeviceAttestation( + deviceProxy, ignoreAttestationFailure ? chip::Credentials::AttestationVerificationResult::kSuccess : lastAttestationResult); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to continue commissioning."); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } +} + JNI_METHOD(void, setUseJavaCallbackForNOCRequest) (JNIEnv * env, jobject self, jlong handle, jboolean useCallback) { @@ -1487,3 +1547,27 @@ CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jint exit: return err; } + +CHIP_ERROR CreateDeviceAttestationDelegateBridge(JNIEnv * env, jlong handle, jobject deviceAttestationDelegate, + jint failSafeExpiryTimeoutSecs, + DeviceAttestationDelegateBridge ** deviceAttestationDelegateBridge) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::Optional timeoutSecs = chip::MakeOptional(static_cast(failSafeExpiryTimeoutSecs)); + bool shouldWaitAfterDeviceAttestation = false; + jclass completionCallbackCls = nullptr; + jobject deviceAttestationDelegateRef = env->NewGlobalRef(deviceAttestationDelegate); + VerifyOrExit(deviceAttestationDelegateRef != nullptr, err = CHIP_JNI_ERROR_NULL_OBJECT); + JniReferences::GetInstance().GetClassRef( + env, "chip/devicecontroller/DeviceAttestationDelegate$DeviceAttestationCompletionCallback", completionCallbackCls); + VerifyOrExit(completionCallbackCls != nullptr, err = CHIP_JNI_ERROR_TYPE_NOT_FOUND); + + if (env->IsInstanceOf(deviceAttestationDelegate, completionCallbackCls)) + { + shouldWaitAfterDeviceAttestation = true; + } + *deviceAttestationDelegateBridge = + new DeviceAttestationDelegateBridge(deviceAttestationDelegateRef, timeoutSecs, shouldWaitAfterDeviceAttestation); +exit: + return err; +} diff --git a/src/controller/java/DeviceAttestationDelegateBridge.cpp b/src/controller/java/DeviceAttestationDelegateBridge.cpp new file mode 100644 index 00000000000000..be46a783b197ad --- /dev/null +++ b/src/controller/java/DeviceAttestationDelegateBridge.cpp @@ -0,0 +1,124 @@ +/** + * + * 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. + */ + +#include "DeviceAttestationDelegateBridge.h" +#include +#include +#include +#include +#include + +using namespace chip; + +CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, + jobject & outAttestationInfo) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + jclass infoClass = nullptr; + jmethodID constructor = nullptr; + jbyteArray javaDAC = nullptr; + jbyteArray javaPAI = nullptr; + jbyteArray javaCD = nullptr; + const ByteSpan DAC = info.dacDerBuffer(); + const ByteSpan PAI = info.paiDerBuffer(); + const Optional certificationDeclarationSpan = info.cdBuffer(); + + err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/AttestationInfo", infoClass); + JniClass attestationInfoClass(infoClass); + SuccessOrExit(err); + + env->ExceptionClear(); + constructor = env->GetMethodID(infoClass, "", "([B[B[B)V"); + VerifyOrExit(constructor != nullptr, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND); + + err = JniReferences::GetInstance().N2J_ByteArray(env, DAC.data(), static_cast(DAC.size()), javaDAC); + SuccessOrExit(err); + err = JniReferences::GetInstance().N2J_ByteArray(env, PAI.data(), static_cast(PAI.size()), javaPAI); + SuccessOrExit(err); + if (certificationDeclarationSpan.HasValue()) + { + err = JniReferences::GetInstance().N2J_ByteArray(env, certificationDeclarationSpan.Value().data(), + static_cast(certificationDeclarationSpan.Value().size()), javaCD); + SuccessOrExit(err); + } + outAttestationInfo = (jobject) env->NewObject(infoClass, constructor, javaDAC, javaPAI, javaCD); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); +exit: + return err; +} + +void DeviceAttestationDelegateBridge::OnDeviceAttestationCompleted( + chip::Controller::DeviceCommissioner * deviceCommissioner, chip::DeviceProxy * device, + const chip::Credentials::DeviceAttestationVerifier::AttestationDeviceInfo & info, + chip::Credentials::AttestationVerificationResult attestationResult) +{ + ChipLogProgress(Controller, "OnDeviceAttestationCompleted with result: %hu", static_cast(attestationResult)); + + mResult = attestationResult; + if (mDeviceAttestationDelegate != nullptr) + { + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + jclass completionCallbackCls = nullptr; + JniReferences::GetInstance().GetClassRef( + env, "chip/devicecontroller/DeviceAttestationDelegate$DeviceAttestationCompletionCallback", completionCallbackCls); + VerifyOrReturn(completionCallbackCls != nullptr, + ChipLogError(Controller, "Could not find device attestation completion callback class.")); + jclass failureCallbackCls = nullptr; + JniReferences::GetInstance().GetClassRef( + env, "chip/devicecontroller/DeviceAttestationDelegate$DeviceAttestationFailureCallback", failureCallbackCls); + VerifyOrReturn(failureCallbackCls != nullptr, + ChipLogError(Controller, "Could not find device attestation failure callback class.")); + + if (env->IsInstanceOf(mDeviceAttestationDelegate, completionCallbackCls)) + { + jmethodID onDeviceAttestationCompletedMethod; + JniReferences::GetInstance().FindMethod(env, mDeviceAttestationDelegate, "onDeviceAttestationCompleted", + "(JLchip/devicecontroller/AttestationInfo;I)V", + &onDeviceAttestationCompletedMethod); + VerifyOrReturn(onDeviceAttestationCompletedMethod != nullptr, + ChipLogError(Controller, "Could not find deviceAttestation completed method")); + jobject javaAttestationInfo; + CHIP_ERROR err = N2J_AttestationInfo(env, info, javaAttestationInfo); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogError(Controller, "Failed to create AttestationInfo, error: %s", err.AsString())); + env->CallVoidMethod(mDeviceAttestationDelegate, onDeviceAttestationCompletedMethod, reinterpret_cast(device), + javaAttestationInfo, static_cast(attestationResult)); + } + else if ((attestationResult != chip::Credentials::AttestationVerificationResult::kSuccess) && + env->IsInstanceOf(mDeviceAttestationDelegate, failureCallbackCls)) + { + jmethodID onDeviceAttestationFailedMethod; + JniReferences::GetInstance().FindMethod(env, mDeviceAttestationDelegate, "onDeviceAttestationFailed", "(JI)V", + &onDeviceAttestationFailedMethod); + VerifyOrReturn(onDeviceAttestationFailedMethod != nullptr, + ChipLogError(Controller, "Could not find deviceAttestation failed method")); + env->CallVoidMethod(mDeviceAttestationDelegate, onDeviceAttestationFailedMethod, reinterpret_cast(device), + static_cast(attestationResult)); + } + } +} + +DeviceAttestationDelegateBridge::~DeviceAttestationDelegateBridge() +{ + if (mDeviceAttestationDelegate != nullptr) + { + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "Could not get JNIEnv for current thread")); + env->DeleteGlobalRef(mDeviceAttestationDelegate); + mDeviceAttestationDelegate = nullptr; + } +} diff --git a/src/controller/java/DeviceAttestationDelegateBridge.h b/src/controller/java/DeviceAttestationDelegateBridge.h new file mode 100644 index 00000000000000..258a53528eb4e3 --- /dev/null +++ b/src/controller/java/DeviceAttestationDelegateBridge.h @@ -0,0 +1,51 @@ +/** + * + * 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. + */ + +#include +#include +#include + +#include + +class DeviceAttestationDelegateBridge : public chip::Credentials::DeviceAttestationDelegate +{ +public: + DeviceAttestationDelegateBridge(jobject deviceAttestationDelegate, chip::Optional expiryTimeoutSecs, + bool shouldWaitAfterDeviceAttestation) : + mResult(chip::Credentials::AttestationVerificationResult::kSuccess), + mDeviceAttestationDelegate(deviceAttestationDelegate), mExpiryTimeoutSecs(expiryTimeoutSecs), + mShouldWaitAfterDeviceAttestation(shouldWaitAfterDeviceAttestation) + {} + + ~DeviceAttestationDelegateBridge(); + + chip::Optional FailSafeExpiryTimeoutSecs() const override { return mExpiryTimeoutSecs; } + + 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: + chip::Credentials::AttestationVerificationResult mResult; + jobject mDeviceAttestationDelegate = nullptr; + chip::Optional mExpiryTimeoutSecs; + const bool mShouldWaitAfterDeviceAttestation; +}; diff --git a/src/controller/java/src/chip/devicecontroller/AttestationInfo.java b/src/controller/java/src/chip/devicecontroller/AttestationInfo.java index cfa4e115ef28cd..097d6b8ecaf369 100644 --- a/src/controller/java/src/chip/devicecontroller/AttestationInfo.java +++ b/src/controller/java/src/chip/devicecontroller/AttestationInfo.java @@ -12,6 +12,12 @@ public final class AttestationInfo { private byte[] certificationDeclaration; private byte[] firmwareInfo; + public AttestationInfo(byte[] dac, byte[] pai, byte[] certificationDeclaration) { + this.dac = dac; + this.pai = pai; + this.certificationDeclaration = certificationDeclaration; + } + public AttestationInfo( byte[] challenge, byte[] nonce, diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index bc1b66838efcf1..bdcd60233c0b63 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -84,6 +84,45 @@ public void setNOCChainIssuer(NOCChainIssuer issuer) { nocChainIssuer = issuer; } + /** + * If DeviceAttestationCompletionCallback is setted, then it will always be called when device + * attestation completes. + * + *

When {@link + * DeviceAttestationDelegate.DeviceAttestationCompletionCallback#onDeviceAttestationCompleted(long, + * long, AttestationInfo, int)} is received, {@link #continueCommissioning(long, boolean)} must be + * called. + * + * @param failSafeExpiryTimeoutSecs the value to set for the fail-safe timer before + * onDeviceAttestationCompleted is invoked. The unit is seconds. + * @param completionCallback the callback will be invoked when deviceattestation completed with + * device info for additional verification. + */ + public void setDeviceAttestationCompletionCallback( + int failSafeExpiryTimeoutSecs, + DeviceAttestationDelegate.DeviceAttestationCompletionCallback completionCallback) { + setDeviceAttestationDelegate( + deviceControllerPtr, failSafeExpiryTimeoutSecs, completionCallback); + } + + /** + * If DeviceAttestationFailureCallback is setted, then it will be called when device attestation + * fails, and the client can decide to continue or stop the commissioning. + * + *

When {@link + * DeviceAttestationDelegate.DeviceAttestationFailureCallback#onDeviceAttestationFailed(long, + * long, int)} is received, {@link #continueCommissioning(long, boolean)} must be called. + * + * @param failSafeExpiryTimeoutSecs the value to set for the fail-safe timer before + * onDeviceAttestationFailed is invoked. The unit is seconds. + * @param failureCallback the callback will be invoked when device attestation failed. + */ + public void setDeviceAttestationFailureCallback( + int failSafeExpiryTimeoutSecs, + DeviceAttestationDelegate.DeviceAttestationFailureCallback failureCallback) { + setDeviceAttestationDelegate(deviceControllerPtr, failSafeExpiryTimeoutSecs, failureCallback); + } + public void pairDevice( BluetoothGatt bleServer, int connId, @@ -198,6 +237,17 @@ public void commissionDevice( commissionDevice(deviceControllerPtr, deviceId, csrNonce, networkCredentials); } + /** + * This function instructs the commissioner to proceed to the next stage of commissioning after + * attestation is reported. + * + * @param devicePtr a pointer to the device which is being commissioned. + * @param ignoreAttestationFailure whether to ignore device attestation failure. + */ + public void continueCommissioning(long devicePtr, boolean ignoreAttestationFailure) { + continueCommissioning(deviceControllerPtr, devicePtr, ignoreAttestationFailure); + } + /** * When a NOCChainIssuer is set for this controller, then onNOCChainGenerationNeeded will be * called when the NOC CSR needs to be signed. This allows for custom credentials issuer @@ -514,7 +564,8 @@ public void subscribeToPath( int maxInterval, boolean keepSubscriptions, boolean isFabricFiltered) { - // TODO: pass resubscriptionAttemptCallback to ReportCallbackJni since jni layer is not ready + // TODO: pass resubscriptionAttemptCallback to ReportCallbackJni since jni layer + // is not ready // for auto-resubscribe ReportCallbackJni jniCallback = new ReportCallbackJni(subscriptionEstablishedCallback, reportCallback, null); @@ -616,6 +667,9 @@ private native void read( private native long newDeviceController(ControllerParams params); + private native void setDeviceAttestationDelegate( + long deviceControllerPtr, int failSafeExpiryTimeoutSecs, DeviceAttestationDelegate delegate); + private native void pairDevice( long deviceControllerPtr, long deviceId, @@ -645,6 +699,9 @@ private native void commissionDevice( @Nullable byte[] csrNonce, @Nullable NetworkCredentials networkCredentials); + private native void continueCommissioning( + long deviceControllerPtr, long devicePtr, boolean ignoreAttestationFailure); + private native void unpairDevice(long deviceControllerPtr, long deviceId); private native void unpairDeviceCallback( diff --git a/src/controller/java/src/chip/devicecontroller/DeviceAttestationDelegate.java b/src/controller/java/src/chip/devicecontroller/DeviceAttestationDelegate.java new file mode 100644 index 00000000000000..f6de1f11333a32 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/DeviceAttestationDelegate.java @@ -0,0 +1,53 @@ +package chip.devicecontroller; + +/** + * Only one of the following delegate callbacks should be implemented. + * + *

If one of the following callbacks is implemented, {@link + * ChipDeviceController#continueCommissioning(long, boolean)} is expected to be called if + * commissioning should continue. + * + *

If DeviceAttestationCompletionCallback is implemented, then it will always be called when + * device attestation completes. + * + *

If DeviceAttestationFailureCallback is implemented, then it will be called when device + * attestation fails, and the client can decide to continue or stop the commissioning. + * + *

For example: + * + *

+ * // Continue commissioning
+ * deviceController.continueCommissioning(devicePtr, true)
+ *
+ * // Stop commissioning
+ * deviceController.continueCommissioning(devicePtr, false)
+ * 
+ */ +public interface DeviceAttestationDelegate { + + public interface DeviceAttestationCompletionCallback extends DeviceAttestationDelegate { + /** + * The callback will be invoked when device attestation completed with device info for + * additional verification. + * + *

This allows the callback to stop commissioning after examining the device info (DAC, PAI, + * CD). + * + * @param devicePtr Handle of device being commissioned + * @param attestationInfo Attestation information for the device + * @param errorCode Error code on attestation failure. 0 if success. + */ + void onDeviceAttestationCompleted( + long devicePtr, AttestationInfo attestationInfo, int errorCode); + } + + public interface DeviceAttestationFailureCallback extends DeviceAttestationDelegate { + /** + * The callback will be invoked when device attestation failed + * + * @param devicePtr Handle of device being commissioned + * @param errorCode Error code for the failure. + */ + void onDeviceAttestationFailed(long devicePtr, int errorCode); + } +} diff --git a/src/platform/android/BUILD.gn b/src/platform/android/BUILD.gn index 65fb67e76b5f2e..535664a45b8b8d 100644 --- a/src/platform/android/BUILD.gn +++ b/src/platform/android/BUILD.gn @@ -97,7 +97,10 @@ android_library("java") { "java/chip/platform/ServiceResolver.java", ] - javac_flags = [ "-Xlint:deprecation" ] + javac_flags = [ + "-Xlint:deprecation", + "-parameters", # Store infomation about method parameters + ] } java_prebuilt("android_sdk") { diff --git a/src/setup_payload/java/BUILD.gn b/src/setup_payload/java/BUILD.gn index 083cd6db55509f..fcf690b74ec236 100644 --- a/src/setup_payload/java/BUILD.gn +++ b/src/setup_payload/java/BUILD.gn @@ -54,7 +54,10 @@ android_library("java") { "src/chip/setuppayload/SetupPayloadParser.java", ] - javac_flags = [ "-Xlint:deprecation" ] + javac_flags = [ + "-Xlint:deprecation", + "-parameters", # Store infomation about method parameters + ] # TODO: add classpath support (we likely need to add something like # ..../platforms/android-21/android.jar to access BLE items)