From 11b57246d8830d62512fee654abc0d17e26faad4 Mon Sep 17 00:00:00 2001 From: panliming-tuya Date: Thu, 13 Oct 2022 20:18:52 +0800 Subject: [PATCH] [Android] Added mechanism to override device attestation failure based on client/user; Commissioner attestation delegate should be able to override success --- .../DeviceProvisioningFragment.kt | 38 ++- .../java/AndroidDeviceControllerWrapper.h | 16 ++ src/controller/java/BUILD.gn | 8 +- .../java/CHIPDeviceController-JNI.cpp | 87 ++++++- .../java/DeviceAttestationDelegateBridge.cpp | 117 +++++++++ .../java/DeviceAttestationDelegateBridge.h | 56 +++++ .../devicecontroller/AttestationInfo.java | 9 + .../ChipDeviceController.java | 229 +++++++++++++++++- .../DeviceAttestationDelegate.java | 48 ++++ src/setup_payload/java/BUILD.gn | 5 +- 10 files changed, 597 insertions(+), 16 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 f97647ce4ab18a..278c5edb29292e 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.ChipClient import com.google.chip.chiptool.GenericChipDeviceListener @@ -37,6 +42,7 @@ 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 @ExperimentalCoroutinesApi @@ -120,7 +126,37 @@ class DeviceProvisioningFragment : Fragment() { val deviceId = DeviceIdUtil.getNextAvailableId(requireContext()) val connId = bluetoothManager.connectionId - deviceController.pairDevice(gatt, connId, deviceId, deviceInfo.setupPinCode, networkCredentials) + deviceController.pairDevice(gatt, connId, deviceId, deviceInfo.setupPinCode, null, networkCredentials, object: DeviceAttestationFailureCallback { + override fun onDeviceAttestationFailed( + deviceControllerPtr: Long, + devicePtr: Long, + errorCode: Int + ) { + 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() + }) + } + + + }, 600) DeviceIdUtil.setNextAvailableId(requireContext(), deviceId + 1) } } diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index c1421863bbf29d..252b1bbf710c5e 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -33,6 +33,7 @@ #include #include "AndroidOperationalCredentialsIssuer.h" +#include "DeviceAttestationDelegateBridge.h" /** * This class contains all relevant information for the JNI view of CHIPDeviceController @@ -157,6 +158,19 @@ 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; @@ -187,6 +201,8 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::Credentials::PartialDACVerifier mPartialDACVerifier; + DeviceAttestationDelegateBridge * mDeviceAttestationDelegateBridge = nullptr; + AndroidDeviceControllerWrapper(ChipDeviceControllerPtr controller, AndroidOperationalCredentialsIssuerPtr opCredsIssuer) : mController(std::move(controller)), mOpCredsIssuer(std::move(opCredsIssuer)) {} diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index 61244807707b8b..2c6ad4cbff3062 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -37,6 +37,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", @@ -91,6 +93,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", @@ -122,7 +125,10 @@ android_library("java") { "zap-generated/chip/devicecontroller/ClusterWriteMapping.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) diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 6c047d8f9adf86..459808bf6aeca0 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -80,6 +80,8 @@ 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 failSafeExpiryTimeout, + DeviceAttestationDelegateBridge ** deviceAttestationDelegateBridge); namespace { @@ -382,7 +384,8 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr } JNI_METHOD(void, commissionDevice) -(JNIEnv * env, jobject self, jlong handle, jlong deviceId, jbyteArray csrNonce, jobject networkCredentials) +(JNIEnv * env, jobject self, jlong handle, jlong deviceId, jbyteArray csrNonce, jobject networkCredentials, + jobject deviceAttestationDelegate, jint failSafeExpiryTimeout) { chip::DeviceLayer::StackLock lock; CHIP_ERROR err = CHIP_NO_ERROR; @@ -396,7 +399,16 @@ JNI_METHOD(void, commissionDevice) err = wrapper->ApplyNetworkCredentials(commissioningParams, networkCredentials); VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_ERROR_INVALID_ARGUMENT); } - + if (deviceAttestationDelegate != nullptr) + { + wrapper->ClearDeviceAttestationDelegateBridge(); + DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge = nullptr; + err = CreateDeviceAttestationDelegateBridge(env, handle, deviceAttestationDelegate, + failSafeExpiryTimeout, &deviceAttestationDelegateBridge); + VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + wrapper->SetDeviceAttestationDelegateBridge(deviceAttestationDelegateBridge); + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } if (csrNonce != nullptr) { JniByteArray jniCsrNonce(env, csrNonce); @@ -413,7 +425,7 @@ JNI_METHOD(void, commissionDevice) JNI_METHOD(void, pairDevice) (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jint connObj, jlong pinCode, jbyteArray csrNonce, - jobject networkCredentials) + jobject networkCredentials, jobject deviceAttestationDelegate, jint failSafeExpiryTimeout) { chip::DeviceLayer::StackLock lock; CHIP_ERROR err = CHIP_NO_ERROR; @@ -436,8 +448,18 @@ JNI_METHOD(void, pairDevice) JniByteArray jniCsrNonce(env, csrNonce); commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } + if (deviceAttestationDelegate != nullptr) + { + wrapper->ClearDeviceAttestationDelegateBridge(); + DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge = nullptr; + err = CreateDeviceAttestationDelegateBridge(env, handle, deviceAttestationDelegate, + failSafeExpiryTimeout, &deviceAttestationDelegateBridge); + VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + wrapper->SetDeviceAttestationDelegateBridge(deviceAttestationDelegateBridge); + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); - +exit: if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "Failed to pair the device."); @@ -447,7 +469,7 @@ JNI_METHOD(void, pairDevice) JNI_METHOD(void, pairDeviceWithAddress) (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jstring address, jint port, jint discriminator, jlong pinCode, - jbyteArray csrNonce) + jbyteArray csrNonce, jobject deviceAttestationDelegate, jint failSafeExpiryTimeout) { chip::DeviceLayer::StackLock lock; CHIP_ERROR err = CHIP_NO_ERROR; @@ -469,8 +491,18 @@ JNI_METHOD(void, pairDeviceWithAddress) JniByteArray jniCsrNonce(env, csrNonce); commissioningParams.SetCSRNonce(jniCsrNonce.byteSpan()); } + if (deviceAttestationDelegate != nullptr) + { + wrapper->ClearDeviceAttestationDelegateBridge(); + DeviceAttestationDelegateBridge * deviceAttestationDelegateBridge = nullptr; + err = CreateDeviceAttestationDelegateBridge(env, handle, deviceAttestationDelegate, + failSafeExpiryTimeout, &deviceAttestationDelegateBridge); + VerifyOrExit(err == CHIP_NO_ERROR, err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + wrapper->SetDeviceAttestationDelegateBridge(deviceAttestationDelegateBridge); + commissioningParams.SetDeviceAttestationDelegate(wrapper->GetDeviceAttestationDelegateBridge()); + } err = wrapper->Controller()->PairDevice(deviceId, rendezvousParams, commissioningParams); - +exit: if (err != CHIP_NO_ERROR) { ChipLogError(Controller, "Failed to pair the device."); @@ -521,6 +553,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) { @@ -1367,3 +1420,25 @@ CHIP_ERROR N2J_NetworkLocation(JNIEnv * env, jstring ipAddress, jint port, jint exit: return err; } + +CHIP_ERROR CreateDeviceAttestationDelegateBridge(JNIEnv * env, jlong handle, jobject deviceAttestationDelegate, jint failSafeExpiryTimeout, + DeviceAttestationDelegateBridge ** deviceAttestationDelegateBridge) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + chip::Optional timeoutSecs = chip::MakeOptional(static_cast(failSafeExpiryTimeout)); + 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(handle, deviceAttestationDelegateRef, timeoutSecs, shouldWaitAfterDeviceAttestation); +exit: + return err; +} \ No newline at end of file diff --git a/src/controller/java/DeviceAttestationDelegateBridge.cpp b/src/controller/java/DeviceAttestationDelegateBridge.cpp new file mode 100644 index 00000000000000..9510dac4c60cff --- /dev/null +++ b/src/controller/java/DeviceAttestationDelegateBridge.cpp @@ -0,0 +1,117 @@ +/** + * + * Copyright (c) 2020 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(), DAC.size(), javaDAC); + SuccessOrExit(err); + err = JniReferences::GetInstance().N2J_ByteArray(env, PAI.data(), PAI.size(), javaPAI); + SuccessOrExit(err); + if(certificationDeclarationSpan.HasValue()) + { + err = JniReferences::GetInstance().N2J_ByteArray(env, certificationDeclarationSpan.Value().data(), 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", 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", "(JJLchip/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, mDeviceController, 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", "(JJI)V", &onDeviceAttestationFailedMethod); + VerifyOrReturn(onDeviceAttestationFailedMethod != nullptr, ChipLogError(Controller, "Could not find deviceAttestation failed method")); + env->CallVoidMethod(mDeviceAttestationDelegate, onDeviceAttestationFailedMethod, mDeviceController, 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; + } +} \ No newline at end of file diff --git a/src/controller/java/DeviceAttestationDelegateBridge.h b/src/controller/java/DeviceAttestationDelegateBridge.h new file mode 100644 index 00000000000000..ada1dec48126d2 --- /dev/null +++ b/src/controller/java/DeviceAttestationDelegateBridge.h @@ -0,0 +1,56 @@ +/** + * + * Copyright (c) 2020 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(jlong deviceController, + jobject deviceAttestationDelegate, + chip::Optional expiryTimeoutSecs, + bool shouldWaitAfterDeviceAttestation) + : mResult(chip::Credentials::AttestationVerificationResult::kSuccess) + , mDeviceController(deviceController) + , 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; + jlong mDeviceController; + jobject mDeviceAttestationDelegate = nullptr; + chip::Optional mExpiryTimeoutSecs; + const bool mShouldWaitAfterDeviceAttestation; +}; \ No newline at end of file diff --git a/src/controller/java/src/chip/devicecontroller/AttestationInfo.java b/src/controller/java/src/chip/devicecontroller/AttestationInfo.java index cfa4e115ef28cd..3979d67f88f865 100644 --- a/src/controller/java/src/chip/devicecontroller/AttestationInfo.java +++ b/src/controller/java/src/chip/devicecontroller/AttestationInfo.java @@ -12,6 +12,15 @@ 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 62ed67132871d1..78d32f0378715f 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -20,6 +20,7 @@ import android.bluetooth.BluetoothGatt; import android.util.Log; import androidx.annotation.Nullable; +import chip.devicecontroller.DeviceAttestationDelegate.DeviceAttestationCompletionCallback; import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback; import chip.devicecontroller.model.ChipAttributePath; import chip.devicecontroller.model.ChipEventPath; @@ -122,7 +123,116 @@ public void pairDevice( Log.d(TAG, "Bluetooth connection added with ID: " + connectionId); Log.d(TAG, "Pairing device with ID: " + deviceId); pairDevice( - deviceControllerPtr, deviceId, connectionId, setupPincode, csrNonce, networkCredentials); + deviceControllerPtr, deviceId, connectionId, setupPincode, csrNonce, networkCredentials, null, 0); + } else { + Log.e(TAG, "Bluetooth connection already in use."); + completionListener.onError(new Exception("Bluetooth connection already in use.")); + } + } + + /** + * Pair a device connected through BLE. + * + * If the completionCallback non-null, when + * {@link DeviceAttestationDelegate.DeviceAttestationCompletionCallback#onDeviceAttestationCompleted(long, long, AttestationInfo, int)} + * is received, {@link #continueCommissioning(long, boolean)} must be called. + * + * @param bleServer the BluetoothGatt representing the BLE + * connection to the + * device + * @param connId the BluetoothGatt Id representing the BLE + * connection to + * the device + * @param deviceId the node ID to assign to the device + * @param setupPincode the pincode for the device + * @param csrNonce the 32-byte CSR nonce to use, or null if we want + * to use an internally randomlygenerated CSR + * nonce. + * @param networkCredentials the credentials (Wi-Fi or Thread) to be + * provisioned + * @param completionCallback the callback will be invoked when + * deviceattestation completed with device + * info for additional verification. + * @param failSafeExpiryTimeout the value to set for the fail-safe timer before + * onDeviceAttestationCompleted is invoked. + */ + public void pairDevice( + BluetoothGatt bleServer, + int connId, + long deviceId, + long setupPincode, + @Nullable byte[] csrNonce, + NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate.DeviceAttestationCompletionCallback completionCallback, + int failSafeExpiryTimeout) { + if (connectionId == 0) { + connectionId = connId; + + if (connectionId == 0) { + Log.e(TAG, "Failed to add Bluetooth connection."); + completionListener.onError(new Exception("Failed to add Bluetooth connection.")); + return; + } + + Log.d(TAG, "Bluetooth connection added with ID: " + connectionId); + Log.d(TAG, "Pairing device with ID: " + deviceId); + pairDevice( + deviceControllerPtr, deviceId, connectionId, setupPincode, csrNonce, networkCredentials, + completionCallback, failSafeExpiryTimeout); + } else { + Log.e(TAG, "Bluetooth connection already in use."); + completionListener.onError(new Exception("Bluetooth connection already in use.")); + } + } + + /** + * Pair a device connected through BLE. + * + * If the failureCallback non-null, when + * {@link DeviceAttestationDelegate.DeviceAttestationFailureCallback#onDeviceAttestationFailed(long, long, int)} + * is received, {@link #continueCommissioning(long, boolean)} must be called. + * + * @param bleServer the BluetoothGatt representing the BLE + * connection to the + * device + * @param connId the BluetoothGatt Id representing the BLE + * connection to + * the device + * @param deviceId the node ID to assign to the device + * @param setupPincode the pincode for the device + * @param csrNonce the 32-byte CSR nonce to use, or null if we want + * to use an internally randomly generated CSR + * nonce. + * @param networkCredentials the credentials (Wi-Fi or Thread) to be + * provisioned + * @param failureCallback the callback will be invoked when device + * attestation failed. + * @param failSafeExpiryTimeout the value to set for the fail-safe timer before + * onDeviceAttestationFailed is invoked. + */ + public void pairDevice( + BluetoothGatt bleServer, + int connId, + long deviceId, + long setupPincode, + @Nullable byte[] csrNonce, + NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate.DeviceAttestationFailureCallback failureCallback, + int failSafeExpiryTimeout) { + if (connectionId == 0) { + connectionId = connId; + + if (connectionId == 0) { + Log.e(TAG, "Failed to add Bluetooth connection."); + completionListener.onError(new Exception("Failed to add Bluetooth connection.")); + return; + } + + Log.d(TAG, "Bluetooth connection added with ID: " + connectionId); + Log.d(TAG, "Pairing device with ID: " + deviceId); + pairDevice( + deviceControllerPtr, deviceId, connectionId, setupPincode, csrNonce, networkCredentials, + failureCallback, failSafeExpiryTimeout); } else { Log.e(TAG, "Bluetooth connection already in use."); completionListener.onError(new Exception("Bluetooth connection already in use.")); @@ -137,7 +247,36 @@ public void pairDeviceWithAddress( long pinCode, @Nullable byte[] csrNonce) { pairDeviceWithAddress( - deviceControllerPtr, deviceId, address, port, discriminator, pinCode, csrNonce); + deviceControllerPtr, deviceId, address, port, discriminator, pinCode, csrNonce, null, 0); + } + + public void pairDeviceWithAddress( + long deviceId, + String address, + int port, + int discriminator, + long pinCode, + @Nullable byte[] csrNonce, + @Nullable DeviceAttestationDelegate.DeviceAttestationCompletionCallback completionCallback, + int failSafeExpiryTimeout) { + pairDeviceWithAddress( + deviceControllerPtr, deviceId, address, port, discriminator, pinCode, csrNonce, completionCallback, + failSafeExpiryTimeout); + } + + public void pairDeviceWithAddress( + long deviceId, + String address, + int port, + int discriminator, + long pinCode, + @Nullable byte[] csrNonce, + @Nullable DeviceAttestationDelegate.DeviceAttestationFailureCallback failureCallback, + int failSafeExpiryTimeout) { + pairDeviceWithAddress( + deviceControllerPtr, deviceId, address, port, discriminator, pinCode, csrNonce, + failureCallback, + failSafeExpiryTimeout); } public void establishPaseConnection(long deviceId, int connId, long setupPincode) { @@ -181,7 +320,7 @@ public void establishPaseConnection(long deviceId, String address, int port, lon * @param networkCredentials the credentials (Wi-Fi or Thread) to be provisioned */ public void commissionDevice(long deviceId, @Nullable NetworkCredentials networkCredentials) { - commissionDevice(deviceControllerPtr, deviceId, /* csrNonce= */ null, networkCredentials); + commissionDevice(deviceControllerPtr, deviceId, /* csrNonce= */ null, networkCredentials, null, 0); } /** @@ -195,9 +334,76 @@ public void commissionDevice(long deviceId, @Nullable NetworkCredentials network */ public void commissionDevice( long deviceId, @Nullable byte[] csrNonce, @Nullable NetworkCredentials networkCredentials) { - commissionDevice(deviceControllerPtr, deviceId, csrNonce, networkCredentials); + commissionDevice(deviceControllerPtr, deviceId, csrNonce, networkCredentials, null, 0); + } + + /** + * Initiates the automatic commissioning flow using the specified network + * credentials. It is + * expected that a secure session has already been established via {@link + * #establishPaseConnection(long, int, long)}. + * + * If the completionCallback non-null, when + * {@link DeviceAttestationDelegate.DeviceAttestationCompletionCallback#onDeviceAttestationCompleted(long, long, AttestationInfo, int)} + * is received, {@link #continueCommissioning(long, boolean)} must be called. + * + * @param deviceId the ID of the node to be commissioned + * @param csrNonce a nonce to be used for the CSR request + * @param networkCredentials the credentials (Wi-Fi or Thread) to be + * provisioned + * @param completionCallback the callback will be invoked when device + * attestation completed with device + * info for additional verification. + * @param failSafeExpiryTimeout the value to set for the fail-safe timer before + * onDeviceAttestationCompleted is invoked. + */ + public void commissionDevice( + long deviceId, @Nullable byte[] csrNonce, @Nullable NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate.DeviceAttestationCompletionCallback completionCallback, + int failSafeExpiryTimeout) { + commissionDevice(deviceControllerPtr, deviceId, csrNonce, networkCredentials, completionCallback, + failSafeExpiryTimeout); + } + + /** + * Initiates the automatic commissioning flow using the specified network + * credentials. It is + * expected that a secure session has already been established via {@link + * #establishPaseConnection(long, int, long)}. + * + * If the failureCallback non-null, when + * {@link DeviceAttestationDelegate.DeviceAttestationFailureCallback#onDeviceAttestationFailed(long, long, int)} + * is received, {@link #continueCommissioning(long, boolean)} must be called. + * + * @param deviceId the ID of the node to be commissioned + * @param csrNonce a nonce to be used for the CSR request + * @param networkCredentials the credentials (Wi-Fi or Thread) to be + * provisioned + * @param failureCallback the callback will be invoked when device + * attestation failed. + * @param failSafeExpiryTimeout the value to set for the fail-safe timer before + * onDeviceAttestationFailed is invoked. + */ + public void commissionDevice( + long deviceId, @Nullable byte[] csrNonce, @Nullable NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate.DeviceAttestationFailureCallback failureCallback, + int failSafeExpiryTimeout) { + commissionDevice(deviceControllerPtr, deviceId, csrNonce, networkCredentials, + failureCallback, + failSafeExpiryTimeout); } + /** + * 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 @@ -618,7 +824,9 @@ private native void pairDevice( int connectionId, long pinCode, @Nullable byte[] csrNonce, - NetworkCredentials networkCredentials); + NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate delegate, + int failSafeExpiryTimeout); private native void pairDeviceWithAddress( long deviceControllerPtr, @@ -627,7 +835,9 @@ private native void pairDeviceWithAddress( int port, int discriminator, long pinCode, - @Nullable byte[] csrNonce); + @Nullable byte[] csrNonce, + @Nullable DeviceAttestationDelegate delegate, + int failSafeExpiryTimeout); private native void establishPaseConnection( long deviceControllerPtr, long deviceId, int connId, long setupPincode); @@ -639,7 +849,12 @@ private native void commissionDevice( long deviceControllerPtr, long deviceId, @Nullable byte[] csrNonce, - @Nullable NetworkCredentials networkCredentials); + @Nullable NetworkCredentials networkCredentials, + @Nullable DeviceAttestationDelegate delegate, + int failSafeExpiryTimeout); + + private native void continueCommissioning( + long deviceControllerPtr, long devicePtr, boolean ignoreAttestationFailure); private native void unpairDevice(long deviceControllerPtr, long deviceId); 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..2dfe68d10dbb43 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/DeviceAttestationDelegate.java @@ -0,0 +1,48 @@ +package chip.devicecontroller; + +/** + * Only one of the following delegate callbacks should be implemented. + *

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

