Skip to content

Commit

Permalink
CHIP credential serialization (#6400)
Browse files Browse the repository at this point in the history
* 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 <boris.itkis@gmail.com>
  • Loading branch information
2 people authored and pull[bot] committed Aug 5, 2021
1 parent 232c81e commit 2305896
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 4 deletions.
32 changes: 30 additions & 2 deletions src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CertDecodeFlags> decodeFlags)
CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags<CertDecodeFlags> 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.
Expand All @@ -172,6 +172,8 @@ CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags<CertDecodeF
// Must be positioned on the structure element representing the certificate.
VerifyOrReturnError(reader.GetType() == kTLVType_Structure, CHIP_ERROR_INVALID_ARGUMENT);

cert.mCertificate = chipCert;

{
TLVType containerType;

Expand Down Expand Up @@ -776,6 +778,32 @@ CHIP_ERROR ChipDN::GetCertType(uint8_t & certType) const
return err;
}

CHIP_ERROR ChipDN::GetCertChipId(uint64_t & chipId) const
{
uint8_t rdnCount = RDNCount();

chipId = 0;

for (uint8_t i = 0; i < rdnCount; i++)
{
switch (rdn[i].mAttrOID)
{
case kOID_AttributeType_ChipRootId:
case kOID_AttributeType_ChipICAId:
case kOID_AttributeType_ChipNodeId:
case kOID_AttributeType_ChipFirmwareSigningId:
VerifyOrReturnError(chipId == 0, CHIP_ERROR_WRONG_CERT_TYPE);

chipId = rdn[i].mAttrValue.mChipVal;
break;
default:
break;
}
}

return CHIP_NO_ERROR;
}

bool ChipDN::IsEqual(const ChipDN & other) const
{
bool res = true;
Expand Down
14 changes: 13 additions & 1 deletion src/credentials/CHIPCert.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ class ChipDN
**/
CHIP_ERROR GetCertType(uint8_t & certType) const;

/**
* @brief Retrieve the ID of a CHIP certificate.
*
* @param certId A reference to the certificate ID value.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR GetCertChipId(uint64_t & chipId) const;

bool IsEqual(const ChipDN & other) const;

/**
Expand Down Expand Up @@ -301,6 +310,7 @@ struct ChipCertificateData
void Clear();
bool IsEqual(const ChipCertificateData & other) const;

ByteSpan mCertificate; /**< Original raw buffer data. */
ChipDN mSubjectDN; /**< Certificate Subject DN. */
ChipDN mIssuerDN; /**< Certificate Issuer DN. */
CertificateKeyId mSubjectKeyId; /**< Certificate Subject public key identifier. */
Expand Down Expand Up @@ -433,10 +443,12 @@ class DLL_EXPORT ChipCertificateSet
*
* @param reader A TLVReader positioned at the CHIP certificate TLV structure.
* @param decodeFlags Certificate decoding option flags.
* @param chipCert Buffer containing certificate encoded on CHIP format. It is required that this CHIP certificate
* in chipCert ByteSpan stays valid while the certificate data in the set is used.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR LoadCert(chip::TLV::TLVReader & reader, BitFlags<CertDecodeFlags> decodeFlags);
CHIP_ERROR LoadCert(chip::TLV::TLVReader & reader, BitFlags<CertDecodeFlags> decodeFlags, ByteSpan chipCert = ByteSpan());

/**
* @brief Load CHIP certificates into set.
Expand Down
81 changes: 81 additions & 0 deletions src/credentials/CHIPOperationalCredentials.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
#include <credentials/CHIPOperationalCredentials.h>
#include <support/CHIPMem.h>
#include <support/CodeUtils.h>
#include <support/SafeInt.h>

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)
Expand Down Expand Up @@ -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<uint16_t>(serializedKeypair.Length());

for (uint8_t i = 0; i < certificateSet->GetCertCount(); ++i)
{
VerifyOrReturnError(CanCastTo<uint16_t>(dataSet[i].mCertificate.size()), CHIP_ERROR_INTERNAL);
memcpy(ptrSerializableCerts[i], dataSet[i].mCertificate.data(), dataSet[i].mCertificate.size());
*ptrSerializableCertsLen[i] = static_cast<uint16_t>(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>(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>(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)
Expand Down
32 changes: 31 additions & 1 deletion src/credentials/CHIPOperationalCredentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
47 changes: 47 additions & 0 deletions src/credentials/tests/TestChipCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand Down
Loading

0 comments on commit 2305896

Please sign in to comment.