Skip to content

Commit

Permalink
Add support to delegate key storage and signing to the Java layer (#1…
Browse files Browse the repository at this point in the history
…9545)

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 <[email protected]>
  • Loading branch information
2 people authored and pull[bot] committed Jul 12, 2022
1 parent 3eae6ab commit 2567408
Show file tree
Hide file tree
Showing 10 changed files with 542 additions and 31 deletions.
99 changes: 76 additions & 23 deletions src/controller/java/AndroidDeviceControllerWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <lib/support/CodeUtils.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>

#include <controller/CHIPDeviceControllerFactory.h>
#include <credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h>
Expand All @@ -34,6 +35,7 @@
#include <lib/support/TestGroupData.h>
#include <lib/support/ThreadOperationalDataset.h>
#include <platform/KeyValueStoreManager.h>
#include <platform/android/CHIPP256KeypairBridge.h>

using namespace chip;
using namespace chip::Controller;
Expand All @@ -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)
Expand All @@ -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<Inet::TCPEndPoint> * tcpEndPointManager,
chip::Inet::EndPointManager<Inet::UDPEndPoint> * 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<Inet::TCPEndPoint> * tcpEndPointManager,
chip::Inet::EndPointManager<Inet::UDPEndPoint> * udpEndPointManager, AndroidOperationalCredentialsIssuerPtr opCredsIssuerPtr,
jobject keypairDelegate, jbyteArray rootCertificate, jbyteArray intermediateCertificate, jbyteArray nodeOperationalCertificate,
jbyteArray ipkEpochKey, CHIP_ERROR * errInfoOnFailure)
{
if (errInfoOnFailure == nullptr)
{
Expand Down Expand Up @@ -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<DeviceCommissioner> controller(new DeviceCommissioner());

if (!controller)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -217,10 +261,19 @@ AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControlle
static_cast<unsigned>(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;
Expand Down
50 changes: 47 additions & 3 deletions src/controller/java/AndroidDeviceControllerWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <controller/CHIPDeviceController.h>
#include <credentials/GroupDataProviderImpl.h>
#include <lib/support/TimeUtils.h>
#include <platform/android/CHIPP256KeypairBridge.h>
#include <platform/internal/DeviceNetworkInfo.h>

#include "AndroidOperationalCredentialsIssuer.h"
Expand All @@ -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<CHIPP256KeypairBridge>();
}
return mKeypairBridge;
}

void CallJavaMethod(const char * methodName, jint argument);
CHIP_ERROR InitializeOperationalCredentialsIssuer();

Expand All @@ -72,12 +87,40 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel

using AndroidOperationalCredentialsIssuerPtr = std::unique_ptr<chip::Controller::AndroidOperationalCredentialsIssuer>;

/**
* 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<chip::Inet::TCPEndPoint> * tcpEndPointManager,
chip::Inet::EndPointManager<chip::Inet::UDPEndPoint> * 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<chip::Controller::DeviceCommissioner>;
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions src/controller/java/CHIPDeviceController-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -163,9 +165,10 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self)
ChipLogProgress(Controller, "newDeviceController() called");
std::unique_ptr<chip::Controller::AndroidOperationalCredentialsIssuer> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -414,7 +431,16 @@ public native void readPath(
long devicePtr,
List<ChipAttributePath> 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,
Expand Down
52 changes: 52 additions & 0 deletions src/controller/java/src/chip/devicecontroller/KeypairDelegate.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 2567408

Please sign in to comment.