Skip to content

Commit

Permalink
PDC: Identifier calculation, Identity generation, and handling of com…
Browse files Browse the repository at this point in the history
…pact-pdc-identity format (#30159)

* PDC: Add key id calculation for network identities

* PDC: Add identity generation and parsing for the compact TLV format

* PDC: Add support for identity generation to the chip-cert tool

* Address review comments

* Address further review comments

* Prefix out parameters in new APIs

Fixes #29856
  • Loading branch information
ksperling-apple authored Nov 7, 2023
1 parent d058b33 commit 2338544
Show file tree
Hide file tree
Showing 11 changed files with 556 additions and 205 deletions.
109 changes: 99 additions & 10 deletions src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1095,12 +1095,19 @@ DLL_EXPORT CHIP_ERROR ChipEpochToASN1Time(uint32_t epochTime, chip::ASN1::ASN1Un
return CHIP_NO_ERROR;
}

static CHIP_ERROR ValidateCertificateType(const ChipCertificateData & certData, CertType expectedType)
{
CertType certType;
ReturnErrorOnFailure(certData.mSubjectDN.GetCertType(certType));
VerifyOrReturnError(certType == expectedType, CHIP_ERROR_WRONG_CERT_TYPE);
return CHIP_NO_ERROR;
}

CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac)
{
ChipCertificateSet certSet;
ChipCertificateData certData;
ValidationContext validContext;
CertType 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.
Expand All @@ -1109,8 +1116,7 @@ CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac)

ReturnErrorOnFailure(certSet.LoadCert(rcac, CertDecodeFlags::kGenerateTBSHash));

ReturnErrorOnFailure(certData.mSubjectDN.GetCertType(certType));
VerifyOrReturnError(certType == CertType::kRoot, CHIP_ERROR_WRONG_CERT_TYPE);
ReturnErrorOnFailure(ValidateCertificateType(certData, CertType::kRoot));

VerifyOrReturnError(certData.mSubjectDN.IsEqual(certData.mIssuerDN), CHIP_ERROR_WRONG_CERT_TYPE);

Expand Down Expand Up @@ -1427,16 +1433,27 @@ CHIP_ERROR CertificateValidityPolicy::ApplyDefaultPolicy(const ChipCertificateDa
}
}

CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert)
void InitNetworkIdentitySubject(ChipDN & name)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData, CertDecodeFlags::kGenerateTBSHash));
name.Clear();
CHIP_ERROR err = name.AddAttribute_CommonName(kNetworkIdentityCN, /* not printable */ false);
VerifyOrDie(err == CHIP_NO_ERROR); // AddAttribute can't fail in this case
}

CertType certType;
ReturnErrorOnFailure(certData.mSubjectDN.GetCertType(certType));
VerifyOrReturnError(certType == CertType::kNetworkIdentity, CHIP_ERROR_WRONG_CERT_TYPE);
static CHIP_ERROR CalculateKeyIdentifierSha256(const P256PublicKeySpan & publicKey, MutableCertificateKeyId outKeyId)
{
uint8_t hash[kSHA256_Hash_Length];
static_assert(outKeyId.size() <= sizeof(hash)); // truncating 32 bytes down to 20
ReturnErrorOnFailure(Hash_SHA256(publicKey.data(), publicKey.size(), hash));
memcpy(outKeyId.data(), hash, outKeyId.size());
return CHIP_NO_ERROR;
}

static CHIP_ERROR ValidateChipNetworkIdentity(const ChipCertificateData & certData)
{
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 All @@ -1455,5 +1472,77 @@ CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert)
return CHIP_NO_ERROR;
}

CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData, CertDecodeFlags::kGenerateTBSHash));
ReturnErrorOnFailure(ValidateChipNetworkIdentity(certData));
return CHIP_NO_ERROR;
}

CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert, MutableCertificateKeyId outKeyId)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData, CertDecodeFlags::kGenerateTBSHash));
ReturnErrorOnFailure(ValidateChipNetworkIdentity(certData));
ReturnErrorOnFailure(CalculateKeyIdentifierSha256(certData.mPublicKey, outKeyId));
return CHIP_NO_ERROR;
}

CHIP_ERROR ExtractIdentifierFromChipNetworkIdentity(const ByteSpan & cert, MutableCertificateKeyId outKeyId)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData));
ReturnErrorOnFailure(ValidateCertificateType(certData, CertType::kNetworkIdentity));
ReturnErrorOnFailure(CalculateKeyIdentifierSha256(certData.mPublicKey, outKeyId));
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(kNetworkIdentityTBSLength), CHIP_ERROR_NO_MEMORY);

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

