diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp index 8d405396a03901..ddbf9052cd3f12 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp @@ -111,27 +111,14 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChainAfterValidation( // If root certificate not found in the storage, generate new root certificate. else { - ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(mIssuerId)); - - ChipLogProgress(Controller, "Generating RCAC"); - chip::Credentials::X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn }; - ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac)); - - VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); + ReturnErrorOnFailure(GenerateRootCertificate(mIssuer, mIssuerId, Optional(), mNow, mNow + mValidity, rcac)); PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size())))); } icac.reduce_size(0); - ChipDN noc_dn; - ReturnErrorOnFailure(noc_dn.AddAttribute_MatterFabricId(fabricId)); - ReturnErrorOnFailure(noc_dn.AddAttribute_MatterNodeId(nodeId)); - ReturnErrorOnFailure(noc_dn.AddCATs(cats)); - - ChipLogProgress(Controller, "Generating NOC"); - chip::Credentials::X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, rcac_dn }; - return NewNodeOperationalX509Cert(noc_request, pubkey, mIssuer, noc); + return GenerateOperationalCertificate(mIssuer, rcac, pubkey, fabricId, nodeId, cats, mNow, mNow + mValidity, noc); } CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, @@ -403,6 +390,75 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::LocalGenerateNOCChain(const Byte return CHIP_NO_ERROR; } +CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateRootCertificate(Crypto::P256Keypair & keypair, uint64_t issuerId, + Optional fabricId, uint32_t validityStart, + uint32_t validityEnd, MutableByteSpan & rcac) +{ + ChipDN rcac_dn; + ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterRCACId(issuerId)); + + if (fabricId.HasValue()) + { + FabricId fabric = fabricId.Value(); + VerifyOrReturnError(IsValidFabricId(fabric), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(rcac_dn.AddAttribute_MatterFabricId(fabric)); + } + + ChipLogProgress(Controller, "Generating RCAC"); + chip::Credentials::X509CertRequestParams rcac_request = { 0, validityStart, validityEnd, rcac_dn, rcac_dn }; + ReturnErrorOnFailure(NewRootX509Cert(rcac_request, keypair, rcac)); + + VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateIntermediateCertificate( + Crypto::P256Keypair & rootKeypair, const ByteSpan & rcac, const Crypto::P256PublicKey & intermediatePublicKey, + uint64_t issuerId, Optional fabricId, uint32_t validityStart, uint32_t validityEnd, MutableByteSpan & icac) +{ + ChipDN rcac_dn; + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn)); + + ChipDN icac_dn; + ReturnErrorOnFailure(icac_dn.AddAttribute_MatterICACId(issuerId)); + + if (fabricId.HasValue()) + { + FabricId fabric = fabricId.Value(); + VerifyOrReturnError(IsValidFabricId(fabric), CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(icac_dn.AddAttribute_MatterFabricId(fabric)); + } + + ChipLogProgress(Controller, "Generating ICAC"); + chip::Credentials::X509CertRequestParams icac_request = { 0, validityStart, validityEnd, icac_dn, rcac_dn }; + ReturnErrorOnFailure(NewICAX509Cert(icac_request, intermediatePublicKey, rootKeypair, icac)); + + VerifyOrReturnError(CanCastTo(icac.size()), CHIP_ERROR_INTERNAL); + + return CHIP_NO_ERROR; +} + +CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateOperationalCertificate(Crypto::P256Keypair & signingKeypair, + const ByteSpan & signingCertificate, + const Crypto::P256PublicKey & operationalPublicKey, + FabricId fabricId, NodeId nodeId, + const chip::CATValues & cats, uint32_t validityStart, + uint32_t validityEnd, MutableByteSpan & noc) +{ + ChipDN rcac_dn; + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(signingCertificate, rcac_dn)); + + ChipDN noc_dn; + ReturnErrorOnFailure(noc_dn.AddAttribute_MatterFabricId(fabricId)); + ReturnErrorOnFailure(noc_dn.AddAttribute_MatterNodeId(nodeId)); + ReturnErrorOnFailure(noc_dn.AddCATs(cats)); + + ChipLogProgress(Controller, "Generating NOC"); + chip::Credentials::X509CertRequestParams noc_request = { 1, validityStart, validityEnd, noc_dn, rcac_dn }; + return NewNodeOperationalX509Cert(noc_request, operationalPublicKey, signingKeypair, noc); +} + CHIP_ERROR N2J_CSRInfo(JNIEnv * env, jbyteArray nonce, jbyteArray elements, jbyteArray csrElementsSignature, jbyteArray csr, jobject & outCSRInfo) { diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.h b/src/controller/java/AndroidOperationalCredentialsIssuer.h index 11ca4a5c49d8df..f297a0bf3b2f57 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.h +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.h @@ -95,6 +95,39 @@ class DLL_EXPORT AndroidOperationalCredentialsIssuer : public OperationalCredent const Crypto::P256PublicKey & pubkey, MutableByteSpan & rcac, MutableByteSpan & icac, MutableByteSpan & noc); + /** + * Create a root (self-signed) X.509 DER encoded certificate that has the + * right fields to be a valid Matter root certificate. + */ + static CHIP_ERROR GenerateRootCertificate(Crypto::P256Keypair & keypair, uint64_t issuerId, Optional fabricId, + uint32_t validityStart, uint32_t validityEnd, MutableByteSpan & rcac); + + /** + * Create an intermediate X.509 DER encoded certificate that has the + * right fields to be a valid Matter intermediate certificate. + */ + static CHIP_ERROR GenerateIntermediateCertificate(Crypto::P256Keypair & rootKeypair, const ByteSpan & rcac, + const Crypto::P256PublicKey & intermediatePublicKey, uint64_t issuerId, + Optional fabricId, uint32_t validityStart, uint32_t validityEnd, + MutableByteSpan & icac); + + /** + * Create an X.509 DER encoded certificate that has the + * right fields to be a valid Matter operational certificate. + * + * signingKeypair and signingCertificate are the root or intermediate that is + * signing the operational certificate. + * + * cats may be null to indicate no CASE Authenticated Tags + * should be used. If cats is not null, it must contain at most + * 3 numbers, which are expected to be 32-bit unsigned Case Authenticated Tag + * values. + */ + static CHIP_ERROR GenerateOperationalCertificate(Crypto::P256Keypair & signingKeypair, const ByteSpan & signingCertificate, + const Crypto::P256PublicKey & operationalPublicKey, FabricId fabricId, + NodeId nodeId, const chip::CATValues & cats, uint32_t validityStart, + uint32_t validityEnd, MutableByteSpan & noc); + private: CHIP_ERROR CallbackGenerateNOCChain(const ByteSpan & csrElements, const ByteSpan & csrNonce, const ByteSpan & attestationSignature, const ByteSpan & attestationChallenge, diff --git a/src/controller/java/CHIPDeviceController-JNI.cpp b/src/controller/java/CHIPDeviceController-JNI.cpp index fd87516e19dd0a..1e6fedc01327d1 100644 --- a/src/controller/java/CHIPDeviceController-JNI.cpp +++ b/src/controller/java/CHIPDeviceController-JNI.cpp @@ -840,6 +840,269 @@ JNI_METHOD(void, updateCommissioningNetworkCredentials) } } +jint GetCalendarFieldID(JNIEnv * env, const char * method) +{ + jclass calendarCls = env->FindClass("java/util/Calendar"); + jfieldID fieldID = env->GetStaticFieldID(calendarCls, method, "I"); + return env->GetStaticIntField(calendarCls, fieldID); +} + +CHIP_ERROR GetEpochTime(JNIEnv * env, jobject calendar, uint32_t & epochTime) +{ + using namespace ASN1; + ASN1UniversalTime universalTime; + + jmethodID getMethod = nullptr; + + jint yearID = GetCalendarFieldID(env, "YEAR"); + jint monthID = GetCalendarFieldID(env, "MONTH"); + jint dayID = GetCalendarFieldID(env, "DAY_OF_MONTH"); + jint hourID = GetCalendarFieldID(env, "HOUR_OF_DAY"); + jint minuteID = GetCalendarFieldID(env, "MINUTE"); + jint secondID = GetCalendarFieldID(env, "SECOND"); + + if (calendar == nullptr) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + ReturnErrorOnFailure(JniReferences::GetInstance().FindMethod(env, calendar, "get", "(I)I", &getMethod)); + + universalTime.Year = static_cast(env->CallIntMethod(calendar, getMethod, yearID)); + // The first month of the year in the Gregorian and Julian calendars is JANUARY which is 0. See detailed in + // https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.html#MONTH + universalTime.Month = static_cast(env->CallIntMethod(calendar, getMethod, monthID)) + 1; + universalTime.Day = static_cast(env->CallIntMethod(calendar, getMethod, dayID)); + universalTime.Hour = static_cast(env->CallIntMethod(calendar, getMethod, hourID)); + universalTime.Minute = static_cast(env->CallIntMethod(calendar, getMethod, minuteID)); + universalTime.Second = static_cast(env->CallIntMethod(calendar, getMethod, secondID)); + + ReturnErrorOnFailure(ASN1ToChipEpochTime(universalTime, epochTime)); + + return CHIP_NO_ERROR; +} + +JNI_METHOD(jbyteArray, createRootCertificate) +(JNIEnv * env, jclass clazz, jobject jKeypair, jlong issuerId, jobject fabricId, jobject validityStart, jobject validityEnd) +{ +#ifdef JAVA_MATTER_CONTROLLER_TEST + return nullptr; +#else + CHIP_ERROR err = CHIP_NO_ERROR; + + uint32_t allocatedCertLength = chip::Credentials::kMaxDERCertLength; + chip::Platform::ScopedMemoryBuffer outBuf; + jbyteArray outRcac = nullptr; + CHIPP256KeypairBridge keypair; + Optional fabric = Optional(); + + VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY); + + keypair.SetDelegate(jKeypair); + err = keypair.Initialize(Crypto::ECPKeyTarget::ECDSA); + SuccessOrExit(err); + + if (fabricId != nullptr) + { + jlong jfabricId = chip::JniReferences::GetInstance().LongToPrimitive(fabricId); + fabric = MakeOptional(static_cast(jfabricId)); + } + + { + MutableByteSpan rcac(outBuf.Get(), allocatedCertLength); + + uint32_t start; + uint32_t end; + + err = GetEpochTime(env, validityStart, start); + SuccessOrExit(err); + + err = GetEpochTime(env, validityEnd, end); + SuccessOrExit(err); + + err = AndroidOperationalCredentialsIssuer::GenerateRootCertificate(keypair, static_cast(issuerId), fabric, start, + end, rcac); + SuccessOrExit(err); + + err = JniReferences::GetInstance().N2J_ByteArray(env, rcac.data(), static_cast(rcac.size()), outRcac); + SuccessOrExit(err); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to create Root Certificate. Err = %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } + + return outRcac; +#endif +} + +JNI_METHOD(jbyteArray, createIntermediateCertificate) +(JNIEnv * env, jclass clazz, jobject rootKeypair, jbyteArray rootCertificate, jbyteArray intermediatePublicKey, jlong issuerId, + jobject fabricId, jobject validityStart, jobject validityEnd) +{ +#ifdef JAVA_MATTER_CONTROLLER_TEST + return nullptr; +#else + CHIP_ERROR err = CHIP_NO_ERROR; + + uint32_t allocatedCertLength = chip::Credentials::kMaxDERCertLength; + chip::Platform::ScopedMemoryBuffer outBuf; + jbyteArray outIcac = nullptr; + CHIPP256KeypairBridge keypair; + Optional fabric = Optional(); + + chip::JniByteArray jniRcac(env, rootCertificate); + chip::JniByteArray jnipublicKey(env, intermediatePublicKey); + + Credentials::P256PublicKeySpan publicKeySpan(reinterpret_cast(jnipublicKey.data())); + Crypto::P256PublicKey publicKey{ publicKeySpan }; + + VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY); + + keypair.SetDelegate(rootKeypair); + err = keypair.Initialize(Crypto::ECPKeyTarget::ECDSA); + SuccessOrExit(err); + + if (fabricId != nullptr) + { + jlong jfabricId = chip::JniReferences::GetInstance().LongToPrimitive(fabricId); + fabric = MakeOptional(static_cast(jfabricId)); + } + + { + MutableByteSpan icac(outBuf.Get(), allocatedCertLength); + + uint32_t start; + uint32_t end; + + err = GetEpochTime(env, validityStart, start); + SuccessOrExit(err); + + err = GetEpochTime(env, validityEnd, end); + SuccessOrExit(err); + + err = AndroidOperationalCredentialsIssuer::GenerateIntermediateCertificate( + keypair, jniRcac.byteSpan(), publicKey, static_cast(issuerId), fabric, start, end, icac); + SuccessOrExit(err); + + ChipLogByteSpan(Controller, icac); + + err = JniReferences::GetInstance().N2J_ByteArray(env, icac.data(), static_cast(icac.size()), outIcac); + SuccessOrExit(err); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to create Intermediate Certificate. Err = %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } + + return outIcac; +#endif +} + +JNI_METHOD(jbyteArray, createOperationalCertificate) +(JNIEnv * env, jclass clazz, jobject signingKeypair, jbyteArray signingCertificate, jbyteArray operationalPublicKey, jlong fabricId, + jlong nodeId, jobject caseAuthenticatedTags, jobject validityStart, jobject validityEnd) +{ +#ifdef JAVA_MATTER_CONTROLLER_TEST + return nullptr; +#else + CHIP_ERROR err = CHIP_NO_ERROR; + + uint32_t allocatedCertLength = chip::Credentials::kMaxDERCertLength; + chip::Platform::ScopedMemoryBuffer outBuf; + jbyteArray outNoc = nullptr; + CHIPP256KeypairBridge keypair; + + chip::JniByteArray jniCert(env, signingCertificate); + chip::JniByteArray jnipublicKey(env, operationalPublicKey); + + Credentials::P256PublicKeySpan publicKeySpan(reinterpret_cast(jnipublicKey.data())); + Crypto::P256PublicKey publicKey{ publicKeySpan }; + + chip::CATValues cats = chip::kUndefinedCATs; + if (caseAuthenticatedTags != nullptr) + { + jint size; + JniReferences::GetInstance().GetListSize(caseAuthenticatedTags, size); + VerifyOrExit(static_cast(size) <= chip::kMaxSubjectCATAttributeCount, err = CHIP_ERROR_INVALID_ARGUMENT); + + for (jint i = 0; i < size; i++) + { + jobject cat = nullptr; + JniReferences::GetInstance().GetListItem(caseAuthenticatedTags, i, cat); + VerifyOrExit(cat != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); + cats.values[i] = static_cast(JniReferences::GetInstance().IntegerToPrimitive(cat)); + } + } + + VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY); + + keypair.SetDelegate(signingKeypair); + err = keypair.Initialize(Crypto::ECPKeyTarget::ECDSA); + SuccessOrExit(err); + { + MutableByteSpan noc(outBuf.Get(), allocatedCertLength); + + uint32_t start; + uint32_t end; + + err = GetEpochTime(env, validityStart, start); + SuccessOrExit(err); + + err = GetEpochTime(env, validityEnd, end); + SuccessOrExit(err); + + err = AndroidOperationalCredentialsIssuer::GenerateOperationalCertificate( + keypair, jniCert.byteSpan(), publicKey, static_cast(fabricId), static_cast(nodeId), cats, start, + end, noc); + SuccessOrExit(err); + + ChipLogByteSpan(Controller, noc); + + err = JniReferences::GetInstance().N2J_ByteArray(env, noc.data(), static_cast(noc.size()), outNoc); + SuccessOrExit(err); + } +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to create Intermediate Certificate. Err = %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } + + return outNoc; +#endif +} + +JNI_METHOD(jbyteArray, publicKeyFromCSR) +(JNIEnv * env, jclass clazz, jbyteArray certificateSigningRequest) +{ + jbyteArray outJbytes = nullptr; + + chip::JniByteArray jniCsr(env, certificateSigningRequest); + P256PublicKey publicKey; + CHIP_ERROR err = VerifyCertificateSigningRequest(jniCsr.byteSpan().data(), jniCsr.byteSpan().size(), publicKey); + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "publicKeyFromCSR: %" CHIP_ERROR_FORMAT, err.Format()); + return nullptr; + } + + err = JniReferences::GetInstance().N2J_ByteArray(env, publicKey.Bytes(), static_cast(publicKey.Length()), outJbytes); + SuccessOrExit(err); +exit: + if (err != CHIP_NO_ERROR) + { + ChipLogError(Controller, "Failed to publicKeyFromCSR. Err = %" CHIP_ERROR_FORMAT, err.Format()); + JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err); + } + + return outJbytes; +} + JNI_METHOD(jbyteArray, convertX509CertToMatterCert) (JNIEnv * env, jobject self, jbyteArray x509Cert) { diff --git a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java index 2551e08a136ef7..916f65fb270819 100644 --- a/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java +++ b/src/controller/java/src/chip/devicecontroller/ChipDeviceController.java @@ -25,6 +25,7 @@ import chip.devicecontroller.model.ChipEventPath; import chip.devicecontroller.model.InvokeElement; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.util.Optional; import javax.annotation.Nullable; @@ -925,6 +926,99 @@ public void invoke( imTimeoutMs); } + /** Create a root (self-signed) X.509 DER encoded certificate */ + public static byte[] createRootCertificate( + KeypairDelegate keypair, long issuerId, @Nullable Long fabricId) { + // current time + Calendar start = Calendar.getInstance(); + Calendar end = Calendar.getInstance(); + // current time + 10 years + end.add(Calendar.YEAR, 10); + return createRootCertificate(keypair, issuerId, fabricId, start, end); + } + + public static native byte[] createRootCertificate( + KeypairDelegate keypair, + long issuerId, + @Nullable Long fabricId, + Calendar validityStart, + Calendar validityEnd); + + /** Create an intermediate X.509 DER encoded certificate */ + public static byte[] createIntermediateCertificate( + KeypairDelegate rootKeypair, + byte[] rootCertificate, + byte[] intermediatePublicKey, + long issuerId, + @Nullable Long fabricId) { + // current time + Calendar start = Calendar.getInstance(); + // current time + 10 years + Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 10); + return createIntermediateCertificate( + rootKeypair, rootCertificate, intermediatePublicKey, issuerId, fabricId, start, end); + } + + public static native byte[] createIntermediateCertificate( + KeypairDelegate rootKeypair, + byte[] rootCertificate, + byte[] intermediatePublicKey, + long issuerId, + @Nullable Long fabricId, + Calendar validityStart, + Calendar validityEnd); + + /** + * Create an X.509 DER encoded certificate that has the right fields to be a valid Matter + * operational certificate. + * + *

signingKeypair and signingCertificate are the root or intermediate that is signing the + * operational certificate. + * + *

caseAuthenticatedTags may be null to indicate no CASE Authenticated Tags should be used. If + * caseAuthenticatedTags is not null, it must contain at most 3 numbers, which are expected to be + * 32-bit unsigned Case Authenticated Tag values. + */ + public static byte[] createOperationalCertificate( + KeypairDelegate signingKeypair, + byte[] signingCertificate, + byte[] operationalPublicKey, + long fabricId, + long nodeId, + List caseAuthenticatedTags) { + // current time + Calendar start = Calendar.getInstance(); + // current time + 10 years + Calendar end = Calendar.getInstance(); + end.add(Calendar.YEAR, 10); + return createOperationalCertificate( + signingKeypair, + signingCertificate, + operationalPublicKey, + fabricId, + nodeId, + caseAuthenticatedTags, + start, + end); + } + + public static native byte[] createOperationalCertificate( + KeypairDelegate signingKeypair, + byte[] signingCertificate, + byte[] operationalPublicKey, + long fabricId, + long nodeId, + List caseAuthenticatedTags, + Calendar validityStart, + Calendar validityEnd); + + /** + * Extract the public key from the given PKCS#10 certificate signing request. This is the public + * key that a certificate issued in response to the request would need to have. + */ + public static native byte[] publicKeyFromCSR(byte[] csr); + /** * Converts a given X.509v3 certificate into a Matter certificate. *