+ * If DeviceAttestationFailureCallback is implemented, then it + * will always be called when device attestation completes. + */ +public interface DeviceAttestationDelegate { + + public interface DeviceAttestationCompletionCallback extends DeviceAttestationDelegate { + /** + * The callback will be invoked when device attestation completed with device + * info for additional verification. + * If this callback is implemented, continueCommissioningDevice on + * {@link ChipDeviceController} is expected + * to be called if commissioning should continue. + *

+ * This allows the callback to stop commissioning after examining the device + * info (DAC, PAI, CD). + * + * @param deviceControllerPtr Controller corresponding to the commissioning + * process + * @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 deviceControllerPtr, long devicePtr, AttestationInfo attestationInfo, + int errorCode); + } + + public interface DeviceAttestationFailureCallback extends DeviceAttestationDelegate { + /** + * The callback will be invoked when device attestation failed. + * + * @param deviceControllerPtr Controller corresponding to the commissioning + * process + * @param devicePtr Handle of device being commissioned + * @param errorCode Error code for the failure. + */ + void onDeviceAttestationFailed(long deviceControllerPtr, long devicePtr, int errorCode); + } +} diff --git a/src/setup_payload/java/BUILD.gn b/src/setup_payload/java/BUILD.gn index f2b922a2a34b2d..1188190c6eb8aa 100644 --- a/src/setup_payload/java/BUILD.gn +++ b/src/setup_payload/java/BUILD.gn @@ -46,7 +46,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)