From 7e851e03c28120352308170d1f7f6f6a77237f3f Mon Sep 17 00:00:00 2001 From: Ritika Nanda Date: Fri, 25 Jun 2021 23:03:23 -0700 Subject: [PATCH] Support passing csr nonce into commissioning flow (#7696) * Retrieving OpCSR from the Chip Device after passing the CSRNonce. * Retrieving OpCSR from the Chip Device after passing the CSRNonce. Co-authored-by: Ritika Nanda --- .../chiptool/GenericChipDeviceListener.kt | 4 + .../DeviceProvisioningFragment.kt | 4 + src/controller/CHIPDevice.h | 7 +- src/controller/CHIPDeviceController.cpp | 13 ++- .../java/AndroidDeviceControllerWrapper.cpp | 89 +++++++++++++++++-- .../java/AndroidDeviceControllerWrapper.h | 15 ++++ .../java/CHIPDeviceController-JNI.cpp | 27 ++---- src/controller/java/JniReferences.cpp | 15 ++++ src/controller/java/JniReferences.h | 2 + .../ChipDeviceController.java | 9 ++ .../secure_channel/RendezvousParameters.h | 10 +++ 11 files changed, 166 insertions(+), 29 deletions(-) diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt index 02fe0a96d6d3fd..37d8fb4b3612ee 100644 --- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt +++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/GenericChipDeviceListener.kt @@ -38,4 +38,8 @@ open class GenericChipDeviceListener : ChipDeviceController.CompletionListener { override fun onError(error: Throwable?) { // No op } + + override fun onOpCSRGenerationComplete(errorCode: ByteArray) { + // No op + } } diff --git a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt index 7c120e38cf37df..c951b2d834c241 100644 --- a/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt +++ b/src/android/CHIPTool/app/src/main/java/com/google/chip/chiptool/provisioning/DeviceProvisioningFragment.kt @@ -135,6 +135,10 @@ class DeviceProvisioningFragment : Fragment() { } } + override fun onOpCSRGenerationComplete(errorCode: ByteArray) { + Log.d(TAG,String(errorCode)) + } + override fun onPairingDeleted(code: Int) { Log.d(TAG, "onPairingDeleted: $code") } diff --git a/src/controller/CHIPDevice.h b/src/controller/CHIPDevice.h index 7a97c143ab3fe5..99b807a063903e 100644 --- a/src/controller/CHIPDevice.h +++ b/src/controller/CHIPDevice.h @@ -353,7 +353,12 @@ class DLL_EXPORT Device : public Messaging::ExchangeDelegate, public SessionEsta CASESession & GetCASESession() { return mCASESession; } - CHIP_ERROR GenerateCSRNonce() { return Crypto::DRBG_get_bytes(mCSRNonce, sizeof(mCSRNonce)); } + CHIP_ERROR SetCSRNonce(ByteSpan csrNonce) + { + VerifyOrReturnError(csrNonce.size() == sizeof(mCSRNonce), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(mCSRNonce, csrNonce.data(), csrNonce.size()); + return CHIP_NO_ERROR; + } ByteSpan GetCSRNonce() const { return ByteSpan(mCSRNonce, sizeof(mCSRNonce)); } diff --git a/src/controller/CHIPDeviceController.cpp b/src/controller/CHIPDeviceController.cpp index 80d8a0a9b19f21..f0f2968dd03936 100644 --- a/src/controller/CHIPDeviceController.cpp +++ b/src/controller/CHIPDeviceController.cpp @@ -885,6 +885,18 @@ CHIP_ERROR DeviceCommissioner::PairDevice(NodeId remoteDeviceId, RendezvousParam VerifyOrExit(mDeviceBeingPaired < kNumMaxActiveDevices, err = CHIP_ERROR_NO_MEMORY); device = &mActiveDevices[mDeviceBeingPaired]; + // If the CSRNonce is passed in, using that else using a random one.. + if (params.HasCSRNonce()) + { + VerifyOrReturnError(device->SetCSRNonce(params.GetCSRNonce().Value()) == CHIP_NO_ERROR, CHIP_ERROR_INVALID_ARGUMENT); + } + else + { + uint8_t mCSRNonce[kOpCSRNonceLength]; + Crypto::DRBG_get_bytes(mCSRNonce, sizeof(mCSRNonce)); + VerifyOrReturnError(device->SetCSRNonce(ByteSpan(mCSRNonce)) == CHIP_NO_ERROR, CHIP_ERROR_INVALID_ARGUMENT); + } + mIsIPRendezvous = (params.GetPeerAddress().GetTransportType() != Transport::Type::kBle); err = mPairingSession.MessageDispatch().Init(mTransportMgr); @@ -1171,7 +1183,6 @@ CHIP_ERROR DeviceCommissioner::SendOperationalCertificateSigningRequestCommand(D Callback::Cancelable * successCallback = mOpCSRResponseCallback.Cancel(); Callback::Cancelable * failureCallback = mOnCSRFailureCallback.Cancel(); - ReturnErrorOnFailure(device->GenerateCSRNonce()); ReturnErrorOnFailure(cluster.OpCSRRequest(successCallback, failureCallback, device->GetCSRNonce())); ChipLogDetail(Controller, "Sent OpCSR request, waiting for the CSR"); return CHIP_NO_ERROR; diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 9d6e84d684afd1..012cf341e680fe 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -22,6 +22,9 @@ #include #include +#include "JniReferences.h" +#include + #include #include @@ -29,6 +32,7 @@ using namespace chip::Controller; extern chip::Ble::BleLayer * GetJNIBleLayer(); +constexpr const char kOperationalCredentialsIssuerKeypairStorage[] = "AndroidDeviceControllerKey"; AndroidDeviceControllerWrapper::~AndroidDeviceControllerWrapper() { if ((mJavaVM != nullptr) && (mJavaObjectRef != nullptr)) @@ -50,6 +54,15 @@ void AndroidDeviceControllerWrapper::CallJavaMethod(const char * methodName, jin argument); } +CHIP_ERROR AndroidDeviceControllerWrapper::GetRootCACertificate(chip::FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, + uint32_t & outCertLen) +{ + Initialize(); + VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE); + chip::X509CertRequestParams newCertParams = { 0, mIssuerId, mNow, mNow + mValidity, true, fabricId, false, 0 }; + return NewRootX509Cert(newCertParams, mIssuer, certBuf, certBufSize, outCertLen); +} + AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew(JavaVM * vm, jobject deviceControllerObj, pthread_mutex_t * stackLock, chip::NodeId nodeId, chip::System::Layer * systemLayer, @@ -90,11 +103,12 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew(Jav chip::Controller::CommissionerInitParams initParams; - initParams.storageDelegate = wrapper.get(); - initParams.pairingDelegate = wrapper.get(); - initParams.systemLayer = systemLayer; - initParams.inetLayer = inetLayer; - initParams.bleLayer = GetJNIBleLayer(); + initParams.storageDelegate = wrapper.get(); + initParams.pairingDelegate = wrapper.get(); + initParams.operationalCredentialsDelegate = wrapper.get(); + initParams.systemLayer = systemLayer; + initParams.inetLayer = inetLayer; + initParams.bleLayer = GetJNIBleLayer(); *errInfoOnFailure = wrapper->OpCredsIssuer().Initialize(*initParams.storageDelegate); if (*errInfoOnFailure != CHIP_NO_ERROR) @@ -102,8 +116,6 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew(Jav return nullptr; } - initParams.operationalCredentialsDelegate = &wrapper->OpCredsIssuer(); - *errInfoOnFailure = wrapper->Controller()->Init(nodeId, initParams); if (*errInfoOnFailure != CHIP_NO_ERROR) @@ -139,6 +151,69 @@ void AndroidDeviceControllerWrapper::OnPairingDeleted(CHIP_ERROR error) CallJavaMethod("onPairingDeleted", static_cast(error)); } +// TODO Refactor this API to match latest spec, so that GenerateNodeOperationalCertificate receives the full CSR Elements data +// payload. +CHIP_ERROR AndroidDeviceControllerWrapper::GenerateNodeOperationalCertificate(const chip::PeerId & peerId, + const chip::ByteSpan & csr, int64_t serialNumber, + uint8_t * certBuf, uint32_t certBufSize, + uint32_t & outCertLen) +{ + jmethodID method; + CHIP_ERROR err = CHIP_NO_ERROR; + err = JniReferences::GetInstance().FindMethod(JniReferences::GetInstance().GetEnvForCurrentThread(), mJavaObjectRef, + "onOpCSRGenerationComplete", "([B)V", &method); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Error invoking onOpCSRGenerationComplete: %d", err); + return err; + } + + // Initializing the KeyPair. + Initialize(); + + chip::X509CertRequestParams request = { serialNumber, mIssuerId, mNow, mNow + mValidity, true, peerId.GetFabricId(), + true, peerId.GetNodeId() }; + + chip::P256PublicKey pubkey; + ReturnErrorOnFailure(VerifyCertificateSigningRequest(csr.data(), csr.size(), pubkey)); + + ChipLogProgress(chipTool, "VerifyCertificateSigningRequest"); + + CHIP_ERROR generateCert = NewNodeOperationalX509Cert(request, chip::CertificateIssuerLevel::kIssuerIsRootCA, pubkey, mIssuer, + certBuf, certBufSize, outCertLen); + jbyteArray argument; + JniReferences::GetInstance().GetEnvForCurrentThread()->ExceptionClear(); + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), csr.data(), csr.size(), + argument); + JniReferences::GetInstance().GetEnvForCurrentThread()->CallVoidMethod(mJavaObjectRef, method, argument); + return generateCert; +} + +CHIP_ERROR AndroidDeviceControllerWrapper::Initialize() +{ + chip::Crypto::P256SerializedKeypair serializedKey; + uint16_t keySize = static_cast(sizeof(serializedKey)); + + // TODO: Use Android keystore system instead of direct storage of private key and add specific errors to check if a specified + // item is not found in the keystore. + if (SyncGetKeyValue(kOperationalCredentialsIssuerKeypairStorage, &serializedKey, keySize) != CHIP_NO_ERROR) + { + // If storage doesn't have an existing keypair, create one and add it to the storage. + ReturnErrorOnFailure(mIssuer.Initialize()); + ReturnErrorOnFailure(mIssuer.Serialize(serializedKey)); + keySize = static_cast(sizeof(serializedKey)); + SyncSetKeyValue(kOperationalCredentialsIssuerKeypairStorage, &serializedKey, keySize); + } + else + { + // Use the keypair from the storage + ReturnErrorOnFailure(mIssuer.Deserialize(serializedKey)); + } + + mInitialized = true; + return CHIP_NO_ERROR; +} + void AndroidDeviceControllerWrapper::OnMessage(chip::System::PacketBufferHandle && msg) {} void AndroidDeviceControllerWrapper::OnStatusChange(void) {} diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index 12517b19885f01..9a91c6ea1d7f6f 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -26,6 +26,7 @@ #include #include #include +#include /** * This class contains all relevant information for the JNI view of CHIPDeviceController @@ -35,6 +36,7 @@ */ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDelegate, public chip::Controller::DeviceStatusDelegate, + public chip::Controller::OperationalCredentialsDelegate, public chip::PersistentStorageDelegate { public: @@ -47,12 +49,20 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel jlong ToJNIHandle(); void CallJavaMethod(const char * methodName, jint argument); + CHIP_ERROR Initialize(); // DevicePairingDelegate implementation void OnStatusUpdate(chip::Controller::DevicePairingDelegate::Status status) override; void OnPairingComplete(CHIP_ERROR error) override; void OnPairingDeleted(CHIP_ERROR error) override; + // OperationalCredentialsDelegate implementation + CHIP_ERROR GenerateNodeOperationalCertificate(const chip::PeerId & peerId, const chip::ByteSpan & csr, int64_t serialNumber, + uint8_t * certBuf, uint32_t certBufSize, uint32_t & outCertLen) override; + + CHIP_ERROR GetRootCACertificate(chip::FabricId fabricId, uint8_t * certBuf, uint32_t certBufSize, + uint32_t & outCertLen) override; + // DeviceStatusDelegate implementation void OnMessage(chip::System::PacketBufferHandle && msg) override; void OnStatusChange(void) override; @@ -73,6 +83,11 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel private: using ChipDeviceControllerPtr = std::unique_ptr; + chip::Crypto::P256Keypair mIssuer; + bool mInitialized = false; + uint32_t mIssuerId = 0; + uint32_t mNow = chip::CalendarToChipEpochTime(2021, 06, 10, 0, 0, 0, mNow); + uint32_t mValidity = 10 * chip::kSecondsPerStandardYear; ChipDeviceControllerPtr mController; chip::Controller::ExampleOperationalCredentialsIssuer mOpCredsIssuer; diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 52a677a2a8192b..3b4c4b204782a9 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -80,7 +80,6 @@ static void HandleNewConnection(void * appState, const uint16_t discriminator); static void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow); static void ReportError(JNIEnv * env, CHIP_ERROR cbErr, const char * cbName); static void * IOThreadMain(void * arg); -static CHIP_ERROR N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, uint32_t inArrayLen, jbyteArray & outArray); static CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx); namespace { @@ -782,13 +781,13 @@ bool HandleSendCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t * svc ChipLogProgress(Controller, "Received SendCharacteristic"); VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); - err = N2J_ByteArray(env, svcId, 16, svcIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); SuccessOrExit(err); - err = N2J_ByteArray(env, charId, 16, charIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); SuccessOrExit(err); - err = N2J_ByteArray(env, characteristicData, characteristicDataLen, characteristicDataObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, characteristicData, characteristicDataLen, characteristicDataObj); SuccessOrExit(err); method = env->GetStaticMethodID(sAndroidChipStackCls, "onSendCharacteristic", "(I[B[B[B)Z"); @@ -829,10 +828,10 @@ bool HandleSubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_t ChipLogProgress(Controller, "Received SubscribeCharacteristic"); VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); - err = N2J_ByteArray(env, svcId, 16, svcIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); SuccessOrExit(err); - err = N2J_ByteArray(env, charId, 16, charIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); SuccessOrExit(err); { @@ -872,10 +871,10 @@ bool HandleUnsubscribeCharacteristic(BLE_CONNECTION_OBJECT connObj, const uint8_ ChipLogProgress(Controller, "Received UnsubscribeCharacteristic"); VerifyOrExit(env != NULL, err = CHIP_JNI_ERROR_NO_ENV); - err = N2J_ByteArray(env, svcId, 16, svcIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, svcId, 16, svcIdObj); SuccessOrExit(err); - err = N2J_ByteArray(env, charId, 16, charIdObj); + err = JniReferences::GetInstance().N2J_ByteArray(env, charId, 16, charIdObj); SuccessOrExit(err); method = env->GetStaticMethodID(sAndroidChipStackCls, "onUnsubscribeCharacteristic", "(I[B[B)Z"); @@ -1102,18 +1101,6 @@ void ThrowError(JNIEnv * env, CHIP_ERROR errToThrow) } } -CHIP_ERROR N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, uint32_t inArrayLen, jbyteArray & outArray) -{ - outArray = env->NewByteArray((int) inArrayLen); - VerifyOrReturnError(outArray != NULL, CHIP_ERROR_NO_MEMORY); - - env->ExceptionClear(); - env->SetByteArrayRegion(outArray, 0, inArrayLen, (jbyte *) inArray); - VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN); - - return CHIP_NO_ERROR; -} - CHIP_ERROR N2J_Error(JNIEnv * env, CHIP_ERROR inErr, jthrowable & outEx) { CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/controller/java/JniReferences.cpp b/src/controller/java/JniReferences.cpp index e2e3bade657b2f..cf5b56e6619e9c 100644 --- a/src/controller/java/JniReferences.cpp +++ b/src/controller/java/JniReferences.cpp @@ -95,6 +95,21 @@ CHIP_ERROR JniReferences::GetClassRef(JNIEnv * env, const char * clsType, jclass return err; } +CHIP_ERROR JniReferences::N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, uint32_t inArrayLen, jbyteArray & outArray) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + outArray = env->NewByteArray((int) inArrayLen); + VerifyOrReturnError(outArray != NULL, CHIP_ERROR_NO_MEMORY); + + env->ExceptionClear(); + env->SetByteArrayRegion(outArray, 0, inArrayLen, (jbyte *) inArray); + VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN); + +exit: + return err; +} + CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char * methodName, const char * methodSignature, jmethodID * methodId) { diff --git a/src/controller/java/JniReferences.h b/src/controller/java/JniReferences.h index 91b2d70c5463ba..0d57c6911a7960 100644 --- a/src/controller/java/JniReferences.h +++ b/src/controller/java/JniReferences.h @@ -73,6 +73,8 @@ class JniReferences jmethodID * methodId); void CallVoidInt(JNIEnv * env, jobject object, const char * methodName, jint argument); + CHIP_ERROR N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, uint32_t inArrayLen, jbyteArray & outArray); + private: JniReferences() {} diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 28e423f3984a2c..defe6d3f743755 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -106,6 +106,12 @@ public void onPairingComplete(int errorCode) { } } + public void onOpCSRGenerationComplete(byte[] errorCode) { + if (completionListener != null) { + completionListener.onOpCSRGenerationComplete(errorCode); + } + } + public void onPairingDeleted(int errorCode) { if (completionListener != null) { completionListener.onPairingDeleted(errorCode); @@ -265,5 +271,8 @@ public interface CompletionListener { /** Notifies the listener of the error. */ void onError(Throwable error); + + /** Notifies the Commissioner when the OpCSR for the Comissionee is generated. */ + void onOpCSRGenerationComplete(byte[] errorCode); } } diff --git a/src/protocols/secure_channel/RendezvousParameters.h b/src/protocols/secure_channel/RendezvousParameters.h index ce6407dec854d5..fda1299c2be37c 100644 --- a/src/protocols/secure_channel/RendezvousParameters.h +++ b/src/protocols/secure_channel/RendezvousParameters.h @@ -63,12 +63,20 @@ class RendezvousParameters bool HasPeerAddress() const { return mPeerAddress.IsInitialized(); } Transport::PeerAddress GetPeerAddress() const { return mPeerAddress; } + const Optional GetCSRNonce() const { return mCSRNonce; } RendezvousParameters & SetPeerAddress(const Transport::PeerAddress & peerAddress) { mPeerAddress = peerAddress; return *this; } + // The lifetime of the buffer csrNonce is pointing to, should exceed the lifetime of RendezvousParameter object. + RendezvousParameters & SetCSRNonce(ByteSpan csrNonce) + { + mCSRNonce.SetValue(csrNonce); + return *this; + } + bool HasDiscriminator() const { return mDiscriminator <= kMaxRendezvousDiscriminatorValue; } uint16_t GetDiscriminator() const { return mDiscriminator; } RendezvousParameters & SetDiscriminator(uint16_t discriminator) @@ -94,6 +102,7 @@ class RendezvousParameters } bool HasPASEVerifier() const { return mHasPASEVerifier; } + bool HasCSRNonce() const { return mCSRNonce.HasValue(); } const PASEVerifier & GetPASEVerifier() const { return mPASEVerifier; } RendezvousParameters & SetPASEVerifier(PASEVerifier & verifier) { @@ -138,6 +147,7 @@ class RendezvousParameters Optional mRemoteNodeId; ///< the remote node id uint32_t mSetupPINCode = 0; ///< the target peripheral setup PIN Code uint16_t mDiscriminator = UINT16_MAX; ///< the target peripheral discriminator + Optional mCSRNonce; ///< CSR Nonce passed by the commissioner PASEVerifier mPASEVerifier; bool mHasPASEVerifier = false;