Skip to content

Commit

Permalink
PDC: Add identity generation and parsing for the compact TLV format
Browse files Browse the repository at this point in the history
  • Loading branch information
ksperling-apple committed Nov 2, 2023
1 parent 5ef33a3 commit ae8a4bd
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 25 deletions.
56 changes: 55 additions & 1 deletion src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1433,6 +1433,13 @@ CHIP_ERROR CertificateValidityPolicy::ApplyDefaultPolicy(const ChipCertificateDa
}
}

void InitNetworkIdentitySubject(ChipDN & name)
{
name.Clear();
ChipError err = name.AddAttribute_CommonName(kNetworkIdentityCN, /* not printable */ false);
VerifyOrDie(err == CHIP_NO_ERROR); // AddAttribute can't fail in this case
}

static void CalculateKeyIdentifierSha256(const P256PublicKeySpan & publicKey, MutableCertificateKeyId keyId)
{
uint8_t hash[kSHA256_Hash_Length];
Expand All @@ -1445,7 +1452,7 @@ static CHIP_ERROR ValidateChipNetworkIdentity(const ChipCertificateData & certDa
{
ReturnErrorOnFailure(ValidateCertificateType(certData, CertType::kNetworkIdentity));

VerifyOrReturnError(certData.mSerialNumber.data_equal(kNetworkIdentitySerialNumber), CHIP_ERROR_WRONG_CERT_TYPE);
VerifyOrReturnError(certData.mSerialNumber.data_equal(kNetworkIdentitySerialNumberBytes), CHIP_ERROR_WRONG_CERT_TYPE);
VerifyOrReturnError(certData.mNotBeforeTime == kNetworkIdentityNotBeforeTime, CHIP_ERROR_WRONG_CERT_TYPE);
VerifyOrReturnError(certData.mNotAfterTime == kNetworkIdentityNotAfterTime, CHIP_ERROR_WRONG_CERT_TYPE);
VerifyOrReturnError(certData.mIssuerDN.IsEqual(certData.mSubjectDN), CHIP_ERROR_WRONG_CERT_TYPE);
Expand Down Expand Up @@ -1490,5 +1497,52 @@ CHIP_ERROR ExtractIdentifierFromChipNetworkIdentity(const ByteSpan & cert, Mutab
return CHIP_NO_ERROR;
}

static CHIP_ERROR GenerateNetworkIdentitySignature(const P256Keypair & keypair, P256ECDSASignature & signature)
{
// Create a buffer and writer to capture the TBS (to-be-signed) portion of the certificate.
chip::Platform::ScopedMemoryBuffer<uint8_t> asn1TBSBuf;
VerifyOrReturnError(asn1TBSBuf.Alloc(kNetworkIdenitityTBSLength), CHIP_ERROR_NO_MEMORY);

ASN1Writer writer;
writer.Init(asn1TBSBuf.Get(), kNetworkIdenitityTBSLength);

// Generate the TBSCertificate and sign it
ReturnErrorOnFailure(EncodeNetworkIdentityTBSCert(keypair.Pubkey(), writer));
ReturnErrorOnFailure(keypair.ECDSA_sign_msg(asn1TBSBuf.Get(), writer.GetLengthWritten(), signature));

return CHIP_NO_ERROR;
}

static CHIP_ERROR EncodeCompactIdentityCert(TLVWriter & writer, Tag tag, P256PublicKeySpan const & publicKey,
P256ECDSASignatureSpan const & signature)
{
TLVType containerType;
ReturnErrorOnFailure(writer.StartContainer(tag, kTLVType_Structure, containerType));
ReturnErrorOnFailure(writer.Put(ContextTag(kTag_EllipticCurvePublicKey), publicKey));
ReturnErrorOnFailure(writer.Put(ContextTag(kTag_ECDSASignature), signature));
ReturnErrorOnFailure(writer.EndContainer(containerType));
return CHIP_NO_ERROR;
}

CHIP_ERROR NewChipNetworkIdentity(const Crypto::P256Keypair & keypair, MutableByteSpan & compactIdentityCert)
{
VerifyOrReturnError(!compactIdentityCert.empty(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(CanCastTo<uint32_t>(compactIdentityCert.size()), CHIP_ERROR_INVALID_ARGUMENT);

Crypto::P256ECDSASignature signature;
ReturnErrorOnFailure(GenerateNetworkIdentitySignature(keypair, signature));

TLVWriter writer;
writer.Init(compactIdentityCert);

P256PublicKeySpan publicKeySpan(keypair.Pubkey().ConstBytes());
P256ECDSASignatureSpan signatureSpan(signature.ConstBytes());
VerifyOrDie(signature.Length() == signatureSpan.size());
ReturnErrorOnFailure(EncodeCompactIdentityCert(writer, AnonymousTag(), publicKeySpan, signatureSpan));

compactIdentityCert.reduce_size(writer.GetLengthWritten());
return CHIP_NO_ERROR;
}

} // namespace Credentials
} // namespace chip
16 changes: 14 additions & 2 deletions src/credentials/CHIPCert.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ static constexpr uint16_t kX509NoWellDefinedExpirationDateYear = 9999;
static constexpr uint32_t kMaxCHIPCertLength = 400;
static constexpr uint32_t kMaxDERCertLength = 600;

// The decode buffer is used to reconstruct TBS section of X.509 certificate, which doesn't include signature.
static constexpr uint32_t kMaxCHIPCertDecodeBufLength = kMaxDERCertLength - Crypto::kMax_ECDSA_Signature_Length_Der;
// As per spec section 11.24 (Wi-Fi Authentication with Per-Device Credentials)
inline constexpr uint32_t kMaxCHIPCompactNetworkIdentityLength = 140;

/** Data Element Tags for the CHIP Certificate
*/
Expand Down Expand Up @@ -542,6 +542,7 @@ CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac);

/**
* Validates a Network (Client) Identity in TLV-encoded form.
* Accepts either a full certificate or the compact-pdc-identity format.
*
* This function parses the certificate, ensures the rigid fields have the values mandated by the
* specification, and validates the certificate signature.
Expand Down Expand Up @@ -618,6 +619,17 @@ CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, const Cry
CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, const Crypto::P256PublicKey & subjectPubkey,
const Crypto::P256Keypair & issuerKeypair, MutableByteSpan & x509Cert);

/**
* @brief Generates a Network (Client) Identity certificate in TLV-encoded form.
*
* @param keypair The key pair underlying the identity.
* @param cert Buffer to store the signed certificate in compact-pdc-identity TLV format.
* Must be at least `kMaxCHIPCompactNetworkIdentityLength` bytes long.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR NewChipNetworkIdentity(const Crypto::P256Keypair & keypair, MutableByteSpan & cert);

/**
* @brief
* Convert a certificate date/time (in the form of an ASN.1 universal time structure) into a CHIP Epoch time.
Expand Down
53 changes: 50 additions & 3 deletions src/credentials/CHIPCertToX509.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#include <inttypes.h>
#include <stddef.h>

#include <credentials/CHIPCert.h>
#include <credentials/CHIPCert_Internal.h>
#include <lib/asn1/ASN1.h>
#include <lib/asn1/ASN1Macros.h>
#include <lib/core/CHIPCore.h>
Expand Down Expand Up @@ -493,6 +493,8 @@ static CHIP_ERROR DecodeConvertECDSASignature(TLVReader & reader, ASN1Writer & w
* @param certData Structure containing data extracted from the TBS portion of the
* CHIP certificate.
*
* Note: The reader must be positioned on the SerialNumber element.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
static CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData)
Expand All @@ -513,7 +515,7 @@ static CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer,

// serialNumber CertificateSerialNumber
// CertificateSerialNumber ::= INTEGER
ReturnErrorOnFailure(reader.Next(kTLVType_ByteString, ContextTag(kTag_SerialNumber)));
ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_SerialNumber)));
ReturnErrorOnFailure(reader.Get(certData.mSerialNumber));
ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false,
certData.mSerialNumber.data(), static_cast<uint16_t>(certData.mSerialNumber.size())));
Expand Down Expand Up @@ -554,6 +556,41 @@ static CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer,
return err;
}

/**
* Variant of DecodeConvertTBSCert that handles reading a compact-pdc-identity
* where only the subject public key is actually encoded. All other values are
* populated / written as the well-known values mandated by the specification.
*
* Note: The reader must be positioned on the EllipticCurvePublicKey element.
*/
static CHIP_ERROR DecodeConvertTBSCertCompactIdentity(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData)
{
// Decode the public key, everything else is rigid
ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_EllipticCurvePublicKey)));
ReturnErrorOnFailure(reader.Get(certData.mPublicKey));

