From 256740871e6f062c830009573c0461b9d4db4b1e Mon Sep 17 00:00:00 2001 From: Kevin Coppock <47542933+g-coppock@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:53:39 -0500 Subject: [PATCH] Add support to delegate key storage and signing to the Java layer (#19545) To support the use of the Android KeyStore to securely hold the private keys used for operational control, we cannot pass the raw private key to the SDK. This adds a bridging layer for Java that allows interception at the Java layer of key generation requests as well as ECDSA signing. Added an optional constructor to take in an OperationalKeyConfig, holding the RCAC/ICAC/NOC and a Java KeypairDelegate implementation to forward signing operations. The no-arg constructor will retain the existing behavior for compatibility (generating an ephemeral keypair and NOC chain). Tested internally by delegating to an internal KeyStore. However, the code from internal (currently TE9 tag) has diverged a bit from master. Did a sanity test from CHIPTool which is currently failing on master, but fails the same way with these changes. Could not do an end to end verification with real operational credentials at this base. Co-authored-by: Andrei Litvin --- .../java/AndroidDeviceControllerWrapper.cpp | 99 ++++++--- .../java/AndroidDeviceControllerWrapper.h | 50 ++++- src/controller/java/BUILD.gn | 2 + .../java/CHIPDeviceController-JNI.cpp | 11 +- .../ChipDeviceController.java | 28 ++- .../devicecontroller/KeypairDelegate.java | 52 +++++ .../OperationalKeyConfig.java | 59 ++++++ src/platform/android/BUILD.gn | 2 + .../android/CHIPP256KeypairBridge.cpp | 194 ++++++++++++++++++ src/platform/android/CHIPP256KeypairBridge.h | 76 +++++++ 10 files changed, 542 insertions(+), 31 deletions(-) create mode 100644 src/controller/java/src/chip/devicecontroller/KeypairDelegate.java create mode 100644 src/controller/java/src/chip/devicecontroller/OperationalKeyConfig.java create mode 100644 src/platform/android/CHIPP256KeypairBridge.cpp create mode 100644 src/platform/android/CHIPP256KeypairBridge.h diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index d0fe68ae3f6130..25299318448045 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ #include #include #include +#include using namespace chip; using namespace chip::Controller; @@ -47,6 +49,12 @@ AndroidDeviceControllerWrapper::~AndroidDeviceControllerWrapper() JniReferences::GetInstance().GetEnvForCurrentThread()->DeleteGlobalRef(mJavaObjectRef); } mController->Shutdown(); + + if (mKeypairBridge != nullptr) + { + chip::Platform::Delete(mKeypairBridge); + mKeypairBridge = nullptr; + } } void AndroidDeviceControllerWrapper::SetJavaObjectRef(JavaVM * vm, jobject obj) @@ -61,12 +69,12 @@ void AndroidDeviceControllerWrapper::CallJavaMethod(const char * methodName, jin argument); } -AndroidDeviceControllerWrapper * -AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControllerObj, chip::NodeId nodeId, - const chip::CATValues & cats, chip::System::Layer * systemLayer, - chip::Inet::EndPointManager * tcpEndPointManager, - chip::Inet::EndPointManager * udpEndPointManager, - AndroidOperationalCredentialsIssuerPtr opCredsIssuerPtr, CHIP_ERROR * errInfoOnFailure) +AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( + JavaVM * vm, jobject deviceControllerObj, chip::NodeId nodeId, const chip::CATValues & cats, chip::System::Layer * systemLayer, + chip::Inet::EndPointManager * tcpEndPointManager, + chip::Inet::EndPointManager * udpEndPointManager, AndroidOperationalCredentialsIssuerPtr opCredsIssuerPtr, + jobject keypairDelegate, jbyteArray rootCertificate, jbyteArray intermediateCertificate, jbyteArray nodeOperationalCertificate, + jbyteArray ipkEpochKey, CHIP_ERROR * errInfoOnFailure) { if (errInfoOnFailure == nullptr) { @@ -94,6 +102,14 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle *errInfoOnFailure = CHIP_NO_ERROR; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + if (env == nullptr) + { + ChipLogError(Controller, "Failed to retrieve JNIEnv."); + *errInfoOnFailure = CHIP_ERROR_INCORRECT_STATE; + return nullptr; + } + std::unique_ptr controller(new DeviceCommissioner()); if (!controller) @@ -167,25 +183,53 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle } MutableByteSpan rcacSpan(rcac.Get(), kMaxCHIPDERCertLength); - Crypto::P256Keypair ephemeralKey; - *errInfoOnFailure = ephemeralKey.Initialize(); - if (*errInfoOnFailure != CHIP_NO_ERROR) + if (rootCertificate != nullptr && intermediateCertificate != nullptr && nodeOperationalCertificate != nullptr && + keypairDelegate != nullptr) { - return nullptr; + CHIPP256KeypairBridge * nativeKeypairBridge = wrapper->GetP256KeypairBridge(); + nativeKeypairBridge->SetDelegate(keypairDelegate); + *errInfoOnFailure = nativeKeypairBridge->Initialize(); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + + setupParams.operationalKeypair = nativeKeypairBridge; + setupParams.hasExternallyOwnedOperationalKeypair = true; + + JniByteArray jniRcac(env, rootCertificate); + JniByteArray jniIcac(env, intermediateCertificate); + JniByteArray jniNoc(env, nodeOperationalCertificate); + + setupParams.controllerRCAC = jniRcac.byteSpan(); + setupParams.controllerICAC = jniIcac.byteSpan(); + setupParams.controllerNOC = jniNoc.byteSpan(); } - - *errInfoOnFailure = opCredsIssuer->GenerateNOCChainAfterValidation(nodeId, /* fabricId = */ 1, cats, ephemeralKey.Pubkey(), - rcacSpan, icacSpan, nocSpan); - if (*errInfoOnFailure != CHIP_NO_ERROR) + else { - return nullptr; + Crypto::P256Keypair ephemeralKey; + *errInfoOnFailure = ephemeralKey.Initialize(); + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + setupParams.operationalKeypair = &ephemeralKey; + setupParams.hasExternallyOwnedOperationalKeypair = false; + + *errInfoOnFailure = opCredsIssuer->GenerateNOCChainAfterValidation(nodeId, + /* fabricId = */ 1, cats, ephemeralKey.Pubkey(), + rcacSpan, icacSpan, nocSpan); + + if (*errInfoOnFailure != CHIP_NO_ERROR) + { + return nullptr; + } + + setupParams.controllerRCAC = rcacSpan; + setupParams.controllerICAC = icacSpan; + setupParams.controllerNOC = nocSpan; } - setupParams.operationalKeypair = &ephemeralKey; - setupParams.controllerRCAC = rcacSpan; - setupParams.controllerICAC = icacSpan; - setupParams.controllerNOC = nocSpan; - *errInfoOnFailure = DeviceControllerFactory::GetInstance().Init(initParams); if (*errInfoOnFailure != CHIP_NO_ERROR) { @@ -217,10 +261,19 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle static_cast(fabricInfo->GetFabricIndex())); ChipLogByteSpan(Support, compressedFabricIdSpan); - chip::ByteSpan defaultIpk = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + chip::ByteSpan ipkSpan; + if (ipkEpochKey != nullptr) + { + JniByteArray jniIpk(env, ipkEpochKey); + ipkSpan = jniIpk.byteSpan(); + } + else + { + ipkSpan = chip::GroupTesting::DefaultIpkValue::GetDefaultIpk(); + } - *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey(&wrapper->mGroupDataProvider, fabricInfo->GetFabricIndex(), - defaultIpk, compressedFabricIdSpan); + *errInfoOnFailure = chip::Credentials::SetSingleIpkEpochKey(&wrapper->mGroupDataProvider, fabricInfo->GetFabricIndex(), ipkSpan, + compressedFabricIdSpan); if (*errInfoOnFailure != CHIP_NO_ERROR) { return nullptr; diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index 7600dd68fd1c38..14651e5dfbd891 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "AndroidOperationalCredentialsIssuer.h" @@ -46,6 +47,20 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel jobject JavaObjectRef() { return mJavaObjectRef; } jlong ToJNIHandle(); + /** + * Returns a CHIPP256KeypairBridge which can be used to delegate signing operations + * to a KeypairDelegate in the Java layer. Note that this will always return a pointer + * to the same instance, once initialized. + */ + CHIPP256KeypairBridge * GetP256KeypairBridge() + { + if (mKeypairBridge == nullptr) + { + mKeypairBridge = chip::Platform::New(); + } + return mKeypairBridge; + } + void CallJavaMethod(const char * methodName, jint argument); CHIP_ERROR InitializeOperationalCredentialsIssuer(); @@ -72,12 +87,40 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel using AndroidOperationalCredentialsIssuerPtr = std::unique_ptr; + /** + * Initializes a new CHIPDeviceController using the given parameters, and returns a pointer to the + * AndroidDeviceControllerWrapper that holds the underlying controller. + * + * If the keypairDelegate is provided, then the rootCertificate, nodeOperationalCertificate, and + * ipkEpochKey must also be specified. If no operational credentials are specified here, then an + * ephemeral signing configuration will be generated for you. + * + * If there are any errors during the initialization of this controller, then a nullptr will be + * returned. + * + * @param[in] vm the JavaVM + * @param[in] deviceControllerObj a reference to the Java ChipDeviceController + * @param[in] nodeId the local node ID to use for this controller instance + * @param[in] cats the set of CASE authenticated tags + * @param[in] systemLayer a pointer to the System::Layer instance + * @param[in] tcpEndpointManager a pointer to a Inet::EndPointManager for TCP connections + * @param[in] udpEndpointManager a pointer to a Inet::EndPointManager for UDP connections + * @param[in] opCredsIssuer a pointer to an issuer for Android operational credentials + * @param[in] keypairDelegate a pointer to a Java KeypairDelegate implementation. + * @param[in] rootCertificate an X.509 DER-encoded trusted root certificate for this node + * @param[in] intermediateCertificate an X.509 DER-encoded intermediate certificate for this node + * @param[in] nodeOperationalCertificate an X.509 DER-encoded operational certificate for this node + * @param[in] ipkEpochKey the IPK epoch key to use for this node + * @param[out] errInfoOnFailure a pointer to a CHIP_ERROR that will be populated if this method returns nullptr + */ static AndroidDeviceControllerWrapper * AllocateNew(JavaVM * vm, jobject deviceControllerObj, chip::NodeId nodeId, const chip::CATValues & cats, chip::System::Layer * systemLayer, chip::Inet::EndPointManager * tcpEndPointManager, chip::Inet::EndPointManager * udpEndPointManager, AndroidOperationalCredentialsIssuerPtr opCredsIssuer, - CHIP_ERROR * errInfoOnFailure); + jobject keypairDelegate, jbyteArray rootCertificate, + jbyteArray intermediateCertificate, jbyteArray nodeOperationalCertificate, + jbyteArray ipkEpochKey, CHIP_ERROR * errInfoOnFailure); private: using ChipDeviceControllerPtr = std::unique_ptr; @@ -87,8 +130,9 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel // TODO: This may need to be injected as a GroupDataProvider* chip::Credentials::GroupDataProviderImpl mGroupDataProvider; - JavaVM * mJavaVM = nullptr; - jobject mJavaObjectRef = nullptr; + JavaVM * mJavaVM = nullptr; + jobject mJavaObjectRef = nullptr; + CHIPP256KeypairBridge * mKeypairBridge = nullptr; // These fields allow us to release the string/byte array memory later. jstring ssidStr = nullptr; diff --git a/src/controller/java/BUILD.gn b/src/controller/java/BUILD.gn index ed5a9fee167a16..bb704ce4cc5905 100644 --- a/src/controller/java/BUILD.gn +++ b/src/controller/java/BUILD.gn @@ -89,9 +89,11 @@ android_library("java") { "src/chip/devicecontroller/ChipDeviceControllerException.java", "src/chip/devicecontroller/DiscoveredDevice.java", "src/chip/devicecontroller/GetConnectedDeviceCallbackJni.java", + "src/chip/devicecontroller/KeypairDelegate.java", "src/chip/devicecontroller/NetworkCredentials.java", "src/chip/devicecontroller/NetworkLocation.java", "src/chip/devicecontroller/OpenCommissioningCallback.java", + "src/chip/devicecontroller/OperationalKeyConfig.java", "src/chip/devicecontroller/PaseVerifierParams.java", "src/chip/devicecontroller/ReportCallback.java", "src/chip/devicecontroller/ReportCallbackJni.java", diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index da01467c58b933..50ac6a8c4a35d7 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -153,7 +153,9 @@ void JNI_OnUnload(JavaVM * jvm, void * reserved) chip::Platform::MemoryShutdown(); } -JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self) +JNI_METHOD(jlong, newDeviceController) +(JNIEnv * env, jobject self, jobject keypairDelegate, jbyteArray rootCertificate, jbyteArray intermediateCertificate, + jbyteArray operationalCertificate, jbyteArray ipk) { chip::DeviceLayer::StackLock lock; CHIP_ERROR err = CHIP_NO_ERROR; @@ -163,9 +165,10 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self) ChipLogProgress(Controller, "newDeviceController() called"); std::unique_ptr opCredsIssuer( new chip::Controller::AndroidOperationalCredentialsIssuer()); - wrapper = AndroidDeviceControllerWrapper::AllocateNew(sJVM, self, kLocalDeviceId, chip::kUndefinedCATs, - &DeviceLayer::SystemLayer(), DeviceLayer::TCPEndPointManager(), - DeviceLayer::UDPEndPointManager(), std::move(opCredsIssuer), &err); + wrapper = AndroidDeviceControllerWrapper::AllocateNew( + sJVM, self, kLocalDeviceId, chip::kUndefinedCATs, &DeviceLayer::SystemLayer(), DeviceLayer::TCPEndPointManager(), + DeviceLayer::UDPEndPointManager(), std::move(opCredsIssuer), keypairDelegate, rootCertificate, intermediateCertificate, + operationalCertificate, ipk, &err); SuccessOrExit(err); // Create and start the IO thread. Must be called after Controller()->Init diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 9e89f1d531ad9e..698d95e2ffe52e 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -39,10 +39,27 @@ public static void loadJni() { return; } + /** + * Returns a new {@link ChipDeviceController} with ephemerally generated operational credentials. + */ public ChipDeviceController() { deviceControllerPtr = newDeviceController(); } + /** + * Returns a new {@link ChipDeviceController} which uses the provided {@code operationalKeyConfig} + * as its operating credentials. + */ + public ChipDeviceController(OperationalKeyConfig operationalKeyConfig) { + deviceControllerPtr = + newDeviceController( + operationalKeyConfig.getKeypairDelegate(), + operationalKeyConfig.getTrustedRootCertificate(), + operationalKeyConfig.getIntermediateCertificate(), + operationalKeyConfig.getNodeOperationalCertificate(), + operationalKeyConfig.getIpkEpochKey()); + } + public void setCompletionListener(CompletionListener listener) { completionListener = listener; } @@ -414,7 +431,16 @@ public native void readPath( long devicePtr, List attributePaths); - private native long newDeviceController(); + private long newDeviceController() { + return newDeviceController(null, null, null, null, null); + } + + private native long newDeviceController( + @Nullable KeypairDelegate keypairDelegate, + @Nullable byte[] rootCertificate, + @Nullable byte[] intermediateCertificate, + @Nullable byte[] operationalCertificate, + @Nullable byte[] ipk); private native void pairDevice( long deviceControllerPtr, diff --git a/src/controller/java/src/chip/devicecontroller/KeypairDelegate.java b/src/controller/java/src/chip/devicecontroller/KeypairDelegate.java new file mode 100644 index 00000000000000..499395f76187db --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/KeypairDelegate.java @@ -0,0 +1,52 @@ +package chip.devicecontroller; + +/** Delegate for a P256Keypair for use within the Java environment. */ +public interface KeypairDelegate { + /** + * Ensure that a private key is generated when this method returns. + * + * @throws KeypairException if a private key could not be generated or resolved + */ + void generatePrivateKey() throws KeypairException; + + /** + * Returns an operational PKCS#10 CSR in DER-encoded form, signed by the underlying private key. + * + * @throws KeypairException if the CSR could not be generated + */ + byte[] createCertificateSigningRequest() throws KeypairException; + + /** + * Returns the DER-encoded X.509 public key, generating a new private key if one has not already + * been created. + * + * @throws KeypairException if a private key could not be resolved + */ + byte[] getPublicKey() throws KeypairException; + + /** + * Signs the given message with the private key (generating one if it has not yet been created) + * using ECDSA and returns a DER-encoded signature. + * + * @throws KeypairException if a private key could not be resolved, or the message could not be + * signed + */ + byte[] ecdsaSignMessage(byte[] message) throws KeypairException; + + /** Encompassing exception to encapsulate errors thrown during operations. */ + final class KeypairException extends Exception { + private static final long serialVersionUID = 2646523289554350914L; + + /** Constructs an exception with the specified {@code msg} as the message. */ + public KeypairException(String msg) { + super(msg); + } + /** + * Constructs an exception with the specified {@code msg} as the message and the provided {@code + * cause}. + */ + public KeypairException(String msg, Throwable cause) { + super(msg, cause); + } + } +} diff --git a/src/controller/java/src/chip/devicecontroller/OperationalKeyConfig.java b/src/controller/java/src/chip/devicecontroller/OperationalKeyConfig.java new file mode 100644 index 00000000000000..dc1130c51d8566 --- /dev/null +++ b/src/controller/java/src/chip/devicecontroller/OperationalKeyConfig.java @@ -0,0 +1,59 @@ +package chip.devicecontroller; + +import androidx.annotation.Nullable; + +/** Represents a set of operating credentials for a ChipDeviceController. */ +public final class OperationalKeyConfig { + private final byte[] trustedRootCertificate; + @Nullable private final byte[] intermediateCertificate; + private final byte[] nodeOperationalCertificate; + private final byte[] ipkEpochKey; + private final KeypairDelegate keypairDelegate; + + /** + * @param keypairDelegate a delegate for signing operations + * @param trustedRootCertificate the trusted root X.509 certificate in DER-encoded form + * @param intermediateCertificate the intermediate X.509 certificate in DER-encoded form + * @param nodeOperationalCertificate the node operational X.509 certificate in DER-encoded form + * @param ipkEpochKey the IPK epoch key to use (assuming a single one) + */ + public OperationalKeyConfig( + KeypairDelegate keypairDelegate, + byte[] trustedRootCertificate, + @Nullable byte[] intermediateCertificate, + byte[] nodeOperationalCertificate, + byte[] ipkEpochKey) { + this.keypairDelegate = keypairDelegate; + this.trustedRootCertificate = trustedRootCertificate.clone(); + this.intermediateCertificate = + intermediateCertificate == null ? null : intermediateCertificate.clone(); + this.nodeOperationalCertificate = nodeOperationalCertificate.clone(); + this.ipkEpochKey = ipkEpochKey.clone(); + } + + /** Returns the trusted root X.509 certificate in DER-encoded form. */ + public byte[] getTrustedRootCertificate() { + return trustedRootCertificate.clone(); + } + + /** Returns the intermediate X.509 certificate in DER-encoded form, if provided. */ + @Nullable + public byte[] getIntermediateCertificate() { + return intermediateCertificate == null ? null : intermediateCertificate.clone(); + } + + /** Returns the node operational X.509 certificate in DER-encoded form. */ + public byte[] getNodeOperationalCertificate() { + return nodeOperationalCertificate.clone(); + } + + /** Returns the IPK epoch key. */ + public byte[] getIpkEpochKey() { + return ipkEpochKey.clone(); + } + + /** Returns the delegate for signing operations. */ + public KeypairDelegate getKeypairDelegate() { + return keypairDelegate; + } +} diff --git a/src/platform/android/BUILD.gn b/src/platform/android/BUILD.gn index 5e07ce67fca319..be0da30f597b29 100644 --- a/src/platform/android/BUILD.gn +++ b/src/platform/android/BUILD.gn @@ -36,6 +36,8 @@ static_library("android") { "BLEManagerImpl.h", "BlePlatformConfig.h", "CHIPDevicePlatformEvent.h", + "CHIPP256KeypairBridge.cpp", + "CHIPP256KeypairBridge.h", "CommissionableDataProviderImpl.cpp", "CommissionableDataProviderImpl.h", "ConfigurationManagerImpl.cpp", diff --git a/src/platform/android/CHIPP256KeypairBridge.cpp b/src/platform/android/CHIPP256KeypairBridge.cpp new file mode 100644 index 00000000000000..41ebf572c70adf --- /dev/null +++ b/src/platform/android/CHIPP256KeypairBridge.cpp @@ -0,0 +1,194 @@ +/** + * + * Copyright (c) 2021 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 "platform/android/CHIPP256KeypairBridge.h" +#include "lib/core/CHIPError.h" +#include "lib/support/CHIPJNIError.h" +#include "lib/support/JniReferences.h" +#include "lib/support/JniTypeWrappers.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace chip; +using namespace chip::Crypto; + +CHIPP256KeypairBridge::~CHIPP256KeypairBridge() +{ + VerifyOrReturn(mDelegate != nullptr); + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturn(env != nullptr); + env->DeleteGlobalRef(mDelegate); +} + +CHIP_ERROR CHIPP256KeypairBridge::SetDelegate(jobject delegate) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrExit(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); + + mDelegate = env->NewGlobalRef(delegate); + + err = JniReferences::GetInstance().GetClassRef(env, "chip/devicecontroller/KeypairDelegate", mKeypairDelegateClass); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed to find class for KeypairDelegate.")); + + err = JniReferences::GetInstance().FindMethod(env, delegate, "createCertificateSigningRequest", "()[B", + &mCreateCertificateSigningRequestMethod); + VerifyOrExit(err == CHIP_NO_ERROR, + ChipLogError(Controller, "Failed to find KeypairDelegate.createCertificateSigningRequest() method.")); + + err = JniReferences::GetInstance().FindMethod(env, delegate, "getPublicKey", "()[B", &mGetPublicKeyMethod); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed to find KeypairDelegate.getPublicKey() method.")); + + err = JniReferences::GetInstance().FindMethod(env, delegate, "ecdsaSignMessage", "([B)[B", &mEcdsaSignMessageMethod); + VerifyOrExit(err == CHIP_NO_ERROR, ChipLogError(Controller, "Failed to find KeypairDelegate.ecdsaSignMessage() method.")); + +exit: + return err; +} + +CHIP_ERROR CHIPP256KeypairBridge::Initialize() +{ + if (HasKeypair()) + { + SetPubkey(); + } + return CHIP_NO_ERROR; +} + +CHIP_ERROR CHIPP256KeypairBridge::Serialize(P256SerializedKeypair & output) const +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR CHIPP256KeypairBridge::Deserialize(P256SerializedKeypair & input) +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR CHIPP256KeypairBridge::NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + // Not supported for use from within the CHIP SDK. We provide our own + // implementation that is JVM-specific. + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR CHIPP256KeypairBridge::ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, P256ECDSASignature & out_signature) const +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + CHIP_ERROR err = CHIP_NO_ERROR; + jbyteArray jniMsg; + jobject signedResult = nullptr; + JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnError(env != nullptr, err = CHIP_JNI_ERROR_NO_ENV); + err = JniReferences::GetInstance().N2J_ByteArray(env, msg, msg_length, jniMsg); + VerifyOrReturnError(err == CHIP_NO_ERROR, err); + VerifyOrReturnError(jniMsg != nullptr, err); + + signedResult = env->CallObjectMethod(mDelegate, mEcdsaSignMessageMethod, jniMsg); + + if (env->ExceptionCheck()) + { + ChipLogError(Controller, "Java exception in KeypairDelegate.ecdsaSignMessage()"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return CHIP_JNI_ERROR_EXCEPTION_THROWN; + } + + JniByteArray jniSignature(env, static_cast(signedResult)); + MutableByteSpan signatureSpan(out_signature, out_signature.Capacity()); + ReturnErrorOnFailure(EcdsaAsn1SignatureToRaw(CHIP_CRYPTO_GROUP_SIZE_BYTES, jniSignature.byteSpan(), signatureSpan)); + ReturnErrorOnFailure(out_signature.SetLength(signatureSpan.size())); + + return err; +} + +CHIP_ERROR CHIPP256KeypairBridge::ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, + P256ECDSASignature & out_signature) const +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + // Not required for Java SDK. + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR CHIPP256KeypairBridge::ECDH_derive_secret(const P256PublicKey & remote_public_key, + P256ECDHDerivedSecret & out_secret) const +{ + if (!HasKeypair()) + { + return CHIP_ERROR_INCORRECT_STATE; + } + + // Not required for Java SDK. + return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE; +} + +CHIP_ERROR CHIPP256KeypairBridge::SetPubkey() +{ + jobject publicKey = nullptr; + JNIEnv * env = nullptr; + + VerifyOrReturnError(HasKeypair(), CHIP_ERROR_INCORRECT_STATE); + + env = JniReferences::GetInstance().GetEnvForCurrentThread(); + VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV); + + publicKey = env->CallObjectMethod(mDelegate, mGetPublicKeyMethod); + if (env->ExceptionCheck()) + { + ChipLogError(Controller, "Java exception in KeypairDelegate.getPublicKey()"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return CHIP_JNI_ERROR_EXCEPTION_THROWN; + } + + VerifyOrReturnError(publicKey != nullptr, CHIP_JNI_ERROR_NULL_OBJECT); + JniByteArray jniPublicKey(env, static_cast(publicKey)); + FixedByteSpan publicKeySpan = + FixedByteSpan(reinterpret_cast(jniPublicKey.data())); + mPublicKey = P256PublicKey(publicKeySpan); + return CHIP_NO_ERROR; +} diff --git a/src/platform/android/CHIPP256KeypairBridge.h b/src/platform/android/CHIPP256KeypairBridge.h new file mode 100644 index 00000000000000..63b8fc3ad17a20 --- /dev/null +++ b/src/platform/android/CHIPP256KeypairBridge.h @@ -0,0 +1,76 @@ +/** + * + * Copyright (c) 2021 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. + */ +#pragma once + +#include + +#include "crypto/CHIPCryptoPAL.h" +#include "lib/core/CHIPError.h" +#include "lib/support/logging/CHIPLogging.h" + +/** + * Bridging implementation of P256Keypair to allow delegation of signing and + * key generation to the JVM layer (through the KeypairDelegate Java interface). + * + * This implementation explicitly does not support serialization or + * deserialization and will always return CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE if + * either of them are invoked (as this bridge is not expected to ever have + * access to the raw key bits). + */ +class CHIPP256KeypairBridge : public chip::Crypto::P256Keypair +{ +public: + ~CHIPP256KeypairBridge() override; + + /** + * Sets a reference to the Java implementation of the KeypairDelegate + * interface that will be called whenever this P256Keypair is used. + */ + CHIP_ERROR SetDelegate(jobject delegate); + + bool HasKeypair() const { return mDelegate != nullptr; } + + CHIP_ERROR Initialize() override; + + CHIP_ERROR Serialize(chip::Crypto::P256SerializedKeypair & output) const override; + + CHIP_ERROR Deserialize(chip::Crypto::P256SerializedKeypair & input) override; + + CHIP_ERROR NewCertificateSigningRequest(uint8_t * csr, size_t & csr_length) const override; + + CHIP_ERROR ECDSA_sign_msg(const uint8_t * msg, size_t msg_length, + chip::Crypto::P256ECDSASignature & out_signature) const override; + + CHIP_ERROR ECDSA_sign_hash(const uint8_t * hash, size_t hash_length, + chip::Crypto::P256ECDSASignature & out_signature) const override; + + CHIP_ERROR ECDH_derive_secret(const chip::Crypto::P256PublicKey & remote_public_key, + chip::Crypto::P256ECDHDerivedSecret & out_secret) const override; + + const chip::Crypto::P256PublicKey & Pubkey() const override { return mPublicKey; }; + +private: + jobject mDelegate; + jclass mKeypairDelegateClass; + jmethodID mGetPublicKeyMethod; + jmethodID mCreateCertificateSigningRequestMethod; + jmethodID mEcdsaSignMessageMethod; + + chip::Crypto::P256PublicKey mPublicKey; + + CHIP_ERROR SetPubkey(); +};