diff --git a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt index f8686ce3220b70..0534f7f3ee3589 100644 --- a/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt +++ b/examples/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/ChipClient.kt @@ -22,6 +22,8 @@ import android.util.Log import chip.devicecontroller.ChipDeviceController import chip.devicecontroller.ControllerParams import chip.devicecontroller.GetConnectedDeviceCallbackJni.GetConnectedDeviceCallback +import chip.devicecontroller.ICDCheckInDelegate +import chip.devicecontroller.ICDClientInfo import chip.platform.AndroidBleManager import chip.platform.AndroidChipPlatform import chip.platform.ChipMdnsCallbackImpl @@ -60,6 +62,23 @@ object ChipClient { chipDeviceController.setAttestationTrustStoreDelegate( ExampleAttestationTrustStoreDelegate(chipDeviceController) ) + + chipDeviceController.setICDCheckInDelegate( + object : ICDCheckInDelegate { + override fun onCheckInComplete(info: ICDClientInfo) { + Log.d(TAG, "onCheckInComplete : $info") + } + + override fun onKeyRefreshNeeded(info: ICDClientInfo): ByteArray? { + Log.d(TAG, "onKeyRefreshNeeded : $info") + return null + } + + override fun onKeyRefreshDone(errorCode: Long) { + Log.d(TAG, "onKeyRefreshDone : $errorCode") + } + } + ) } return chipDeviceController diff --git a/src/controller/java/AndroidCheckInDelegate.cpp b/src/controller/java/AndroidCheckInDelegate.cpp new file mode 100644 index 00000000000000..1091738c804bbc --- /dev/null +++ b/src/controller/java/AndroidCheckInDelegate.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AndroidCheckInDelegate.h" + +#include +#include +#include +#include +#include + +#define PARSE_CLIENT_INFO(_clientInfo, _peerNodeId, _startCounter, _offset, _monitoredSubject, _jniICDAesKey, _jniICDHmacKey) \ + jlong _peerNodeId = static_cast(_clientInfo.peer_node.GetNodeId()); \ + jlong _startCounter = static_cast(_clientInfo.start_icd_counter); \ + jlong _offset = static_cast(_clientInfo.offset); \ + jlong _monitoredSubject = static_cast(_clientInfo.monitored_subject); \ + chip::ByteSpan aes_buf(_clientInfo.aes_key_handle.As()); \ + chip::ByteSpan hmac_buf(_clientInfo.hmac_key_handle.As()); \ + chip::ByteArray _jniICDAesKey(env, aes_buf); \ + chip::ByteArray _jniICDHmacKey(env, hmac_buf); + +namespace chip { +namespace app { + +CHIP_ERROR AndroidCheckInDelegate::Init(ICDClientStorage * storage, InteractionModelEngine * engine) +{ + VerifyOrReturnError(storage != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(mpStorage == nullptr, CHIP_ERROR_INCORRECT_STATE); + mpStorage = storage; + mpImEngine = engine; + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidCheckInDelegate::SetDelegate(jobject checkInDelegateObj) +{ + ReturnLogErrorOnFailure(mCheckInDelegate.Init(checkInDelegateObj)); + return CHIP_NO_ERROR; +} + +void AndroidCheckInDelegate::OnCheckInComplete(const ICDClientInfo & clientInfo) +{ + ChipLogProgress( + ICD, "Check In Message processing complete: start_counter=%" PRIu32 " offset=%" PRIu32 " nodeid=" ChipLogFormatScopedNodeId, + clientInfo.start_icd_counter, clientInfo.offset, ChipLogValueScopedNodeId(clientInfo.peer_node)); + + VerifyOrReturn(mCheckInDelegate.HasValidObjectRef(), ChipLogProgress(ICD, "check-in delegate is not implemented!")); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "JNIEnv is null!")); + PARSE_CLIENT_INFO(clientInfo, peerNodeId, startCounter, offset, monitoredSubject, jniICDAesKey, jniICDHmacKey) + + jmethodID onCheckInCompleteMethodID = nullptr; + CHIP_ERROR err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onCheckInComplete", + "(JJJJ[B[B)V", &onCheckInCompleteMethodID); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogProgress(ICD, "onCheckInComplete - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + env->CallVoidMethod(mCheckInDelegate.ObjectRef(), onCheckInCompleteMethodID, peerNodeId, startCounter, offset, monitoredSubject, + jniICDAesKey.jniValue(), jniICDHmacKey.jniValue()); +} + +RefreshKeySender * AndroidCheckInDelegate::OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + RefreshKeySender::RefreshKeyBuffer newKey; + + bool hasSetKey = false; + if (mCheckInDelegate.HasValidObjectRef()) + { + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnValue(env != nullptr, nullptr, ChipLogError(Controller, "JNIEnv is null!")); + + PARSE_CLIENT_INFO(clientInfo, peerNodeId, startCounter, offset, monitoredSubject, jniICDAesKey, jniICDHmacKey) + + jmethodID onKeyRefreshNeededMethodID = nullptr; + err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onKeyRefreshNeeded", "(JJJJ[B[B)V", + &onKeyRefreshNeededMethodID); + VerifyOrReturnValue(err == CHIP_NO_ERROR, nullptr, + ChipLogProgress(ICD, "onKeyRefreshNeeded - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + jbyteArray key = static_cast(env->CallObjectMethod(mCheckInDelegate.ObjectRef(), onKeyRefreshNeededMethodID, + peerNodeId, startCounter, offset, monitoredSubject, + jniICDAesKey.jniValue(), jniICDHmacKey.jniValue())); + + if (key != nullptr) + { + JniByteArray jniKey(env, key); + VerifyOrReturnValue(static_cast(jniKey.size()) == newKey.Capacity(), nullptr, + ChipLogProgress(ICD, "Invalid key length : %d", jniKey.size())); + memcpy(newKey.Bytes(), jniKey.data(), newKey.Capacity()); + hasSetKey = true; + } + } + else + { + ChipLogProgress(ICD, "check-in delegate is not implemented!"); + } + if (!hasSetKey) + { + err = Crypto::DRBG_get_bytes(newKey.Bytes(), newKey.Capacity()); + if (err != CHIP_NO_ERROR) + { + ChipLogError(ICD, "Generation of new key failed: %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + } + + auto refreshKeySender = Platform::New(this, clientInfo, clientStorage, mpImEngine, newKey); + if (refreshKeySender == nullptr) + { + return nullptr; + } + return refreshKeySender; +} + +void AndroidCheckInDelegate::OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) +{ + if (error == CHIP_NO_ERROR) + { + ChipLogProgress(ICD, "Re-registration with new key completed successfully"); + } + else + { + ChipLogError(ICD, "Re-registration with new key failed with error : %" CHIP_ERROR_FORMAT, error.Format()); + // The callee can take corrective action based on the error received. + } + + VerifyOrReturn(mCheckInDelegate.HasValidObjectRef(), ChipLogProgress(ICD, "check-in delegate is not implemented!")); + + JNIEnv * env = chip::JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr, ChipLogError(Controller, "JNIEnv is null!")); + + jmethodID onKeyRefreshDoneMethodID = nullptr; + CHIP_ERROR err = chip::JniReferences::GetInstance().FindMethod(env, mCheckInDelegate.ObjectRef(), "onKeyRefreshDone", "(J)V", + &onKeyRefreshDoneMethodID); + VerifyOrReturn(err == CHIP_NO_ERROR, + ChipLogProgress(ICD, "onKeyRefreshDone - FindMethod is failed! : %" CHIP_ERROR_FORMAT, err.Format())); + + env->CallVoidMethod(mCheckInDelegate.ObjectRef(), onKeyRefreshDoneMethodID, static_cast(error.AsInteger())); + + if (refreshKeySender != nullptr) + { + Platform::Delete(refreshKeySender); + refreshKeySender = nullptr; + } +} +} // namespace app +} // namespace chip diff --git a/src/controller/java/AndroidCheckInDelegate.h b/src/controller/java/AndroidCheckInDelegate.h new file mode 100644 index 00000000000000..5616b2815ed9b2 --- /dev/null +++ b/src/controller/java/AndroidCheckInDelegate.h @@ -0,0 +1,52 @@ +/* + * + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +namespace chip { +namespace app { + +using namespace std; + +class InteractionModelEngine; + +/// Callbacks for check in protocol +class AndroidCheckInDelegate : public CheckInDelegate +{ +public: + virtual ~AndroidCheckInDelegate() {} + CHIP_ERROR Init(ICDClientStorage * storage, InteractionModelEngine * engine); + void OnCheckInComplete(const ICDClientInfo & clientInfo) override; + RefreshKeySender * OnKeyRefreshNeeded(ICDClientInfo & clientInfo, ICDClientStorage * clientStorage) override; + void OnKeyRefreshDone(RefreshKeySender * refreshKeySender, CHIP_ERROR error) override; + + CHIP_ERROR SetDelegate(jobject checkInDelegateObj); + +private: + ICDClientStorage * mpStorage = nullptr; + InteractionModelEngine * mpImEngine = nullptr; + + JniGlobalReference mCheckInDelegate; +}; + +} // namespace app +} // namespace chip diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 0143d36e8d3c8e..d914bb5279f99e 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -689,6 +689,11 @@ CHIP_ERROR AndroidDeviceControllerWrapper::FinishOTAProvider() #endif } +CHIP_ERROR AndroidDeviceControllerWrapper::SetICDCheckInDelegate(jobject checkInDelegate) +{ + return mCheckInDelegate.SetDelegate(checkInDelegate); +} + void AndroidDeviceControllerWrapper::OnStatusUpdate(chip::Controller::DevicePairingDelegate::Status status) { chip::DeviceLayer::StackUnlock unlock; diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index fe56c02646942c..cc0152cdc66421 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -25,7 +25,6 @@ #include #include -#include #include #include #include @@ -43,6 +42,7 @@ #include #endif // JAVA_MATTER_CONTROLLER_TEST +#include "AndroidCheckInDelegate.h" #include "AndroidOperationalCredentialsIssuer.h" #include "AttestationTrustStoreBridge.h" #include "DeviceAttestationDelegateBridge.h" @@ -212,6 +212,8 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::app::DefaultICDClientStorage * getICDClientStorage() { return &mICDClientStorage; } + CHIP_ERROR SetICDCheckInDelegate(jobject checkInDelegate); + private: using ChipDeviceControllerPtr = std::unique_ptr; @@ -225,7 +227,7 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel chip::Crypto::RawKeySessionKeystore mSessionKeystore; chip::app::DefaultICDClientStorage mICDClientStorage; - chip::app::DefaultCheckInDelegate mCheckInDelegate; + chip::app::AndroidCheckInDelegate mCheckInDelegate; chip::app::CheckInHandler mCheckInHandler; JavaVM * mJavaVM = nullptr; diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index ff42d1b43a42f4..9e135aa5de2f34 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -41,6 +41,8 @@ shared_library("jni") { "AndroidCallbacks-JNI.cpp", "AndroidCallbacks.cpp", "AndroidCallbacks.h", + "AndroidCheckInDelegate.cpp", + "AndroidCheckInDelegate.h", "AndroidClusterExceptions.cpp", "AndroidClusterExceptions.h", "AndroidCommissioningWindowOpener.cpp", @@ -459,6 +461,8 @@ android_library("java") { "src/chip/devicecontroller/ExtendableInvokeCallbackJni.java", "src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java", "src/chip/devicecontroller/GroupKeySecurityPolicy.java", + "src/chip/devicecontroller/ICDCheckInDelegate.java", + "src/chip/devicecontroller/ICDCheckInDelegateWrapper.java", "src/chip/devicecontroller/ICDClientInfo.java", "src/chip/devicecontroller/ICDDeviceInfo.java", "src/chip/devicecontroller/ICDRegistrationInfo.java", diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 8890c881dc0c2c..f6509557650bee 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -588,6 +588,25 @@ JNI_METHOD(void, finishOTAProvider)(JNIEnv * env, jobject self, jlong handle) #endif } +JNI_METHOD(void, setICDCheckInDelegate)(JNIEnv * env, jobject self, jlong handle, jobject checkInDelegate) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + + VerifyOrExit(wrapper != nullptr, err = CHIP_ERROR_INCORRECT_STATE); + + ChipLogProgress(Controller, "setICDCheckInDelegate() called"); + + err = wrapper->SetICDCheckInDelegate(checkInDelegate); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to set ICD Check-In Deleagate. : %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } +} + JNI_METHOD(void, commissionDevice) (JNIEnv * env, jobject self, jlong handle, jlong deviceId, jbyteArray csrNonce, jobject networkCredentials) { diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index d6b31c59dabcd5..9064d71755ceb2 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -151,6 +151,11 @@ public void finishOTAProvider() { finishOTAProvider(deviceControllerPtr); } + /** Set the delegate of ICD check in */ + public void setICDCheckInDelegate(ICDCheckInDelegate delegate) { + setICDCheckInDelegate(deviceControllerPtr, new ICDCheckInDelegateWrapper(delegate)); + } + public void pairDevice( BluetoothGatt bleServer, int connId, @@ -1434,6 +1439,9 @@ private native void setAttestationTrustStoreDelegate( private native void finishOTAProvider(long deviceControllerPtr); + private native void setICDCheckInDelegate( + long deviceControllerPtr, ICDCheckInDelegateWrapper delegate); + private native void pairDevice( long deviceControllerPtr, long deviceId, diff --git a/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java new file mode 100644 index 00000000000000..10b1eaaf9c64f6 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegate.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package chip.devicecontroller; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Delegate for ICD check in. + * + *

See detailed in {@link ChipDeviceController#setICDCheckInDelegate(ICDCheckInDelegate)} + */ +public interface ICDCheckInDelegate { + /** + * Callback used to let the application know that a check-in message was received and validated. + * + * @param info ICDClientInfo object representing the state associated with the node that sent the + * check-in message. + */ + void onCheckInComplete(@Nonnull ICDClientInfo info); + + /** + * Callback used to let the application know that a key refresh is needed to avoid counter + * rollover problems. + * + * @param info ICDClientInfo object representing the state associated with the node that sent the + * check-in message. + * @return refreshed key + */ + @Nullable + byte[] onKeyRefreshNeeded(@Nonnull ICDClientInfo info); + + /** + * Callback used to let the application know that the re-registration process is done. + * + * @param errorCode to check for success and failure + */ + void onKeyRefreshDone(long errorCode); +} diff --git a/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java new file mode 100644 index 00000000000000..39ca5df1b9e766 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/ICDCheckInDelegateWrapper.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Project CHIP Authors + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package chip.devicecontroller; + +class ICDCheckInDelegateWrapper { + private ICDCheckInDelegate delegate; + + ICDCheckInDelegateWrapper(ICDCheckInDelegate delegate) { + this.delegate = delegate; + } + + // For JNI call + @SuppressWarnings("unused") + private void onCheckInComplete( + long peerNodeId, + long startCounter, + long offset, + long monitoredSubject, + byte[] icdAesKey, + byte[] icdHmacKey) { + delegate.onCheckInComplete( + new ICDClientInfo( + peerNodeId, startCounter, offset, monitoredSubject, icdAesKey, icdHmacKey)); + } + + @SuppressWarnings("unused") + private byte[] onKeyRefreshNeeded( + long peerNodeId, + long startCounter, + long offset, + long monitoredSubject, + byte[] icdAesKey, + byte[] icdHmacKey) { + return delegate.onKeyRefreshNeeded( + new ICDClientInfo( + peerNodeId, startCounter, offset, monitoredSubject, icdAesKey, icdHmacKey)); + } + + @SuppressWarnings("unused") + private void onKeyRefreshDone(int errorCode) { + delegate.onKeyRefreshDone(errorCode); + } +}