// Populate rigid ChipCertificateData fields
certData.mSerialNumber = kNetworkIdentitySerialNumberBytes;
certData.mSigAlgoOID = kOID_SigAlgo_ECDSAWithSHA256;
InitNetworkIdentitySubject(certData.mIssuerDN);
certData.mNotBeforeTime = kNetworkIdentityNotBeforeTime;
certData.mNotAfterTime = kNetworkIdentityNotAfterTime;
InitNetworkIdentitySubject(certData.mSubjectDN);
certData.mPubKeyAlgoOID = kOID_PubKeyAlgo_ECPublicKey;
certData.mPubKeyCurveOID = kOID_EllipticCurve_prime256v1;
certData.mCertFlags.Set(CertFlags::kExtPresent_BasicConstraints);
certData.mCertFlags.Set(CertFlags::kExtPresent_KeyUsage);
certData.mKeyUsageFlags = kNetworkIdentityKeyUsage;
certData.mCertFlags.Set(CertFlags::kExtPresent_ExtendedKeyUsage);
certData.mKeyPurposeFlags = kNetworkIdentityKeyPurpose;

if (!writer.IsNullWriter())
{
ReturnErrorOnFailure(EncodeNetworkIdentityTBSCert(certData.mPublicKey, writer));
}
return CHIP_NO_ERROR;
}

