From 2305896cbc695a17d59feb8e70506da8a0e06115 Mon Sep 17 00:00:00 2001 From: Joseph Kelly <41064086+jpk233@users.noreply.github.com> Date: Wed, 2 Jun 2021 23:38:33 -0400 Subject: [PATCH] CHIP credential serialization (#6400) * CHIP certificate value changes * Addressed review comments, removed TODOs and ExtractPubKey method * Added 2 new tests: - Test OperationalCredentialSet Serialization - Test new method for retrieving Certificate CHIP ID * Fix CI builds * Update with bzbarsky-apple's suggestions, document lifetime assumption * Additional documentation, moved serializable objects to .data in order to avoid stack limitations * Cleanup old code remnants, size check for optional CA certificate * Sync with master, CHIPCert updates Co-authored-by: Boris Itkis --- src/credentials/CHIPCert.cpp | 32 +++++++- src/credentials/CHIPCert.h | 14 +++- .../CHIPOperationalCredentials.cpp | 81 +++++++++++++++++++ src/credentials/CHIPOperationalCredentials.h | 32 +++++++- src/credentials/tests/TestChipCert.cpp | 47 +++++++++++ .../tests/TestChipOperationalCredentials.cpp | 68 ++++++++++++++++ 6 files changed, 270 insertions(+), 4 deletions(-) diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp index 325a2aab3d585d..3426b510467a5b 100644 --- a/src/credentials/CHIPCert.cpp +++ b/src/credentials/CHIPCert.cpp @@ -156,13 +156,13 @@ CHIP_ERROR ChipCertificateSet::LoadCert(const uint8_t * chipCert, uint32_t chipC err = reader.Next(kTLVType_Structure, ProfileTag(Protocols::OpCredentials::Id.ToTLVProfileId(), kTag_ChipCertificate)); SuccessOrExit(err); - err = LoadCert(reader, decodeFlags); + err = LoadCert(reader, decodeFlags, ByteSpan(chipCert, chipCertLen)); exit: return err; } -CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags decodeFlags) +CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags decodeFlags, ByteSpan chipCert) { ASN1Writer writer; // ASN1Writer is used to encode TBS portion of the certificate for the purpose of signature // validation, which should be performed on the TBS data encoded in ASN.1 DER form. @@ -172,6 +172,8 @@ CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags decodeFlags); + CHIP_ERROR LoadCert(chip::TLV::TLVReader & reader, BitFlags decodeFlags, ByteSpan chipCert = ByteSpan()); /** * @brief Load CHIP certificates into set. diff --git a/src/credentials/CHIPOperationalCredentials.cpp b/src/credentials/CHIPOperationalCredentials.cpp index 9154c6cac8fbc8..e19ba10db0f229 100644 --- a/src/credentials/CHIPOperationalCredentials.cpp +++ b/src/credentials/CHIPOperationalCredentials.cpp @@ -30,10 +30,14 @@ #include #include #include +#include namespace chip { namespace Credentials { +static constexpr size_t kOperationalCertificatesMax = 3; +static constexpr size_t kOperationalCertificateDecodeBufSize = 1024; + using namespace chip::Crypto; CHIP_ERROR OperationalCredentialSet::Init(uint8_t maxCertsArraySize) @@ -266,6 +270,83 @@ CHIP_ERROR OperationalCredentialSet::SetDevOpCredKeypair(const CertificateKeyId return CHIP_NO_ERROR; } +CHIP_ERROR OperationalCredentialSet::ToSerializable(const CertificateKeyId & trustedRootId, + OperationalCredentialSerializable & serializable) +{ + const NodeCredential * nodeCredential = GetNodeCredentialAt(trustedRootId); + P256Keypair * keypair = GetNodeKeypairAt(trustedRootId); + P256SerializedKeypair serializedKeypair; + ChipCertificateSet * certificateSet = FindCertSet(trustedRootId); + const ChipCertificateData * dataSet = nullptr; + uint8_t * ptrSerializableCerts[] = { serializable.mRootCertificate, serializable.mCACertificate }; + uint16_t * ptrSerializableCertsLen[] = { &serializable.mRootCertificateLen, &serializable.mCACertificateLen }; + + VerifyOrReturnError(certificateSet != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorOnFailure(keypair->Serialize(serializedKeypair)); + VerifyOrReturnError(serializedKeypair.Length() <= sizeof(serializable.mNodeKeypair), CHIP_ERROR_INVALID_ARGUMENT); + + dataSet = certificateSet->GetCertSet(); + VerifyOrReturnError(dataSet != nullptr, CHIP_ERROR_INVALID_ARGUMENT); + + memset(&serializable, 0, sizeof(serializable)); + serializable.mNodeCredentialLen = nodeCredential->mLen; + + memcpy(serializable.mNodeCredential, nodeCredential->mCredential, nodeCredential->mLen); + memcpy(serializable.mNodeKeypair, serializedKeypair, serializedKeypair.Length()); + serializable.mNodeKeypairLen = static_cast(serializedKeypair.Length()); + + for (uint8_t i = 0; i < certificateSet->GetCertCount(); ++i) + { + VerifyOrReturnError(CanCastTo(dataSet[i].mCertificate.size()), CHIP_ERROR_INTERNAL); + memcpy(ptrSerializableCerts[i], dataSet[i].mCertificate.data(), dataSet[i].mCertificate.size()); + *ptrSerializableCertsLen[i] = static_cast(dataSet[i].mCertificate.size()); + } + + return CHIP_NO_ERROR; +} + +CHIP_ERROR OperationalCredentialSet::FromSerializable(const OperationalCredentialSerializable & serializable) +{ + CHIP_ERROR err = CHIP_NO_ERROR; + + P256Keypair keypair; + P256SerializedKeypair serializedKeypair; + ChipCertificateSet certificateSet; + CertificateKeyId trustedRootId; + + SuccessOrExit(err = certificateSet.Init(kOperationalCertificatesMax, kOperationalCertificateDecodeBufSize)); + + err = certificateSet.LoadCert(serializable.mRootCertificate, serializable.mRootCertificateLen, + BitFlags(CertDecodeFlags::kIsTrustAnchor)); + SuccessOrExit(err); + + trustedRootId.mId = certificateSet.GetLastCert()->mAuthKeyId.mId; + trustedRootId.mLen = certificateSet.GetLastCert()->mAuthKeyId.mLen; + + if (serializable.mCACertificateLen != 0) + { + err = certificateSet.LoadCert(serializable.mCACertificate, serializable.mCACertificateLen, + BitFlags(CertDecodeFlags::kGenerateTBSHash)); + SuccessOrExit(err); + } + + LoadCertSet(&certificateSet); + + memcpy(serializedKeypair, serializable.mNodeKeypair, serializable.mNodeKeypairLen); + SuccessOrExit(err = serializedKeypair.SetLength(serializable.mNodeKeypairLen)); + + SuccessOrExit(err = keypair.Deserialize(serializedKeypair)); + + SuccessOrExit(err = SetDevOpCredKeypair(trustedRootId, &keypair)); + + SuccessOrExit(err = SetDevOpCred(trustedRootId, serializable.mNodeCredential, serializable.mNodeCredentialLen)); + +exit: + certificateSet.Release(); + + return err; +} + const NodeCredential * OperationalCredentialSet::GetNodeCredentialAt(const CertificateKeyId & trustedRootId) const { for (size_t i = 0; i < kOperationalCredentialsMax && mChipDeviceCredentials[i].nodeCredential.mCredential != nullptr; ++i) diff --git a/src/credentials/CHIPOperationalCredentials.h b/src/credentials/CHIPOperationalCredentials.h index 13837bb8d2303b..e9abae03d66a23 100644 --- a/src/credentials/CHIPOperationalCredentials.h +++ b/src/credentials/CHIPOperationalCredentials.h @@ -35,7 +35,8 @@ namespace chip { namespace Credentials { -static constexpr size_t kOperationalCredentialsMax = 5; +static constexpr size_t kOperationalCredentialsMax = 5; +static constexpr size_t kOperationalCertificateMaxSize = 400; using namespace Crypto; @@ -45,6 +46,18 @@ struct NodeCredential uint16_t mLen = 0; }; +struct OperationalCredentialSerializable +{ + uint16_t mNodeCredentialLen; + uint8_t mNodeCredential[kOperationalCertificateMaxSize]; + uint16_t mNodeKeypairLen; + uint8_t mNodeKeypair[kP256_PublicKey_Length + kP256_PrivateKey_Length]; + uint16_t mRootCertificateLen; + uint8_t mRootCertificate[kOperationalCertificateMaxSize]; + uint16_t mCACertificateLen; + uint8_t mCACertificate[kOperationalCertificateMaxSize]; +}; + struct NodeCredentialMap { CertificateKeyId trustedRootId; @@ -219,6 +232,23 @@ class DLL_EXPORT OperationalCredentialSet CHIP_ERROR SetDevOpCred(const CertificateKeyId & trustedRootId, const uint8_t * chipDeviceCredentials, uint16_t chipDeviceCredentialsLen); + /** + * @brief + * Serialize the OperationalCredentialSet indexed by a TrustedRootID to the given serializable data structure + * + * This method must be called while the OperationalCredentialSet class is valid (After Init and before Release) + */ + CHIP_ERROR ToSerializable(const CertificateKeyId & trustedRootId, OperationalCredentialSerializable & output); + + /** + * @brief + * Reconstruct OperationalCredentialSet class from the serializable data structure. + * + * This method must be called after initializing the OperationalCredentialSet class with internal allocation. + * No references/pointers to the input parameter are made. The input parameter can be freed after calling this method. + */ + CHIP_ERROR FromSerializable(const OperationalCredentialSerializable & input); + P256Keypair & GetDevOpCredKeypair(const CertificateKeyId & trustedRootId) { return *GetNodeKeypairAt(trustedRootId); } CHIP_ERROR SetDevOpCredKeypair(const CertificateKeyId & trustedRootId, P256Keypair * newKeypair); diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp index 84621cb1fdd5a2..9526ec3c260123 100644 --- a/src/credentials/tests/TestChipCert.cpp +++ b/src/credentials/tests/TestChipCert.cpp @@ -639,6 +639,52 @@ static void TestChipCert_CertType(nlTestSuite * inSuite, void * inContext) } } +static void TestChipCert_CertId(nlTestSuite * inSuite, void * inContext) +{ + CHIP_ERROR err; + ChipCertificateSet certSet; + + struct TestCase + { + uint8_t Cert; + uint64_t ExpectedCertId; + }; + + // clang-format off + static TestCase sTestCases[] = { + // Cert ExpectedCertId + // ============================================================= + { TestCert::kRoot01, 0xCACACACA00000001 }, + { TestCert::kRoot02, 0xCACACACA00000002 }, + { TestCert::kICA01, 0xCACACACA00000003 }, + { TestCert::kICA02, 0xCACACACA00000004 }, + { TestCert::kICA01_1, 0xCACACACA00000005 }, + { TestCert::kFWSign01, 0xFFFFFFFF00000001 }, + { TestCert::kNode01_01, 0xDEDEDEDE00010001 }, + { TestCert::kNode01_02, 0xDEDEDEDE00010002 }, + { TestCert::kNode02_01, 0xDEDEDEDE00020001 }, + { TestCert::kNode02_02, 0xDEDEDEDE00020002 }, + }; + // clang-format on + static const size_t sNumTestCases = sizeof(sTestCases) / sizeof(sTestCases[0]); + + for (unsigned i = 0; i < sNumTestCases; i++) + { + const TestCase & testCase = sTestCases[i]; + uint64_t chipId; + + // Initialize the certificate set and load the test certificate. + certSet.Init(1, kTestCertBufSize); + err = LoadTestCert(certSet, testCase.Cert, sNullLoadFlag, sNullDecodeFlag); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + err = certSet.GetCertSet()->mSubjectDN.GetCertChipId(chipId); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, chipId == testCase.ExpectedCertId); + } +} + static void TestChipCert_LoadDuplicateCerts(nlTestSuite * inSuite, void * inContext) { CHIP_ERROR err; @@ -963,6 +1009,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test CHIP Certificate Validation time", TestChipCert_CertValidTime), NL_TEST_DEF("Test CHIP Certificate Usage", TestChipCert_CertUsage), NL_TEST_DEF("Test CHIP Certificate Type", TestChipCert_CertType), + NL_TEST_DEF("Test CHIP Certificate ID", TestChipCert_CertId), NL_TEST_DEF("Test Loading Duplicate Certificates", TestChipCert_LoadDuplicateCerts), NL_TEST_DEF("Test CHIP Generate Root Certificate", TestChipCert_GenerateRootCert), NL_TEST_DEF("Test CHIP Generate Root Certificate with Fabric", TestChipCert_GenerateRootFabCert), diff --git a/src/credentials/tests/TestChipOperationalCredentials.cpp b/src/credentials/tests/TestChipOperationalCredentials.cpp index 99a81cc74512d7..4664286ad7c53a 100644 --- a/src/credentials/tests/TestChipOperationalCredentials.cpp +++ b/src/credentials/tests/TestChipOperationalCredentials.cpp @@ -40,11 +40,16 @@ enum // (in either CHIP or DER form), or to decode the certificates. }; +namespace { static const BitFlags sGenTBSHashFlag(CertDecodeFlags::kGenerateTBSHash); static const BitFlags sTrustAnchorFlag(CertDecodeFlags::kIsTrustAnchor); static const BitFlags sNullLoadFlag; +static OperationalCredentialSerializable sSerialized; +static OperationalCredentialSerializable sSerialized2; +} // namespace + static CHIP_ERROR SetEffectiveTime(ValidationContext & validContext, uint16_t year, uint8_t mon, uint8_t day, uint8_t hour = 0, uint8_t min = 0, uint8_t sec = 0) { @@ -180,6 +185,68 @@ static void TestChipOperationalCredentials_CertValidation(nlTestSuite * inSuite, } } +static void TestChipOperationalCredentials_Serialization(nlTestSuite * inSuite, void * inContext) +{ + CHIP_ERROR err; + ChipCertificateSet certSet; + OperationalCredentialSet opCredSet; + OperationalCredentialSet opCredSet2; + P256Keypair keypair; + P256SerializedKeypair serializedKeypair; + enum + { + kMaxCerts = 2 + }; + + // Initialize the certificate set and load the specified test certificates. + certSet.Init(kMaxCerts, kTestCertBufSize); + err = LoadTestCert(certSet, TestCerts::kRoot01, sNullLoadFlag, sTrustAnchorFlag); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + err = LoadTestCert(certSet, TestCerts::kICA01, sNullLoadFlag, sGenTBSHashFlag); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + // Initialize the Operational Credential Set and load certificate set + NL_TEST_ASSERT(inSuite, opCredSet.Init(&certSet, 1) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCredSet2.Init(1) == CHIP_NO_ERROR); + + const CertificateKeyId * trustedRootId = opCredSet.GetTrustedRootId(static_cast(opCredSet.GetCertCount() - 1)); + NL_TEST_ASSERT(inSuite, trustedRootId != nullptr); + + NL_TEST_ASSERT(inSuite, + serializedKeypair.SetLength(sTestCert_Node01_01_PublicKey_Len + sTestCert_Node01_01_PrivateKey_Len) == + CHIP_NO_ERROR); + + memcpy(static_cast(serializedKeypair), sTestCert_Node01_01_PublicKey, sTestCert_Node01_01_PublicKey_Len); + memcpy(static_cast(serializedKeypair) + sTestCert_Node01_01_PublicKey_Len, sTestCert_Node01_01_PrivateKey, + sTestCert_Node01_01_PrivateKey_Len); + + NL_TEST_ASSERT(inSuite, keypair.Deserialize(serializedKeypair) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, opCredSet.SetDevOpCredKeypair(*trustedRootId, &keypair) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + opCredSet.SetDevOpCred(*trustedRootId, sTestCert_Node01_01_Chip, + static_cast(sTestCert_Node01_01_Chip_Len)) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, opCredSet.ToSerializable(*trustedRootId, sSerialized) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, opCredSet2.FromSerializable(sSerialized) == CHIP_NO_ERROR); + + const CertificateKeyId * trustedRootId2 = opCredSet2.GetTrustedRootId(static_cast(opCredSet2.GetCertCount() - 1)); + NL_TEST_ASSERT(inSuite, trustedRootId2->IsEqual(*trustedRootId)); + + NL_TEST_ASSERT(inSuite, opCredSet2.ToSerializable(*trustedRootId2, sSerialized2) == CHIP_NO_ERROR); + + NL_TEST_ASSERT(inSuite, + strncmp(reinterpret_cast(&sSerialized), reinterpret_cast(&sSerialized2), + sizeof(sSerialized)) == 0); + + // Clear the certificate set. + certSet.Release(); + // Clear the Operational Credential Set + opCredSet2.Release(); + opCredSet.Release(); +} + /** * Set up the test suite. */ @@ -210,6 +277,7 @@ int TestChipOperationalCredentials_Teardown(void * inContext) // clang-format off static const nlTest sTests[] = { NL_TEST_DEF("Test CHIP Certificate Validation", TestChipOperationalCredentials_CertValidation), + NL_TEST_DEF("Test CHIP Certificate Serialization", TestChipOperationalCredentials_Serialization), NL_TEST_SENTINEL() }; // clang-format on