Add support for CertDecodeFlags to DecodeChipCert (#30013)
* Add support for CertDecodeFlags to DecodeChipCert
This moves support for generating the TBSHash during decoding from
ChipCertificateSet::LoadCert to DecodeChipCert. LoadCert can now delegate
to DecodeChipCert and only contains a few extra checks to retain existing
behavior.
* Add ASN1Writer.IsNullWriter() and use it internally
* Avoid conversion work in DecodeConvertECDSASignature if using a null writer
The original LoadCert avoided this work by calling DecodeECDSASignature
directly, so this brings the refactored version back to baseline.
* Add test for DecodeChipCert with different options
diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp
index 8a651e7..425d30a 100644
--- a/src/credentials/CHIPCert.cpp
+++ b/src/credentials/CHIPCert.cpp
@@ -54,9 +54,6 @@
using namespace chip::Protocols;
using namespace chip::Crypto;
-extern CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData);
-extern CHIP_ERROR DecodeECDSASignature(TLVReader & reader, ChipCertificateData & certData);
-
ChipCertificateSet::ChipCertificateSet()
{
mCerts = nullptr;
@@ -137,76 +134,21 @@
TLVReader reader;
reader.Init(chipCert);
-
- ReturnErrorOnFailure(reader.Next(kTLVType_Structure, AnonymousTag()));
-
return LoadCert(reader, decodeFlags, chipCert);
}
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.
ChipCertificateData cert;
- cert.Clear();
+ ReturnErrorOnFailure(DecodeChipCert(reader, cert, decodeFlags));
- // Must be positioned on the structure element representing the certificate.
- VerifyOrReturnError(reader.GetType() == kTLVType_Structure, CHIP_ERROR_INVALID_ARGUMENT);
+ // Verify the cert has both the Subject Key Id and Authority Key Id extensions present.
+ // Only certs with both these extensions are supported for the purposes of certificate validation.
+ VerifyOrReturnError(cert.mCertFlags.HasAll(CertFlags::kExtPresent_SubjectKeyId, CertFlags::kExtPresent_AuthKeyId),
+ CHIP_ERROR_UNSUPPORTED_CERT_FORMAT);
- cert.mCertificate = chipCert;
-
- {
- TLVType containerType;
-
- // Enter the certificate structure...
- ReturnErrorOnFailure(reader.EnterContainer(containerType));
-
- // If requested to generate the TBSHash.
- if (decodeFlags.Has(CertDecodeFlags::kGenerateTBSHash))
- {
- chip::Platform::ScopedMemoryBuffer<uint8_t> asn1TBSBuf;
- ReturnErrorCodeIf(!asn1TBSBuf.Alloc(kMaxCHIPCertDecodeBufLength), CHIP_ERROR_NO_MEMORY);
-
- // Initialize an ASN1Writer and convert the TBS (to-be-signed) portion of the certificate to ASN.1 DER
- // encoding. At the same time, parse various components within the certificate and set the corresponding
- // fields in the CertificateData object.
- writer.Init(asn1TBSBuf.Get(), kMaxCHIPCertDecodeBufLength);
- ReturnErrorOnFailure(DecodeConvertTBSCert(reader, writer, cert));
-
- // Generate a SHA hash of the encoded TBS certificate.
- chip::Crypto::Hash_SHA256(asn1TBSBuf.Get(), writer.GetLengthWritten(), cert.mTBSHash);
-
- cert.mCertFlags.Set(CertFlags::kTBSHashPresent);
- }
- else
- {
- // Initialize an ASN1Writer as a NullWriter.
- writer.InitNullWriter();
- ReturnErrorOnFailure(DecodeConvertTBSCert(reader, writer, cert));
- }
-
- // Verify the cert has both the Subject Key Id and Authority Key Id extensions present.
- // Only certs with both these extensions are supported for the purposes of certificate validation.
- VerifyOrReturnError(cert.mCertFlags.HasAll(CertFlags::kExtPresent_SubjectKeyId, CertFlags::kExtPresent_AuthKeyId),
- CHIP_ERROR_UNSUPPORTED_CERT_FORMAT);
-
- // Verify the cert was signed with ECDSA-SHA256. This is the only signature algorithm currently supported.
- VerifyOrReturnError(cert.mSigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, CHIP_ERROR_UNSUPPORTED_SIGNATURE_TYPE);
-
- // Decode the certificate's signature...
- ReturnErrorOnFailure(DecodeECDSASignature(reader, cert));
-
- // Verify no more elements in the certificate.
- ReturnErrorOnFailure(reader.VerifyEndOfContainer());
-
- ReturnErrorOnFailure(reader.ExitContainer(containerType));
- }
-
- // If requested by the caller, mark the certificate as trusted.
- if (decodeFlags.Has(CertDecodeFlags::kIsTrustAnchor))
- {
- cert.mCertFlags.Set(CertFlags::kIsTrustAnchor);
- }
+ // Verify the cert was signed with ECDSA-SHA256. This is the only signature algorithm currently supported.
+ VerifyOrReturnError(cert.mSigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, CHIP_ERROR_UNSUPPORTED_SIGNATURE_TYPE);
// Check if this cert matches any currently loaded certificates
for (uint32_t i = 0; i < mCertCount; i++)
diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h
index caa47ba..69041f0 100644
--- a/src/credentials/CHIPCert.h
+++ b/src/credentials/CHIPCert.h
@@ -458,7 +458,7 @@
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
-CHIP_ERROR DecodeChipCert(const ByteSpan chipCert, ChipCertificateData & certData);
+CHIP_ERROR DecodeChipCert(const ByteSpan chipCert, ChipCertificateData & certData, BitFlags<CertDecodeFlags> decodeFlags = {});
/**
* @brief Decode CHIP certificate.
@@ -470,7 +470,8 @@
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
-CHIP_ERROR DecodeChipCert(chip::TLV::TLVReader & reader, ChipCertificateData & certData);
+CHIP_ERROR DecodeChipCert(chip::TLV::TLVReader & reader, ChipCertificateData & certData,
+ BitFlags<CertDecodeFlags> decodeFlags = {});
/**
* @brief Decode CHIP Distinguished Name (DN).
diff --git a/src/credentials/CHIPCertToX509.cpp b/src/credentials/CHIPCertToX509.cpp
index 85e7514..228aaf2 100644
--- a/src/credentials/CHIPCertToX509.cpp
+++ b/src/credentials/CHIPCertToX509.cpp
@@ -454,7 +454,7 @@
return err;
}
-CHIP_ERROR DecodeECDSASignature(TLVReader & reader, ChipCertificateData & certData)
+static CHIP_ERROR DecodeECDSASignature(TLVReader & reader, ChipCertificateData & certData)
{
ReturnErrorOnFailure(reader.Next(kTLVType_ByteString, ContextTag(kTag_ECDSASignature)));
ReturnErrorOnFailure(reader.Get(certData.mSignature));
@@ -467,6 +467,9 @@
ReturnErrorOnFailure(DecodeECDSASignature(reader, certData));
+ // Converting the signature is a bit of work, so explicitly check if we have a null writer
+ ReturnErrorCodeIf(writer.IsNullWriter(), CHIP_NO_ERROR);
+
// signatureValue BIT STRING
// Per RFC3279, the ECDSA signature value is encoded in DER encapsulated in the signatureValue BIT STRING.
ASN1_START_BIT_STRING_ENCAPSULATED
@@ -492,7 +495,7 @@
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
-CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData)
+static CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData)
{
CHIP_ERROR err;
@@ -550,7 +553,18 @@
return err;
}
-static CHIP_ERROR DecodeConvertCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData)
+/**
+ * Decode a CHIP TLV certificate and convert it to X.509 DER form.
+ *
+ * This helper function takes separate ASN1Writers for the whole Certificate
+ * and the TBSCertificate, to allow the caller to control which part (if any)
+ * to capture.
+ *
+ * If `writer` is NOT a null writer, then `tbsWriter` MUST be a reference
+ * to the same writer, otherwise the overall Certificate written will not be
+ * valid.
+ */
+static CHIP_ERROR DecodeConvertCert(TLVReader & reader, ASN1Writer & writer, ASN1Writer & tbsWriter, ChipCertificateData & certData)
{
CHIP_ERROR err;
TLVType containerType;
@@ -568,7 +582,7 @@
ASN1_START_SEQUENCE
{
// tbsCertificate TBSCertificate,
- ReturnErrorOnFailure(DecodeConvertTBSCert(reader, writer, certData));
+ ReturnErrorOnFailure(DecodeConvertTBSCert(reader, tbsWriter, certData));
// signatureAlgorithm AlgorithmIdentifier
// AlgorithmIdentifier ::= SEQUENCE
@@ -603,31 +617,56 @@
certData.Clear();
- ReturnErrorOnFailure(DecodeConvertCert(reader, writer, certData));
+ ReturnErrorOnFailure(DecodeConvertCert(reader, writer, writer, certData));
x509Cert.reduce_size(writer.GetLengthWritten());
return CHIP_NO_ERROR;
}
-CHIP_ERROR DecodeChipCert(const ByteSpan chipCert, ChipCertificateData & certData)
+CHIP_ERROR DecodeChipCert(const ByteSpan chipCert, ChipCertificateData & certData, BitFlags<CertDecodeFlags> decodeFlags)
{
TLVReader reader;
reader.Init(chipCert);
-
- return DecodeChipCert(reader, certData);
+ return DecodeChipCert(reader, certData, decodeFlags);
}
-CHIP_ERROR DecodeChipCert(TLVReader & reader, ChipCertificateData & certData)
+CHIP_ERROR DecodeChipCert(TLVReader & reader, ChipCertificateData & certData, BitFlags<CertDecodeFlags> decodeFlags)
{
- ASN1Writer writer;
-
- writer.InitNullWriter();
+ ASN1Writer nullWriter;
+ nullWriter.InitNullWriter();
certData.Clear();
- return DecodeConvertCert(reader, writer, certData);
+ if (decodeFlags.Has(CertDecodeFlags::kGenerateTBSHash))
+ {
+ // Create a buffer and writer to capture the TBS (to-be-signed) portion of the certificate
+ // when we decode (and convert) the certificate, so we can hash it to create the TBSHash.
+ chip::Platform::ScopedMemoryBuffer<uint8_t> asn1TBSBuf;
+ VerifyOrReturnError(asn1TBSBuf.Alloc(kMaxCHIPCertDecodeBufLength), CHIP_ERROR_NO_MEMORY);
+ ASN1Writer tbsWriter;
+ tbsWriter.Init(asn1TBSBuf.Get(), kMaxCHIPCertDecodeBufLength);
+
+ ReturnErrorOnFailure(DecodeConvertCert(reader, nullWriter, tbsWriter, certData));
+
+ // 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);
+ certData.mCertFlags.Set(CertFlags::kTBSHashPresent);
+ }
+ else
+ {
+ ReturnErrorOnFailure(DecodeConvertCert(reader, nullWriter, nullWriter, certData));
+ }
+
+ // If requested by the caller, mark the certificate as trusted.
+ if (decodeFlags.Has(CertDecodeFlags::kIsTrustAnchor))
+ {
+ certData.mCertFlags.Set(CertFlags::kIsTrustAnchor);
+ }
+
+ return CHIP_NO_ERROR;
}
CHIP_ERROR DecodeChipDN(TLVReader & reader, ChipDN & dn)
diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp
index 1a8da83..ff79157 100644
--- a/src/credentials/tests/TestChipCert.cpp
+++ b/src/credentials/tests/TestChipCert.cpp
@@ -1275,6 +1275,34 @@
}
}
+static void TestChipCert_DecodingOptions(nlTestSuite * inSuite, void * inContext)
+{
+ CHIP_ERROR err;
+ ByteSpan cert;
+ ChipCertificateData certData;
+
+ err = GetTestCert(TestCert::kRoot01, sNullLoadFlag, cert);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+
+ // Decode with default (null) options
+ err = DecodeChipCert(cert, certData);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, !certData.mCertFlags.Has(CertFlags::kIsTrustAnchor));
+
+ // Decode as trust anchor
+ err = DecodeChipCert(cert, certData, CertDecodeFlags::kIsTrustAnchor);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, certData.mCertFlags.Has(CertFlags::kIsTrustAnchor));
+
+ // Decode with TBS Hash calculation
+ err = DecodeChipCert(cert, certData, CertDecodeFlags::kGenerateTBSHash);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, certData.mCertFlags.Has(CertFlags::kTBSHashPresent));
+ // When the TBS hash is available signature verification should work
+ err = VerifyCertSignature(certData, certData); // test cert is a self-signed root
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+}
+
static void TestChipCert_LoadDuplicateCerts(nlTestSuite * inSuite, void * inContext)
{
CHIP_ERROR err;
@@ -2147,6 +2175,7 @@
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 CHIP Certificate Decoding Options", TestChipCert_DecodingOptions),
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/lib/asn1/ASN1.h b/src/lib/asn1/ASN1.h
index fd7c518..1ba3166 100644
--- a/src/lib/asn1/ASN1.h
+++ b/src/lib/asn1/ASN1.h
@@ -210,6 +210,8 @@
void InitNullWriter(void);
size_t GetLengthWritten(void) const;
+ bool IsNullWriter() const { return mBuf == nullptr; }
+
CHIP_ERROR PutInteger(int64_t val);
CHIP_ERROR PutBoolean(bool val);
CHIP_ERROR PutObjectId(const uint8_t * val, uint16_t valLen);
diff --git a/src/lib/asn1/ASN1Writer.cpp b/src/lib/asn1/ASN1Writer.cpp
index 09b73d4..643d790 100644
--- a/src/lib/asn1/ASN1Writer.cpp
+++ b/src/lib/asn1/ASN1Writer.cpp
@@ -75,6 +75,8 @@
CHIP_ERROR ASN1Writer::PutInteger(int64_t val)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
uint8_t encodedVal[sizeof(int64_t)];
uint8_t valStart, valLen;
@@ -95,8 +97,7 @@
CHIP_ERROR ASN1Writer::PutBoolean(bool val)
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
ReturnErrorOnFailure(EncodeHead(kASN1TagClass_Universal, kASN1UniversalTag_Boolean, false, 1));
@@ -172,11 +173,9 @@
CHIP_ERROR ASN1Writer::PutBitString(uint32_t val)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
uint8_t len;
-
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
-
if (val == 0)
len = 1;
else if (val < 256)
@@ -222,8 +221,7 @@
CHIP_ERROR ASN1Writer::PutBitString(uint8_t unusedBitCount, const uint8_t * encodedBits, uint16_t encodedBitsLen)
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
ReturnErrorOnFailure(EncodeHead(kASN1TagClass_Universal, kASN1UniversalTag_BitString, false, encodedBitsLen + 1));
@@ -236,11 +234,9 @@
CHIP_ERROR ASN1Writer::PutBitString(uint8_t unusedBitCount, chip::TLV::TLVReader & tlvReader)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
ByteSpan encodedBits;
-
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
-
ReturnErrorOnFailure(tlvReader.Get(encodedBits));
VerifyOrReturnError(CanCastTo<int32_t>(encodedBits.size() + 1), ASN1_ERROR_LENGTH_OVERFLOW);
@@ -257,6 +253,8 @@
CHIP_ERROR ASN1Writer::PutTime(const ASN1UniversalTime & val)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
char buf[ASN1UniversalTime::kASN1TimeStringMaxLength];
MutableCharSpan bufSpan(buf);
uint8_t tag;
@@ -281,8 +279,7 @@
CHIP_ERROR ASN1Writer::PutConstructedType(const uint8_t * val, uint16_t valLen)
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
// Make sure we have enough space to write
VerifyOrReturnError((mWritePoint + valLen) <= mBufEnd, ASN1_ERROR_OVERFLOW);
@@ -304,8 +301,7 @@
CHIP_ERROR ASN1Writer::StartEncapsulatedType(uint8_t cls, uint8_t tag, bool bitStringEncoding)
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
ReturnErrorOnFailure(EncodeHead(cls, tag, false, kUnknownLength));
@@ -328,8 +324,7 @@
CHIP_ERROR ASN1Writer::PutValue(uint8_t cls, uint8_t tag, bool isConstructed, const uint8_t * val, uint16_t valLen)
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
ReturnErrorOnFailure(EncodeHead(cls, tag, isConstructed, valLen));
@@ -340,11 +335,9 @@
CHIP_ERROR ASN1Writer::PutValue(uint8_t cls, uint8_t tag, bool isConstructed, chip::TLV::TLVReader & tlvReader)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
ByteSpan val;
-
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
-
ReturnErrorOnFailure(tlvReader.Get(val));
VerifyOrReturnError(CanCastTo<int32_t>(val.size()), ASN1_ERROR_LENGTH_OVERFLOW);
@@ -358,12 +351,11 @@
CHIP_ERROR ASN1Writer::EncodeHead(uint8_t cls, uint8_t tag, bool isConstructed, int32_t len)
{
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
+
uint8_t bytesForLen;
uint32_t totalLen;
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
-
// Only tags < 31 supported. The implication of this is that encoded tags are exactly 1 byte long.
VerifyOrReturnError(tag < 0x1F, ASN1_ERROR_UNSUPPORTED_ENCODING);
@@ -410,8 +402,7 @@
CHIP_ERROR ASN1Writer::WriteDeferredLength()
{
- // Do nothing for a null writer.
- VerifyOrReturnError(mBuf != nullptr, CHIP_NO_ERROR);
+ ReturnErrorCodeIf(IsNullWriter(), CHIP_NO_ERROR);
VerifyOrReturnError(mDeferredLengthCount > 0, ASN1_ERROR_INVALID_STATE);