Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDC: Identifier calculation, Identity generation, and handling of compact-pdc-identity format #30159

Merged
merged 6 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,26 @@ 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 void CalculateKeyIdentifierSha256(const P256PublicKeySpan & publicKey, MutableCertificateKeyId keyId)
{
uint8_t hash[kSHA256_Hash_Length];
static_assert(keyId.size() <= sizeof(hash)); // truncating 32 bytes down to 20
chip::Crypto::Hash_SHA256(publicKey.data(), publicKey.size(), hash);
ksperling-apple marked this conversation as resolved.
Show resolved Hide resolved
memcpy(keyId.data(), hash, keyId.size());
}

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 +1471,78 @@ 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 keyId)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData, CertDecodeFlags::kGenerateTBSHash));
ReturnErrorOnFailure(ValidateChipNetworkIdentity(certData));
CalculateKeyIdentifierSha256(certData.mPublicKey, keyId);
return CHIP_NO_ERROR;
}

CHIP_ERROR ExtractIdentifierFromChipNetworkIdentity(const ByteSpan & cert, MutableCertificateKeyId keyId)
{
ChipCertificateData certData;
ReturnErrorOnFailure(DecodeChipCert(cert, certData));
ReturnErrorOnFailure(ValidateCertificateType(certData, CertType::kNetworkIdentity));
CalculateKeyIdentifierSha256(certData.mPublicKey, keyId);
return CHIP_NO_ERROR;
}
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved

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, const P256PublicKeySpan & publicKey,
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved
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 & 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());
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved
ReturnErrorOnFailure(EncodeCompactIdentityCert(writer, AnonymousTag(), publicKeySpan, signatureSpan));

compactIdentityCert.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;
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved

/** 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 keyId);
ksperling-apple marked this conversation as resolved.
Show resolved Hide resolved

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 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 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 keyId);

} // namespace Credentials
} // namespace chip
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 @@ -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)
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved
{
// 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())
ksperling-apple marked this conversation as resolved.
Show resolved Hide resolved
{
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
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;
tcarmelveilleux marked this conversation as resolved.
Show resolved Hide resolved

// The TBSCerticate of a Network (Client) Identity has a fixed (smaller) size.
inline constexpr size_t kNetworkIdenitityTBSLength = 244;
ksperling-apple marked this conversation as resolved.
Show resolved Hide resolved

// 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
Loading