diff --git a/src/controller/AutoCommissioner.cpp b/src/controller/AutoCommissioner.cpp index 0feec835699252..6a3b064feaac83 100644 --- a/src/controller/AutoCommissioner.cpp +++ b/src/controller/AutoCommissioner.cpp @@ -51,6 +51,12 @@ CHIP_ERROR AutoCommissioner::SetCommissioningParameters(const CommissioningParam mParams.SetFailsafeTimerSeconds(params.GetFailsafeTimerSeconds().Value()); } + if (params.GetAdminSubject().HasValue()) + { + ChipLogProgress(Controller, "Setting adminSubject from parameters"); + mParams.SetAdminSubject(params.GetAdminSubject().Value()); + } + if (params.GetThreadOperationalDataset().HasValue()) { ByteSpan dataset = params.GetThreadOperationalDataset().Value(); diff --git a/src/controller/java/AndroidDeviceControllerWrapper.cpp b/src/controller/java/AndroidDeviceControllerWrapper.cpp index 6ccdb94fc2abfe..8b28896a701d44 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.cpp +++ b/src/controller/java/AndroidDeviceControllerWrapper.cpp @@ -175,7 +175,7 @@ AndroidDeviceControllerWrapper * AndroidDeviceControllerWrapper::AllocateNew( initParams.opCertStore = &wrapper->mOpCertStore; // TODO: Init IPK Epoch Key in opcreds issuer, so that commissionees get the right IPK - opCredsIssuer->Initialize(*wrapper.get(), wrapper.get()->mJavaObjectRef); + opCredsIssuer->Initialize(*wrapper.get(), &wrapper->mAutoCommissioner, wrapper.get()->mJavaObjectRef); Platform::ScopedMemoryBuffer noc; if (!noc.Alloc(kMaxCHIPDERCertLength)) @@ -425,7 +425,7 @@ void AndroidDeviceControllerWrapper::OnCommissioningStatusUpdate(PeerId peerId, JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread(); jmethodID onCommissioningStatusUpdateMethod; CHIP_ERROR err = JniReferences::GetInstance().FindMethod(env, mJavaObjectRef, "onCommissioningStatusUpdate", - "(JLJAVA/LANG/STRING;I)V", &onCommissioningStatusUpdateMethod); + "(JLjava/lang/string;I)V", &onCommissioningStatusUpdateMethod); VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(Controller, "Error finding Java method: %" CHIP_ERROR_FORMAT, err.Format())); UtfString jStageCompleted(env, StageToString(stageCompleted)); diff --git a/src/controller/java/AndroidDeviceControllerWrapper.h b/src/controller/java/AndroidDeviceControllerWrapper.h index e5809340fea39c..77f9a6d735cc25 100644 --- a/src/controller/java/AndroidDeviceControllerWrapper.h +++ b/src/controller/java/AndroidDeviceControllerWrapper.h @@ -146,6 +146,11 @@ class AndroidDeviceControllerWrapper : public chip::Controller::DevicePairingDel uint16_t listenPort, uint16_t controllerVendorId, uint16_t failsafeTimerSeconds, bool attemptNetworkScanWiFi, bool attemptNetworkScanThread, CHIP_ERROR * errInfoOnFailure); + chip::Controller::AndroidOperationalCredentialsIssuer * GetAndroidOperationalCredentialsIssuer() + { + return mOpCredsIssuer.get(); + } + private: using ChipDeviceControllerPtr = std::unique_ptr; diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp index 619fc4cb5b8d7b..b82eea4d8b21c9 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp @@ -40,7 +40,8 @@ using namespace Credentials; using namespace Crypto; using namespace TLV; -CHIP_ERROR AndroidOperationalCredentialsIssuer::Initialize(PersistentStorageDelegate & storage, jobject javaObjectRef) +CHIP_ERROR AndroidOperationalCredentialsIssuer::Initialize(PersistentStorageDelegate & storage, AutoCommissioner * autoCommissioner, + jobject javaObjectRef) { using namespace ASN1; ASN1UniversalTime effectiveTime; @@ -70,8 +71,9 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::Initialize(PersistentStorageDele ReturnErrorOnFailure(mIssuer.Deserialize(serializedKey)); } - mStorage = &storage; - mJavaObjectRef = javaObjectRef; + mStorage = &storage; + mAutoCommissioner = autoCommissioner; + mJavaObjectRef = javaObjectRef; mInitialized = true; return CHIP_NO_ERROR; @@ -128,6 +130,103 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, Callback::Callback * onCompletion) +{ + if (mUseJavaCallbackForNOCRequest) + { + return CallbackGenerateNOCChain(csrElements, csrNonce, attestationSignature, attestationChallenge, DAC, PAI, onCompletion); + } + else + { + return LocalGenerateNOCChain(csrElements, csrNonce, attestationSignature, attestationChallenge, DAC, PAI, onCompletion); + } +} + +CHIP_ERROR AndroidOperationalCredentialsIssuer::CallbackGenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, + const ByteSpan & csrSignature, + const ByteSpan & attestationChallenge, + const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion) +{ + jmethodID method; + CHIP_ERROR err = CHIP_NO_ERROR; + err = JniReferences::GetInstance().FindMethod(JniReferences::GetInstance().GetEnvForCurrentThread(), mJavaObjectRef, + "onNOCChainGenerationNeeded", "([B[B[B[B[B[B[B[B[B)V", &method); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Error invoking onNOCChainGenerationNeeded: %" CHIP_ERROR_FORMAT, err.Format()); + return err; + } + + mOnNOCCompletionCallback = onCompletion; + + JniReferences::GetInstance().GetEnvForCurrentThread()->ExceptionClear(); + + jbyteArray javaCsr; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), csrElements.data(), + csrElements.size(), javaCsr); + + jbyteArray javaCsrNonce; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), csrNonce.data(), + csrNonce.size(), javaCsrNonce); + + jbyteArray javaCsrSignature; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), csrSignature.data(), + csrSignature.size(), javaCsrSignature); + + jbyteArray javaAttestationChallenge; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), attestationChallenge.data(), + attestationChallenge.size(), javaAttestationChallenge); + + const ByteSpan & attestationElements = mAutoCommissioner->GetCommissioningParameters().GetAttestationElements().Value(); + jbyteArray javaAttestationElements; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), attestationElements.data(), + attestationElements.size(), javaAttestationElements); + + const ByteSpan & attestationNonce = mAutoCommissioner->GetCommissioningParameters().GetAttestationNonce().Value(); + jbyteArray javaAttestationNonce; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), attestationNonce.data(), + attestationNonce.size(), javaAttestationNonce); + + const ByteSpan & attestationElementsSignature = + mAutoCommissioner->GetCommissioningParameters().GetAttestationSignature().Value(); + jbyteArray javaAttestationElementsSignature; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), + attestationElementsSignature.data(), attestationElementsSignature.size(), + javaAttestationElementsSignature); + + jbyteArray javaDAC; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), DAC.data(), DAC.size(), + javaDAC); + + jbyteArray javaPAI; + JniReferences::GetInstance().N2J_ByteArray(JniReferences::GetInstance().GetEnvForCurrentThread(), PAI.data(), PAI.size(), + javaPAI); + + JniReferences::GetInstance().GetEnvForCurrentThread()->CallVoidMethod( + mJavaObjectRef, method, javaCsr, javaCsrNonce, javaCsrSignature, javaAttestationChallenge, javaAttestationElements, + javaAttestationNonce, javaAttestationElementsSignature, javaDAC, javaPAI); + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidOperationalCredentialsIssuer::NOCChainGenerated(CHIP_ERROR status, const ByteSpan & noc, const ByteSpan & icac, + const ByteSpan & rcac, Optional ipk, + Optional adminSubject) +{ + ReturnErrorCodeIf(mOnNOCCompletionCallback == nullptr, CHIP_ERROR_INCORRECT_STATE); + + Callback::Callback * onCompletion = mOnNOCCompletionCallback; + mOnNOCCompletionCallback = nullptr; + + // Call-back into commissioner with the generated data. + onCompletion->mCall(onCompletion->mContext, status, noc, icac, rcac, ipk, adminSubject); + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidOperationalCredentialsIssuer::LocalGenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, + const ByteSpan & attestationSignature, + const ByteSpan & attestationChallenge, const ByteSpan & DAC, + const ByteSpan & PAI, + Callback::Callback * onCompletion) { jmethodID method; CHIP_ERROR err = CHIP_NO_ERROR; diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.h b/src/controller/java/AndroidOperationalCredentialsIssuer.h index 1fa5f6a6b16c24..49a845710b3e4b 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.h +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.h @@ -29,6 +29,7 @@ #pragma once +#include #include #include #include @@ -51,6 +52,14 @@ class DLL_EXPORT AndroidOperationalCredentialsIssuer : public OperationalCredent const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, Callback::Callback * onCompletion) override; + CHIP_ERROR NOCChainGenerated(CHIP_ERROR status, const ByteSpan & noc, const ByteSpan & icac, const ByteSpan & rcac, + Optional ipk, Optional adminSubject); + + void SetUseJavaCallbackForNOCRequest(bool useJavaCallbackForNOCRequest) + { + mUseJavaCallbackForNOCRequest = useJavaCallbackForNOCRequest; + } + void SetNodeIdForNextNOCRequest(NodeId nodeId) override { mNextRequestedNodeId = nodeId; @@ -69,7 +78,7 @@ class DLL_EXPORT AndroidOperationalCredentialsIssuer : public OperationalCredent * * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise **/ - CHIP_ERROR Initialize(PersistentStorageDelegate & storage, jobject javaObjectRef); + CHIP_ERROR Initialize(PersistentStorageDelegate & storage, AutoCommissioner * autoCommissioner, jobject javaObjectRef); void SetIssuerId(uint32_t id) { mIssuerId = id; } @@ -87,6 +96,15 @@ class DLL_EXPORT AndroidOperationalCredentialsIssuer : public OperationalCredent MutableByteSpan & noc); private: + CHIP_ERROR CallbackGenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, + const ByteSpan & attestationSignature, const ByteSpan & attestationChallenge, + const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion); + + CHIP_ERROR LocalGenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, const ByteSpan & attestationSignature, + const ByteSpan & attestationChallenge, const ByteSpan & DAC, const ByteSpan & PAI, + Callback::Callback * onCompletion); + Crypto::P256Keypair mIssuer; bool mInitialized = false; uint32_t mIssuerId = 0; @@ -97,12 +115,16 @@ class DLL_EXPORT AndroidOperationalCredentialsIssuer : public OperationalCredent NodeId mNextAvailableNodeId = 1; PersistentStorageDelegate * mStorage = nullptr; + AutoCommissioner * mAutoCommissioner = nullptr; NodeId mNextRequestedNodeId = 1; FabricId mNextFabricId = 1; bool mNodeIdRequested = false; jobject mJavaObjectRef = nullptr; + + bool mUseJavaCallbackForNOCRequest = false; + Callback::Callback * mOnNOCCompletionCallback = nullptr; }; } // namespace Controller diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index 3e8db12d8f0794..070c993a3ab674 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -155,6 +155,99 @@ void JNI_OnUnload(JavaVM * jvm, void * reserved) chip::Platform::MemoryShutdown(); } +JNI_METHOD(jint, onNOCChainGeneration) +(JNIEnv * env, jobject self, jlong handle, jobject controllerParams) +{ + chip::DeviceLayer::StackLock lock; + CHIP_ERROR err = CHIP_NO_ERROR; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + + ChipLogProgress(Controller, "setNOCChain() called"); + + jmethodID getRootCertificate; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getRootCertificate", "()[B", &getRootCertificate); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err.AsInteger()); + + jmethodID getIntermediateCertificate; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getIntermediateCertificate", "()[B", + &getIntermediateCertificate); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err.AsInteger()); + + jmethodID getOperationalCertificate; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getOperationalCertificate", "()[B", + &getOperationalCertificate); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err.AsInteger()); + + jmethodID getIpk; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getIpk", "()[B", &getIpk); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err.AsInteger()); + + jmethodID getAdminSubject; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getAdminSubject", "()J", &getAdminSubject); + VerifyOrReturnValue(err == CHIP_NO_ERROR, err.AsInteger()); + + jbyteArray rootCertificate = (jbyteArray) env->CallObjectMethod(controllerParams, getRootCertificate); + VerifyOrReturnValue(rootCertificate != nullptr, CHIP_ERROR_BAD_REQUEST.AsInteger()); + + jbyteArray intermediateCertificate = (jbyteArray) env->CallObjectMethod(controllerParams, getIntermediateCertificate); + VerifyOrReturnValue(intermediateCertificate != nullptr, CHIP_ERROR_BAD_REQUEST.AsInteger()); + + jbyteArray operationalCertificate = (jbyteArray) env->CallObjectMethod(controllerParams, getOperationalCertificate); + VerifyOrReturnValue(operationalCertificate != nullptr, CHIP_ERROR_BAD_REQUEST.AsInteger()); + + // use ipk and adminSubject from CommissioningParameters if not set in ControllerParams + CommissioningParameters commissioningParams = wrapper->GetCommissioningParameters(); + + Optional ipkOptional; + uint8_t ipkValue[CHIP_CRYPTO_SYMMETRIC_KEY_LENGTH_BYTES]; + Crypto::AesCcm128KeySpan ipkTempSpan(ipkValue); + + jbyteArray ipk = (jbyteArray) env->CallObjectMethod(controllerParams, getIpk); + if (ipk != nullptr) + { + JniByteArray jByteArrayIpk(env, ipk); + + VerifyOrReturnValue(jByteArrayIpk.byteSpan().size() == sizeof(ipkValue), CHIP_ERROR_INTERNAL.AsInteger()); + memcpy(&ipkValue[0], jByteArrayIpk.byteSpan().data(), jByteArrayIpk.byteSpan().size()); + + ipkOptional.SetValue(ipkTempSpan); + } + else if (commissioningParams.GetIpk().HasValue()) + { + // if no value pass in ControllerParams, use value from CommissioningParameters + ipkOptional.SetValue(commissioningParams.GetIpk().Value()); + } + + Optional adminSubjectOptional; + uint64_t adminSubject = env->CallLongMethod(controllerParams, getAdminSubject); + if (adminSubject == kUndefinedNodeId) + { + // if no value pass in ControllerParams, use value from CommissioningParameters + adminSubject = commissioningParams.GetAdminSubject().ValueOr(kUndefinedNodeId); + } + if (adminSubject != kUndefinedNodeId) + { + adminSubjectOptional.SetValue(adminSubject); + } + // NOTE: we are allowing adminSubject to not be set since the OnNOCChainGeneration callback makes this field + // optional and includes logic to handle the case where it is not set. It would also make sense to return + // an error here since that use case may not be realistic. + + JniByteArray jByteArrayRcac(env, rootCertificate); + JniByteArray jByteArrayIcac(env, intermediateCertificate); + JniByteArray jByteArrayNoc(env, operationalCertificate); + + err = wrapper->GetAndroidOperationalCredentialsIssuer()->NOCChainGenerated(CHIP_NO_ERROR, jByteArrayNoc.byteSpan(), + jByteArrayIcac.byteSpan(), jByteArrayRcac.byteSpan(), + ipkOptional, adminSubjectOptional); + + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to SetNocChain for the device: %" CHIP_ERROR_FORMAT, err.Format()); + } + return err.AsInteger(); +} + JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject controllerParams) { chip::DeviceLayer::StackLock lock; @@ -211,6 +304,10 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getIpk", "()[B", &getIpk); SuccessOrExit(err); + jmethodID getAdminSubject; + err = chip::JniReferences::GetInstance().FindMethod(env, controllerParams, "getAdminSubject", "()J", &getAdminSubject); + SuccessOrExit(err); + { uint16_t listenPort = env->CallIntMethod(controllerParams, getUdpListenPort); uint16_t controllerVendorId = env->CallIntMethod(controllerParams, getControllerVendorId); @@ -220,8 +317,9 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr jbyteArray operationalCertificate = (jbyteArray) env->CallObjectMethod(controllerParams, getOperationalCertificate); jbyteArray ipk = (jbyteArray) env->CallObjectMethod(controllerParams, getIpk); uint16_t failsafeTimerSeconds = env->CallIntMethod(controllerParams, getFailsafeTimerSeconds); - bool attemptNetworkScanWiFi = env->CallIntMethod(controllerParams, getAttemptNetworkScanWiFi); - bool attemptNetworkScanThread = env->CallIntMethod(controllerParams, getAttemptNetworkScanThread); + bool attemptNetworkScanWiFi = env->CallBooleanMethod(controllerParams, getAttemptNetworkScanWiFi); + bool attemptNetworkScanThread = env->CallBooleanMethod(controllerParams, getAttemptNetworkScanThread); + uint64_t adminSubject = env->CallLongMethod(controllerParams, getAdminSubject); std::unique_ptr opCredsIssuer( new chip::Controller::AndroidOperationalCredentialsIssuer()); @@ -231,6 +329,19 @@ JNI_METHOD(jlong, newDeviceController)(JNIEnv * env, jobject self, jobject contr operationalCertificate, ipk, listenPort, controllerVendorId, failsafeTimerSeconds, attemptNetworkScanWiFi, attemptNetworkScanThread, &err); SuccessOrExit(err); + + if (adminSubject != kUndefinedNodeId) + { + // if there is a valid adminSubject in the ControllerParams, then remember it + CommissioningParameters commissioningParams = wrapper->GetCommissioningParameters(); + commissioningParams.SetAdminSubject(adminSubject); + err = wrapper->UpdateCommissioningParameters(commissioningParams); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "UpdateCommissioningParameters failed. Err = %" CHIP_ERROR_FORMAT, err.Format()); + SuccessOrExit(err); + } + } } // Create and start the IO thread. Must be called after Controller()->Init @@ -422,6 +533,16 @@ JNI_METHOD(void, resumeCommissioning) wrapper->GetAutoCommissioner()->ResumeCommissioning(); } +JNI_METHOD(void, setUseJavaCallbackForNOCRequest) +(JNIEnv * env, jobject self, jlong handle, jboolean useCallback) +{ + ChipLogProgress(Controller, "setUseJavaCallbackForNOCRequest() called"); + chip::DeviceLayer::StackLock lock; + AndroidDeviceControllerWrapper * wrapper = AndroidDeviceControllerWrapper::FromJNIHandle(handle); + + wrapper->GetAndroidOperationalCredentialsIssuer()->SetUseJavaCallbackForNOCRequest(useCallback); +} + JNI_METHOD(void, updateCommissioningNetworkCredentials) (JNIEnv * env, jobject self, jlong handle, jobject networkCredentials) { diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 6b1197ae28cdc5..e617cb39281a49 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -34,6 +34,7 @@ public class ChipDeviceController { private int connectionId; private CompletionListener completionListener; private ScanNetworksListener scanNetworksListener; + private NOCChainIssuer nocChainIssuer; /** * To load class and jni, we need to new AndroidChipPlatform after jni load but before new @@ -61,6 +62,18 @@ public void setScanNetworksListener(ScanNetworksListener listener) { scanNetworksListener = listener; } + /** + * Sets this DeviceController to use the given issuer for issuing operational certs. By default, + * the DeviceController uses an internal, OperationalCredentialsDelegate (see + * AndroidOperationalCredentialsIssuer) + * + * @param issuer + */ + public void setNOCChainIssuer(NOCChainIssuer issuer) { + setUseJavaCallbackForNOCRequest(deviceControllerPtr, issuer != null); + nocChainIssuer = issuer; + } + public void pairDevice( BluetoothGatt bleServer, int connId, @@ -183,6 +196,28 @@ public void resumeCommissioning() { resumeCommissioning(deviceControllerPtr); } + /** + * When a NOCChainIssuer is set for this controller, then onNOCChainGenerationNeeded will be + * called when the NOC CSR needs to be signed. This allows for custom credentials issuer + * implementations, for example, when a proprietary cloud API will perform the CSR signing. + * + *

The commissioning workflow will stop upon the onNOCChainGenerationNeeded callback and resume + * once onNOCChainGeneration is called. + * + *

The following fields on the ControllerParams object MUST be populated: rootCertificate, + * intermediateCertificate, operationalCertificate + * + *

If ipk and adminSubject are set on the ControllerParams object, then they will be used in + * the AddNOC command set to the commissionee. If they are not populated, then the values provided + * in the ChipDeviceController initialization will be used. + * + * @param params + * @return CHIP_ERROR error code (0 is no error) + */ + public int onNOCChainGeneration(ControllerParams params) { + return onNOCChainGeneration(deviceControllerPtr, params); + } + /** * Update the network credentials held by the commissioner for the current commissioning session. * The updated values will be used by the commissioner if the network credentials haven't already @@ -314,6 +349,30 @@ public void onError(Throwable error) { completionListener.onError(error); } + public void onNOCChainGenerationNeeded( + byte[] csrElements, + byte[] csrNonce, + byte[] csrElementsSignature, + byte[] attestationChallenge, + byte[] attestationElements, + byte[] attestationNonce, + byte[] attestationElementsSignature, + byte[] dac, + byte[] pai) { + if (nocChainIssuer != null) { + nocChainIssuer.onNOCChainGenerationNeeded( + csrElements, + csrNonce, + csrElementsSignature, + attestationChallenge, + attestationElements, + attestationNonce, + attestationElementsSignature, + dac, + pai); + } + } + public void close() { releaseBluetoothGatt(connectionId); } @@ -633,9 +692,14 @@ private native boolean openPairingWindowWithPINCallback( private native void resumeCommissioning(long deviceControllerPtr); + private native void setUseJavaCallbackForNOCRequest( + long deviceControllerPtr, boolean useCallback); + private native void updateCommissioningNetworkCredentials( long deviceControllerPtr, NetworkCredentials networkCredentials); + private native int onNOCChainGeneration(long deviceControllerPtr, ControllerParams params); + private native void shutdownSubscriptions(long deviceControllerPtr, long devicePtr); private native void shutdownCommissioning(long deviceControllerPtr); @@ -654,7 +718,44 @@ protected void finalize() throws Throwable { } } - /** Interface to listen for callbacks from CHIPDeviceController. */ + /** Interface to implement custom operational credentials issuer (NOC chain generation). */ + public interface NOCChainIssuer { + /** + * When a NOCChainIssuer is set for this controller, then onNOCChainGenerationNeeded will be + * called when the NOC CSR needs to be signed. This allows for custom credentials issuer + * implementations, for example, when a proprietary cloud API will perform the CSR signing. + * + *

The commissioning workflow will stop upon the onNOCChainGenerationNeeded callback and + * resume once onNOCChainGeneration is called. + * + *

The following fields on the ControllerParams object passed to onNOCChainGeneration MUST be + * populated: rootCertificate, intermediateCertificate, operationalCertificate + * + *

If ipk and adminSubject are set on the ControllerParams object, then they will be used in + * the AddNOC command set to the commissionee. If they are not populated, then the values + * provided in the ChipDeviceController initialization will be used. + * + *

All csr and attestation fields are provided to allow for custom attestestation checks. + */ + void onNOCChainGenerationNeeded( + byte[] csrElements, + byte[] csrNonce, + byte[] csrElementsSignature, + byte[] attestationChallenge, + byte[] attestationElements, + byte[] attestationNonce, + byte[] attestationElementsSignature, + byte[] dac, + byte[] pai); + } + + /** + * Interface to listen for scan networks callbacks from CHIPDeviceController. + * + *

Set the AttemptNetworkScanWiFi or AttemptNetworkScanThread to configure the enable/disable + * WiFi or Thread network scan during commissioning in the the default CommissioningDelegate used + * by the ChipDeviceCommissioner. + */ public interface ScanNetworksListener { /** Notifies when scan networks call fails. */ void onScanNetworksFailure(int errorCode); diff --git a/src/controller/java/src/chip/devicecontroller/ControllerParams.java b/src/controller/java/src/chip/devicecontroller/ControllerParams.java index 87fb85a375adf1..42fd04f42907de 100644 --- a/src/controller/java/src/chip/devicecontroller/ControllerParams.java +++ b/src/controller/java/src/chip/devicecontroller/ControllerParams.java @@ -7,14 +7,15 @@ public final class ControllerParams { private final int udpListenPort; private final int controllerVendorId; + private final int failsafeTimerSeconds; + private final boolean attemptNetworkScanWiFi; + private final boolean attemptNetworkScanThread; @Nullable private final KeypairDelegate keypairDelegate; @Nullable private final byte[] rootCertificate; @Nullable private final byte[] intermediateCertificate; @Nullable private final byte[] operationalCertificate; @Nullable private final byte[] ipk; - private final int failsafeTimerSeconds = 30; - private final boolean attemptNetworkScanWiFi = false; - private final boolean attemptNetworkScanThread = true; + private final long adminSubject; private static final int LEGACY_GLOBAL_CHIP_PORT = 5540; @@ -22,11 +23,15 @@ public final class ControllerParams { private ControllerParams(Builder builder) { this.udpListenPort = builder.udpListenPort; this.controllerVendorId = builder.controllerVendorId; + this.failsafeTimerSeconds = builder.failsafeTimerSeconds; + this.attemptNetworkScanWiFi = builder.attemptNetworkScanWiFi; + this.attemptNetworkScanThread = builder.attemptNetworkScanThread; this.keypairDelegate = builder.keypairDelegate; this.rootCertificate = builder.rootCertificate; this.intermediateCertificate = builder.intermediateCertificate; this.operationalCertificate = builder.operationalCertificate; this.ipk = builder.ipk; + this.adminSubject = builder.adminSubject; } /** Gets the UDP listening port; 0 indicates "any available port" */ @@ -38,6 +43,18 @@ public int getControllerVendorId() { return controllerVendorId; } + public int getFailsafeTimerSeconds() { + return failsafeTimerSeconds; + } + + public boolean getAttemptNetworkScanWiFi() { + return attemptNetworkScanWiFi; + } + + public boolean getAttemptNetworkScanThread() { + return attemptNetworkScanThread; + } + public KeypairDelegate getKeypairDelegate() { return keypairDelegate; } @@ -58,16 +75,8 @@ public byte[] getIpk() { return ipk; } - public int getFailsafeTimerSeconds() { - return failsafeTimerSeconds; - } - - public boolean getAttemptNetworkScanWiFi() { - return attemptNetworkScanWiFi; - } - - public boolean getAttemptNetworkScanThread() { - return attemptNetworkScanThread; + public long getAdminSubject() { + return adminSubject; } /** Returns parameters with ephemerally generated operational credentials */ @@ -92,14 +101,15 @@ public static Builder newBuilder(OperationalKeyConfig operationalKeyConfig) { public static class Builder { private int udpListenPort = LEGACY_GLOBAL_CHIP_PORT + 1; private int controllerVendorId = 0xFFFF; + private int failsafeTimerSeconds = 30; + private boolean attemptNetworkScanWiFi = false; + private boolean attemptNetworkScanThread = false; @Nullable private KeypairDelegate keypairDelegate = null; @Nullable private byte[] rootCertificate = null; @Nullable private byte[] intermediateCertificate = null; @Nullable private byte[] operationalCertificate = null; @Nullable private byte[] ipk = null; - private int failsafeTimerSeconds = 30; - private boolean attemptNetworkScanWiFi = false; - private boolean attemptNetworkScanThread = true; + private long adminSubject = 0; private Builder() {} @@ -116,6 +126,17 @@ public Builder setControllerVendorId(int controllerVendorId) { return this; } + /** + * Sets the FailsafeTimer duration passed to ChipDeviceCommissioner's CommissioningParameters. + * Increasing this value from its default will allow more time for network scans, cloud op cert + * signing calls, and user interaction. + * + *

Note: It is also possible for internal logic (within Autocommissioner, etc) to re-call + * ArmFailSafe to account for network config delays. + * + * @param failsafeTimerSeconds + * @return + */ public Builder setFailsafeTimerSeconds(int failsafeTimerSeconds) { if (failsafeTimerSeconds < 1 || failsafeTimerSeconds > 900) { throw new IllegalArgumentException("failsafeTimerSeconds must be between 0 and 900"); @@ -124,11 +145,37 @@ public Builder setFailsafeTimerSeconds(int failsafeTimerSeconds) { return this; } + /** + * Enable/disable wifi network scan during commissioning in the the default + * CommissioningDelegate used by the ChipDeviceCommissioner. + * + *

Specifically, this sets AttemptWiFiNetworkScan in the CommissioningParameters passed to + * the CommissioningDelegate. + * + *

When a WiFi scan is attempted, the result will be propagated to the ScanNetworksListener + * assigned to the ChipDeviceController. + * + * @param attemptNetworkScanWiFi + * @return + */ public Builder setAttemptNetworkScanWiFi(boolean attemptNetworkScanWiFi) { this.attemptNetworkScanWiFi = attemptNetworkScanWiFi; return this; } + /** + * Enable/disable Thread network scan during commissioning in the the default + * CommissioningDelegate used by the ChipDeviceCommissioner. + * + *

Specifically, this sets AttemptThreadNetworkScan in the CommissioningParameters passed to + * the CommissioningDelegate. + * + *

When a Thread scan is attempted, the result will be propagated to the ScanNetworksListener + * assigned to the ChipDeviceController. + * + * @param attemptNetworkScanWiFi + * @return + */ public Builder setAttemptNetworkScanThread(boolean attemptNetworkScanThread) { this.attemptNetworkScanThread = attemptNetworkScanThread; return this; @@ -159,6 +206,19 @@ public Builder setIpk(byte[] ipk) { return this; } + /** + * Sets the AdminSubject value passed to ChipDeviceCommissioner's CommissioningParameters. This + * value is passed in the AddNoc command sent to the commissionee and represents the subject of + * the default ACL created by that call. + * + * @param adminSubject + * @return + */ + public Builder setAdminSubject(long adminSubject) { + this.adminSubject = adminSubject; + return this; + } + public ControllerParams build() { return new ControllerParams(this); }