diff --git a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp index 2f051aa70fc644..92427f97744f9f 100644 --- a/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp +++ b/src/app/clusters/operational-credentials-server/operational-credentials-server.cpp @@ -1176,8 +1176,6 @@ bool emberAfOperationalCredentialsClusterAddTrustedRootCertificateCallback( commandObj->FlushAcksRightAwayOnSlowCommand(); // TODO(#17208): Handle checking for byte-to-byte match with existing fabrics before allowing the add - - // TODO(#17208): Validate cert signature prior to setting. err = fabricTable.AddNewPendingTrustedRootCert(rootCertificate); VerifyOrExit(err != CHIP_ERROR_NO_MEMORY, finalStatus = Status::ResourceExhausted); diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp index 7f11430cd2a634..5625a01f0d0860 100644 --- a/src/credentials/CHIPCert.cpp +++ b/src/credentials/CHIPCert.cpp @@ -1145,6 +1145,38 @@ DLL_EXPORT CHIP_ERROR ChipEpochToASN1Time(uint32_t epochTime, chip::ASN1::ASN1Un return CHIP_NO_ERROR; } +CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac) +{ + ChipCertificateSet certSet; + ChipCertificateData certData; + ValidationContext validContext; + uint8_t certType; + + // Note that this function doesn't check RCAC NotBefore / NotAfter time validity. + // It is assumed that RCAC should be valid at the time of installation by definition. + + ReturnErrorOnFailure(certSet.Init(&certData, 1)); + + ReturnErrorOnFailure(certSet.LoadCert(rcac, CertDecodeFlags::kGenerateTBSHash)); + + ReturnErrorOnFailure(certData.mSubjectDN.GetCertType(certType)); + VerifyOrReturnError(certType == kCertType_Root, CHIP_ERROR_WRONG_CERT_TYPE); + + VerifyOrReturnError(certData.mSubjectDN.IsEqual(certData.mIssuerDN), CHIP_ERROR_WRONG_CERT_TYPE); + + VerifyOrReturnError(certData.mSubjectKeyId.data_equal(certData.mAuthKeyId), CHIP_ERROR_WRONG_CERT_TYPE); + + VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kIsCA), CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); + if (certData.mCertFlags.Has(CertFlags::kPathLenConstraintPresent)) + { + VerifyOrReturnError(certData.mPathLenConstraint <= 1, CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); + } + + VerifyOrReturnError(certData.mKeyUsageFlags.Has(KeyUsageFlags::kKeyCertSign), CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); + + return ChipCertificateSet::VerifySignature(&certData, &certData); +} + CHIP_ERROR ConvertIntegerDERToRaw(ByteSpan derInt, uint8_t * rawInt, const uint16_t rawIntLen) { VerifyOrReturnError(!derInt.empty(), CHIP_ERROR_INVALID_ARGUMENT); diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h index 865283f091614e..881c0368e44d88 100644 --- a/src/credentials/CHIPCert.h +++ b/src/credentials/CHIPCert.h @@ -498,6 +498,15 @@ CHIP_ERROR ConvertX509CertToChipCert(const ByteSpan x509Cert, MutableByteSpan & **/ CHIP_ERROR ConvertChipCertToX509Cert(const ByteSpan chipCert, MutableByteSpan & x509Cert); +/** + * Validate CHIP Root CA Certificate (RCAC) in ByteSpan TLV-encoded form. + * This function performs RCAC parcing, checks SubjectDN validity, verifies that SubjectDN + * and IssuerDN are equal, verifies that SKID and AKID are equal, validates certificate signature. + * + * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise + */ +CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac); + struct X509CertRequestParams { int64_t SerialNumber; @@ -708,7 +717,7 @@ CHIP_ERROR ExtractCATsFromOpCert(const ByteSpan & opcert, CATValues & cats); CHIP_ERROR ExtractCATsFromOpCert(const ChipCertificateData & opcert, CATValues & cats); /** - * Extract the and Fabric ID from an operational certificate in ByteSpan TLV-encoded + * Extract Fabric ID from an operational certificate in ByteSpan TLV-encoded * form. This does not perform any sort of validation on the certificate * structure other than parsing it. * diff --git a/src/credentials/FabricTable.cpp b/src/credentials/FabricTable.cpp index f854956f067a64..df920f0bb40bfc 100644 --- a/src/credentials/FabricTable.cpp +++ b/src/credentials/FabricTable.cpp @@ -1530,6 +1530,8 @@ CHIP_ERROR FabricTable::AddNewPendingTrustedRootCert(const ByteSpan & rcac) mStateFlags.HasAny(StateFlags::kIsTrustedRootPending, StateFlags::kIsUpdatePending, StateFlags::kIsAddPending), CHIP_ERROR_INCORRECT_STATE); + ReturnErrorOnFailure(ValidateChipRCAC(rcac)); + EnsureNextAvailableFabricIndexUpdated(); FabricIndex fabricIndexToUse = kUndefinedFabricIndex; diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp index bf9aae14a2394c..afdf04caf166ee 100644 --- a/src/credentials/tests/TestChipCert.cpp +++ b/src/credentials/tests/TestChipCert.cpp @@ -736,6 +736,37 @@ static void TestChipCert_CertValidTime(nlTestSuite * inSuite, void * inContext) certSet.Release(); } +static void TestChipCert_ValidateChipRCAC(nlTestSuite * inSuite, void * inContext) +{ + struct RCACTestCase + { + uint8_t Cert; + CHIP_ERROR mExpectedResult; + }; + + // clang-format off + static RCACTestCase sRCACTestCases[] = { + // Cert Expected Result + // ==================================================== + { TestCert::kRoot01, CHIP_NO_ERROR }, + { TestCert::kRoot02, CHIP_NO_ERROR }, + { TestCert::kICA01, CHIP_ERROR_WRONG_CERT_TYPE }, + { TestCert::kICA02, CHIP_ERROR_WRONG_CERT_TYPE }, + { TestCert::kICA01_1, CHIP_ERROR_WRONG_CERT_TYPE }, + { TestCert::kFWSign01, CHIP_ERROR_WRONG_CERT_TYPE }, + { TestCert::kNode01_01, CHIP_ERROR_WRONG_CERT_TYPE }, + { TestCert::kNode02_08, CHIP_ERROR_WRONG_CERT_TYPE }, + }; + // clang-format on + + for (auto & testCase : sRCACTestCases) + { + ByteSpan cert; + NL_TEST_ASSERT(inSuite, GetTestCert(testCase.Cert, sNullLoadFlag, cert) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, ValidateChipRCAC(cert) == testCase.mExpectedResult); + } +} + class AlwaysAcceptValidityPolicy : public CertificateValidityPolicy { public: @@ -1961,6 +1992,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test CHIP Certificate Distinguish Name", TestChipCert_ChipDN), NL_TEST_DEF("Test CHIP Certificate Validation", TestChipCert_CertValidation), NL_TEST_DEF("Test CHIP Certificate Validation time", TestChipCert_CertValidTime), + NL_TEST_DEF("Test CHIP Root Certificate Validation", TestChipCert_ValidateChipRCAC), NL_TEST_DEF("Test CHIP Certificate Validity Policy injection", TestChipCert_CertValidityPolicyInjection), NL_TEST_DEF("Test CHIP Certificate Usage", TestChipCert_CertUsage), NL_TEST_DEF("Test CHIP Certificate Type", TestChipCert_CertType),