// 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, const P256PublicKeySpan & publicKey,
const P256ECDSASignatureSpan & 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 & outCompactCert)
{
VerifyOrReturnError(!outCompactCert.empty(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(CanCastTo<uint32_t>(outCompactCert.size()), CHIP_ERROR_INVALID_ARGUMENT);

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

TLVWriter writer;
writer.Init(outCompactCert);

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

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

} // namespace Credentials
} // namespace chip
45 changes: 43 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 = 137;

/** Data Element Tags for the CHIP Certificate
*/
Expand Down Expand Up @@ -395,6 +395,16 @@ class ChipDN
*/
using CertificateKeyId = FixedByteSpan<kKeyIdentifierLength>;

/**
* @brief A mutable `CertificateKeyId`.
*/
using MutableCertificateKeyId = FixedSpan<uint8_t, kKeyIdentifierLength>;

/**
* @brief A storage type for `CertificateKeyId` and `MutableCertificateKeyId`.
*/
using CertificateKeyIdStorage = std::array<uint8_t, kKeyIdentifierLength>;

/**
* @brief A data structure for holding a P256 ECDSA signature, without the ownership of it.
*/
Expand Down Expand Up @@ -532,10 +542,13 @@ 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.
*
* @param cert The network identity certificate to validate.
*
* @return CHIP_NO_ERROR on success, CHIP_ERROR_WRONG_CERT_TYPE if the certificate does
* not conform to the requirements for a Network Identity, CHIP_ERROR_INVALID_SIGNATURE
* if the certificate has an invalid signature, or another CHIP_ERROR.
Expand All @@ -544,6 +557,14 @@ CHIP_ERROR ValidateChipRCAC(const ByteSpan & rcac);
*/
CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert);

/**
* Convenience variant of `ValidateChipNetworkIdentity` that, upon successful validation, also
* calculates the key identifier for the Network (Client) Identity.
* @see ValidateChipNetworkIdentity
* @see ExtractIdentifierFromChipNetworkIdentity
*/
CHIP_ERROR ValidateChipNetworkIdentity(const ByteSpan & cert, MutableCertificateKeyId outKeyId);

struct FutureExtension
{
ByteSpan OID;
Expand Down Expand Up @@ -598,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 outCompactCert 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 & outCompactCert);

/**
* @brief
* Convert a certificate date/time (in the form of an ASN.1 universal time structure) into a CHIP Epoch time.
Expand Down Expand Up @@ -827,5 +859,14 @@ CHIP_ERROR ExtractSubjectDNFromChipCert(const ByteSpan & chipCert, ChipDN & dn);
*/
CHIP_ERROR ExtractSubjectDNFromX509Cert(const ByteSpan & x509Cert, ChipDN & dn);

/**
* Extracts the key identifier from a Network (Client) Identity in TLV-encoded form.
* Does NOT perform full validation of the identity certificate.
*
* @return CHIP_NO_ERROR on success, CHIP_ERROR_WRONG_CERT_TYPE if the certificate is
* not a Network (Client) Identity, or another CHIP_ERROR if parsing fails.
*/
CHIP_ERROR ExtractIdentifierFromChipNetworkIdentity(const ByteSpan & cert, MutableCertificateKeyId outKeyId);

} // namespace Credentials
} // namespace chip
55 changes: 51 additions & 4 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 @@ -482,6 +482,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 @@ -502,7 +504,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 @@ -543,6 +545,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 @@ -570,7 +607,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 Expand Up @@ -640,7 +687,7 @@ CHIP_ERROR DecodeChipCert(TLVReader & reader, ChipCertificateData & certData, Bi

// Hash the encoded TBS certificate. Only SHA256 is supported.
VerifyOrReturnError(certData.mSigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, CHIP_ERROR_UNSUPPORTED_SIGNATURE_TYPE);
chip::Crypto::Hash_SHA256(asn1TBSBuf.Get(), tbsWriter.GetLengthWritten(), certData.mTBSHash);
ReturnErrorOnFailure(Hash_SHA256(asn1TBSBuf.Get(), tbsWriter.GetLengthWritten(), certData.mTBSHash));
certData.mCertFlags.Set(CertFlags::kTBSHashPresent);
}
else
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 kNetworkIdentityTBSLength = 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
Loading

0 comments on commit 2338544

Please sign in to comment.