/**
* Decode a CHIP TLV certificate and convert it to X.509 DER form.
*
Expand Down Expand Up @@ -581,7 +618,17 @@ static CHIP_ERROR DecodeConvertCert(TLVReader & reader, ASN1Writer & writer, ASN
ASN1_START_SEQUENCE
{
// tbsCertificate TBSCertificate,
ReturnErrorOnFailure(DecodeConvertTBSCert(reader, tbsWriter, certData));
reader.Next();
if (reader.GetTag() == ContextTag(kTag_EllipticCurvePublicKey))
{
// If the struct starts with the ec-pub-key we're dealing with a
// Network (Client) Identity in compact-pdc-identity format.
DecodeConvertTBSCertCompactIdentity(reader, tbsWriter, certData);
}
else
{
ReturnErrorOnFailure(DecodeConvertTBSCert(reader, tbsWriter, certData));
}

// signatureAlgorithm AlgorithmIdentifier
// AlgorithmIdentifier ::= SEQUENCE
Expand Down
17 changes: 15 additions & 2 deletions src/credentials/CHIPCert_Internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@
namespace chip {
namespace Credentials {

// The decode buffer is used to reconstruct TBS section of X.509 certificate, which doesn't include signature.
inline constexpr size_t kMaxCHIPCertDecodeBufLength = kMaxDERCertLength - Crypto::kMax_ECDSA_Signature_Length_Der;

// The TBSCerticate of a Network (Client) Identity has a fixed (smaller) size.
inline constexpr size_t kNetworkIdenitityTBSLength = 244;

// Constants for Network (Client) Identities as per section 11.24 (Wi-Fi
// Authentication with Per-Device Credentials) of the Matter spec.
inline constexpr CharSpan kNetworkIdentityCN = "*"_span;
inline constexpr ByteSpan kNetworkIdentitySerialNumber = ByteSpan((uint8_t[1]){ 1 });
inline constexpr CharSpan kNetworkIdentityCN = "*"_span;
inline constexpr uint8_t kNetworkIdentitySerialNumber = 1;
inline constexpr ByteSpan kNetworkIdentitySerialNumberBytes = ByteSpan((uint8_t[1]){ kNetworkIdentitySerialNumber });

inline constexpr uint32_t kNetworkIdentityNotBeforeTime = 1;
inline constexpr uint32_t kNetworkIdentityNotAfterTime = kNullCertTime;
Expand All @@ -35,5 +42,11 @@ inline constexpr auto kNetworkIdentityKeyUsage = BitFlags<KeyUsageFlags>(KeyUsag
inline constexpr auto kNetworkIdentityKeyPurpose =
BitFlags<KeyPurposeFlags>(KeyPurposeFlags::kClientAuth, KeyPurposeFlags::kServerAuth);

// Initializes a ChipDN as CN=kNetworkIdentityCN
void InitNetworkIdentitySubject(ChipDN & name);

// Emits a X.509 TBSCertificate for a Network (Client) Identity based on the specified key.
CHIP_ERROR EncodeNetworkIdentityTBSCert(const Crypto::P256PublicKey & pubkey, ASN1::ASN1Writer & writer);

} // namespace Credentials
} // namespace chip
59 changes: 56 additions & 3 deletions src/credentials/GenerateChipX509Cert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
#include <inttypes.h>
#include <stddef.h>

#include <credentials/CHIPCert.h>
#include <credentials/CHIPCert_Internal.h>
#include <lib/asn1/ASN1.h>
#include <lib/asn1/ASN1Macros.h>
#include <lib/core/CHIPCore.h>
Expand Down Expand Up @@ -313,8 +313,6 @@ CHIP_ERROR EncodeChipECDSASignature(Crypto::P256ECDSASignature & signature, ASN1
return err;
}

} // namespace

CHIP_ERROR EncodeTBSCert(const X509CertRequestParams & requestParams, const Crypto::P256PublicKey & subjectPubkey,
const Crypto::P256PublicKey & issuerPubkey, ASN1Writer & writer)
{
Expand Down Expand Up @@ -367,6 +365,61 @@ CHIP_ERROR EncodeTBSCert(const X509CertRequestParams & requestParams, const Cryp
return err;
}

} // namespace

CHIP_ERROR EncodeNetworkIdentityTBSCert(const P256PublicKey & pubkey, ASN1Writer & writer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
ChipDN issuerAndSubject;
InitNetworkIdentitySubject(issuerAndSubject);

ASN1_START_SEQUENCE
{
// version [0] EXPLICIT Version DEFAULT v1
ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0)
{
ASN1_ENCODE_INTEGER(2); // Version ::= INTEGER { v1(0), v2(1), v3(2) }
}
ASN1_END_CONSTRUCTED;

ReturnErrorOnFailure(writer.PutInteger(kNetworkIdentitySerialNumber));

ASN1_START_SEQUENCE
{
ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256);
}
ASN1_END_SEQUENCE;

// issuer Name
ReturnErrorOnFailure(issuerAndSubject.EncodeToASN1(writer));

// validity Validity,
ReturnErrorOnFailure(EncodeValidity(kNetworkIdentityNotBeforeTime, kNetworkIdentityNotAfterTime, writer));

// subject Name
ReturnErrorOnFailure(issuerAndSubject.EncodeToASN1(writer));

ReturnErrorOnFailure(EncodeSubjectPublicKeyInfo(pubkey, writer));

// certificate extensions
ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 3)
{
ASN1_START_SEQUENCE
{
EncodeIsCAExtension(kNotCACert, writer);
EncodeKeyUsageExtension(KeyUsageFlags::kDigitalSignature, writer);
EncodeExtKeyUsageExtension({ kOID_KeyPurpose_ClientAuth, kOID_KeyPurpose_ServerAuth }, writer);
}
ASN1_END_SEQUENCE;
}
ASN1_END_CONSTRUCTED;
}
ASN1_END_SEQUENCE;

exit:
return err;
}

CHIP_ERROR NewChipX509Cert(const X509CertRequestParams & requestParams, const Crypto::P256PublicKey & subjectPubkey,
const Crypto::P256Keypair & issuerKeypair, MutableByteSpan & x509Cert)
{
Expand Down
51 changes: 37 additions & 14 deletions src/credentials/tests/TestChipCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2149,24 +2149,46 @@ static void TestChipCert_ExtractPublicKeyAndSKID(nlTestSuite * inSuite, void * i

static void TestChipCert_PDCIdentityValidation(nlTestSuite * inSuite, void * inContext)
{
CHIP_ERROR err;
CertificateKeyIdStorage keyId;

// Validate only
err = ValidateChipNetworkIdentity(sTestCert_PDCID01_Chip);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
// Test with both the full and compact TLV representations
for (auto && cert : { sTestCert_PDCID01_Chip, sTestCert_PDCID01_ChipCompact })
{
// Validate only
NL_TEST_ASSERT(inSuite, ValidateChipNetworkIdentity(cert) == CHIP_NO_ERROR);

// Validate and calculate identifier
keyId.fill(0xaa);
NL_TEST_ASSERT(inSuite, ValidateChipNetworkIdentity(cert, keyId) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, CertificateKeyId(keyId).data_equal(sTestCert_PDCID01_KeyId));

// Extract identifier only
keyId.fill(0xaa);
NL_TEST_ASSERT(inSuite, ExtractIdentifierFromChipNetworkIdentity(cert, keyId) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, CertificateKeyId(keyId).data_equal(sTestCert_PDCID01_KeyId));
}
}

// Validate and calculate identifier
keyId.fill(0xaa);
err = ValidateChipNetworkIdentity(sTestCert_PDCID01_Chip, keyId);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, CertificateKeyId(keyId).data_equal(sTestCert_PDCID01_KeyId));
static void TestChipCert_PDCIdentityGeneration(nlTestSuite * inSuite, void * inContext)
{
// Generate a new keypair
P256Keypair keypair;
NL_TEST_ASSERT(inSuite, keypair.Initialize(ECPKeyTarget::ECDSA) == CHIP_NO_ERROR);

// Extract identifier only
keyId.fill(0xaa);
err = ExtractIdentifierFromChipNetworkIdentity(sTestCert_PDCID01_Chip, keyId);
NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, CertificateKeyId(keyId).data_equal(sTestCert_PDCID01_KeyId));
// Generate an identity certificate based on the keypair
uint8_t buffer[kMaxCHIPCompactNetworkIdentityLength];
MutableByteSpan cert(buffer);
NL_TEST_ASSERT(inSuite, NewChipNetworkIdentity(keypair, cert) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, ValidateChipNetworkIdentity(cert) == CHIP_NO_ERROR);

// It should round-trip to X.509 DER and back, and remain valid.
uint8_t derBuffer[kMaxDERCertLength];
MutableByteSpan derCert(derBuffer);
NL_TEST_ASSERT(inSuite, ConvertChipCertToX509Cert(cert, derCert) == CHIP_NO_ERROR);
uint8_t tlvBuffer[kMaxCHIPCertLength];
MutableByteSpan tlvCert(tlvBuffer); // won't be compact after round-tripping
NL_TEST_ASSERT(inSuite, ConvertX509CertToChipCert(derCert, tlvCert) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite, ValidateChipNetworkIdentity(tlvCert) == CHIP_NO_ERROR);
}

/**
Expand Down Expand Up @@ -2228,6 +2250,7 @@ static const nlTest sTests[] = {
NL_TEST_DEF("Test extracting Subject DN from chip certificate", TestChipCert_ExtractSubjectDNFromChipCert),
NL_TEST_DEF("Test extracting PublicKey and SKID from chip certificate", TestChipCert_ExtractPublicKeyAndSKID),
NL_TEST_DEF("Test PDC Identity Validation", TestChipCert_PDCIdentityValidation),
NL_TEST_DEF("Test PDC Identity Generation", TestChipCert_PDCIdentityGeneration),
NL_TEST_SENTINEL()
};
// clang-format on
Expand Down

0 comments on commit ae8a4bd

Please sign in to comment.