Skip to content

Commit

Permalink
[Android] Commissioner attestation delegate should be able to overrid…
Browse files Browse the repository at this point in the history
…e 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 <[email protected]>
Co-authored-by: panliming-tuya <[email protected]>
Co-authored-by: Yufeng Wang <[email protected]>
  • Loading branch information
4 people authored and pull[bot] committed Dec 6, 2023
1 parent ff81aea commit 1209421
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions src/controller/java/AndroidDeviceControllerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 21 additions & 1 deletion src/controller/java/AndroidDeviceControllerWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@
#include <controller/ExampleOperationalCredentialsIssuer.h>
#include <controller/ExamplePersistentStorage.h>
#else
#include "AndroidOperationalCredentialsIssuer.h"
#include <platform/android/AndroidChipPlatform-JNI.h>
#include <platform/android/CHIPP256KeypairBridge.h>
#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.
Expand Down Expand Up @@ -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<chip::Controller::DeviceCommissioner>;

Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down
86 changes: 85 additions & 1 deletion src/controller/java/CHIPDeviceController-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ static CHIP_ERROR ParseAttributePath(jobject attributePath, EndpointId & outEndp
static CHIP_ERROR ParseEventPathList(jobject eventPathList, std::vector<app::EventPathParams> & 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 {

Expand Down Expand Up @@ -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)
{
Expand All @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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<chip::DeviceProxy *>(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)
{
Expand Down Expand Up @@ -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<uint16_t> timeoutSecs = chip::MakeOptional(static_cast<uint16_t>(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;
}
Loading

0 comments on commit 1209421

Please sign in to comment.