From 3257674855d9585c8afbaf4d909d3e50765d4931 Mon Sep 17 00:00:00 2001 From: Evgeny Margolis Date: Mon, 28 Mar 2022 13:12:38 -0700 Subject: [PATCH] Updated OpCert Generation in the Operational Credentials Issuer. (#16261) Problem: In the current implementation when ICAC and RCAC are found in the storage the Subject DN for these certificates is generated separately. This might be an issue if after software update the way Subject DN is generated is changed. Fix: The proper implementation is to extract Subject DN from ICAC and RCAC if they are found in the device memory. --- .../ExampleOperationalCredentialsIssuer.cpp | 70 +++---- .../AndroidOperationalCredentialsIssuer.cpp | 41 ++-- src/credentials/CHIPCert.cpp | 180 ++++++++++++++++++ src/credentials/CHIPCert.h | 32 +++- src/credentials/CHIPCertFromX509.cpp | 145 +------------- src/credentials/tests/TestChipCert.cpp | 94 +++++++++ .../CHIP/CHIPOperationalCredentialsDelegate.h | 2 +- .../CHIPOperationalCredentialsDelegate.mm | 59 +++--- src/lib/asn1/ASN1Macros.h | 10 + 9 files changed, 409 insertions(+), 224 deletions(-) diff --git a/src/controller/ExampleOperationalCredentialsIssuer.cpp b/src/controller/ExampleOperationalCredentialsIssuer.cpp index 97b704be471dbc..e0884d12cc0eac 100644 --- a/src/controller/ExampleOperationalCredentialsIssuer.cpp +++ b/src/controller/ExampleOperationalCredentialsIssuer.cpp @@ -116,61 +116,63 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation( MutableByteSpan & rcac, MutableByteSpan & icac, MutableByteSpan & noc) { - ChipDN noc_dn; - // TODO: Is there a way to make this code less error-prone for consumers? - // The consumer doesn't need to know the exact OID value. - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); - ReturnErrorOnFailure(noc_dn.AddCATs(cats)); - ChipDN icac_dn; - ReturnErrorOnFailure(icac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, mIntermediateIssuerId)); ChipDN rcac_dn; - ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); + CHIP_ERROR err = CHIP_NO_ERROR; + uint16_t rcacBufLen = static_cast(std::min(rcac.size(), static_cast(UINT16_MAX))); + PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key, + err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen)); + if (err == CHIP_NO_ERROR) + { + // Found root certificate in the storage. + rcac.reduce_size(rcacBufLen); + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn)); + } + // If root certificate not found in the storage, generate new root certificate. + else + { + ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); - ChipLogProgress(Controller, "Generating NOC"); - X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn }; - ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc)); + ChipLogProgress(Controller, "Generating RCAC"); + X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn }; + ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac)); + + VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); + PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key, + ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size())))); + } + ChipDN icac_dn; uint16_t icacBufLen = static_cast(std::min(icac.size(), static_cast(UINT16_MAX))); - CHIP_ERROR err = CHIP_NO_ERROR; PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key, err = mStorage->SyncGetKeyValue(key, icac.data(), icacBufLen)); if (err == CHIP_NO_ERROR) { - // Found root certificate in the storage. + // Found intermediate certificate in the storage. icac.reduce_size(icacBufLen); + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(icac, icac_dn)); } + // If intermediate certificate not found in the storage, generate new intermediate certificate. else { + ReturnErrorOnFailure(icac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, mIntermediateIssuerId)); + ChipLogProgress(Controller, "Generating ICAC"); X509CertRequestParams icac_request = { 0, mNow, mNow + mValidity, icac_dn, rcac_dn }; ReturnErrorOnFailure(NewICAX509Cert(icac_request, mIntermediateIssuer.Pubkey(), mIssuer, icac)); VerifyOrReturnError(CanCastTo(icac.size()), CHIP_ERROR_INTERNAL); PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key, - err = mStorage->SyncSetKeyValue(key, icac.data(), static_cast(icac.size()))); + ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, icac.data(), static_cast(icac.size())))); } - uint16_t rcacBufLen = static_cast(std::min(rcac.size(), static_cast(UINT16_MAX))); - PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key, - err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen)); - if (err == CHIP_NO_ERROR) - { - // Found root certificate in the storage. - rcac.reduce_size(rcacBufLen); - } - else - { - ChipLogProgress(Controller, "Generating RCAC"); - X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn }; - ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac)); - - VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); - PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key, - err = mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size()))); - } + ChipDN noc_dn; + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); + ReturnErrorOnFailure(noc_dn.AddCATs(cats)); - return err; + ChipLogProgress(Controller, "Generating NOC"); + X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn }; + return NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc); } CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements, diff --git a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp index 2555acfabd0f4c..6e14f5a72729b8 100644 --- a/src/controller/java/AndroidOperationalCredentialsIssuer.cpp +++ b/src/controller/java/AndroidOperationalCredentialsIssuer.cpp @@ -82,18 +82,7 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChainAfterValidation( MutableByteSpan & rcac, MutableByteSpan & icac, MutableByteSpan & noc) { - ChipDN noc_dn; - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); - ReturnErrorOnFailure(noc_dn.AddCATs(cats)); ChipDN rcac_dn; - ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); - - ChipLogProgress(Controller, "Generating NOC"); - chip::Credentials::X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, rcac_dn }; - ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, mIssuer, noc)); - icac.reduce_size(0); - uint16_t rcacBufLen = static_cast(std::min(rcac.size(), static_cast(UINT16_MAX))); CHIP_ERROR err = CHIP_NO_ERROR; PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, @@ -102,18 +91,32 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChainAfterValidation( { // Found root certificate in the storage. rcac.reduce_size(rcacBufLen); - return CHIP_NO_ERROR; + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn)); + } + // If root certificate not found in the storage, generate new root certificate. + else + { + ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); + + ChipLogProgress(Controller, "Generating RCAC"); + chip::Credentials::X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn }; + ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac)); + + VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); + PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, + ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size())))); } - ChipLogProgress(Controller, "Generating RCAC"); - chip::Credentials::X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn }; - ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac)); + icac.reduce_size(0); - VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); - PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, - err = mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size()))); + ChipDN noc_dn; + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); + ReturnErrorOnFailure(noc_dn.AddCATs(cats)); - return err; + ChipLogProgress(Controller, "Generating NOC"); + chip::Credentials::X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, rcac_dn }; + return NewNodeOperationalX509Cert(noc_request, pubkey, mIssuer, noc); } CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements, diff --git a/src/credentials/CHIPCert.cpp b/src/credentials/CHIPCert.cpp index d053bc4e087d52..0ab3b25cb2b8a1 100644 --- a/src/credentials/CHIPCert.cpp +++ b/src/credentials/CHIPCert.cpp @@ -710,6 +710,36 @@ CHIP_ERROR ChipDN::GetCertFabricId(uint64_t & fabricId) const return CHIP_NO_ERROR; } +CHIP_ERROR ChipDN::EncodeToTLV(TLVWriter & writer, Tag tag) const +{ + TLVType outerContainer; + uint8_t rdnCount = RDNCount(); + + ReturnErrorOnFailure(writer.StartContainer(tag, kTLVType_List, outerContainer)); + + for (uint8_t i = 0; i < rdnCount; i++) + { + // Derive the TLV tag number from the enum value assigned to the attribute type OID. For attributes that can be + // either UTF8String or PrintableString, use the high bit in the tag number to distinguish the two. + uint8_t tlvTagNum = GetOIDEnum(rdn[i].mAttrOID); + if (rdn[i].mAttrIsPrintableString) + { + tlvTagNum |= 0x80; + } + + if (IsChipDNAttr(rdn[i].mAttrOID)) + { + ReturnErrorOnFailure(writer.Put(ContextTag(tlvTagNum), rdn[i].mChipVal)); + } + else + { + ReturnErrorOnFailure(writer.PutString(ContextTag(tlvTagNum), rdn[i].mString)); + } + } + + return writer.EndContainer(outerContainer); +} + CHIP_ERROR ChipDN::DecodeFromTLV(TLVReader & reader) { CHIP_ERROR err; @@ -860,6 +890,100 @@ CHIP_ERROR ChipDN::EncodeToASN1(ASN1Writer & writer) const return err; } +CHIP_ERROR ChipDN::DecodeFromASN1(ASN1Reader & reader) +{ + CHIP_ERROR err; + + // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + ASN1_PARSE_ENTER_SEQUENCE + { + while ((err = reader.Next()) == CHIP_NO_ERROR) + { + // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue + ASN1_ENTER_SET + { + // AttributeTypeAndValue ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + // type AttributeType + // AttributeType ::= OBJECT IDENTIFIER + OID attrOID; + ASN1_PARSE_OBJECT_ID(attrOID); + VerifyOrReturnError(GetOIDCategory(attrOID) == kOIDCategory_AttributeType, ASN1_ERROR_INVALID_ENCODING); + + // AttributeValue ::= ANY -- DEFINED BY AttributeType + ASN1_PARSE_ANY; + + uint8_t attrTag = reader.GetTag(); + + // Can only support UTF8String, PrintableString and IA5String. + VerifyOrReturnError(reader.GetClass() == kASN1TagClass_Universal && + (attrTag == kASN1UniversalTag_PrintableString || + attrTag == kASN1UniversalTag_UTF8String || attrTag == kASN1UniversalTag_IA5String), + ASN1_ERROR_UNSUPPORTED_ENCODING); + + // CHIP attributes must be UTF8Strings. + if (IsChipDNAttr(attrOID)) + { + VerifyOrReturnError(attrTag == kASN1UniversalTag_UTF8String, ASN1_ERROR_INVALID_ENCODING); + } + + // If 64-bit CHIP attribute. + if (IsChip64bitDNAttr(attrOID)) + { + uint64_t chipAttr; + VerifyOrReturnError(Encoding::UppercaseHexToUint64(reinterpret_cast(reader.GetValue()), + static_cast(reader.GetValueLen()), + chipAttr) == sizeof(uint64_t), + ASN1_ERROR_INVALID_ENCODING); + + if (attrOID == chip::ASN1::kOID_AttributeType_ChipNodeId) + { + VerifyOrReturnError(IsOperationalNodeId(chipAttr), CHIP_ERROR_WRONG_CERT_DN); + } + else if (attrOID == chip::ASN1::kOID_AttributeType_ChipFabricId) + { + VerifyOrReturnError(IsValidFabricId(chipAttr), CHIP_ERROR_WRONG_CERT_DN); + } + + ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr)); + } + // If 32-bit CHIP attribute. + else if (IsChip32bitDNAttr(attrOID)) + { + CASEAuthTag chipAttr; + VerifyOrReturnError(Encoding::UppercaseHexToUint32(reinterpret_cast(reader.GetValue()), + reader.GetValueLen(), chipAttr) == sizeof(CASEAuthTag), + ASN1_ERROR_INVALID_ENCODING); + + VerifyOrReturnError(IsValidCASEAuthTag(chipAttr), CHIP_ERROR_WRONG_CERT_DN); + + ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr)); + } + // Otherwise, it is a string. + else + { + ReturnErrorOnFailure(AddAttribute(attrOID, + CharSpan(Uint8::to_const_char(reader.GetValue()), reader.GetValueLen()), + attrTag == kASN1UniversalTag_PrintableString)); + } + } + ASN1_EXIT_SEQUENCE; + + // Only one AttributeTypeAndValue allowed per RDN. + err = reader.Next(); + ReturnErrorCodeIf(err == CHIP_NO_ERROR, ASN1_ERROR_UNSUPPORTED_ENCODING); + VerifyOrReturnError(err == ASN1_END, err); + } + ASN1_EXIT_SET; + } + } + ASN1_EXIT_SEQUENCE; + +exit: + return err; +} + bool ChipDN::IsEqual(const ChipDN & other) const { bool res = true; @@ -1159,5 +1283,61 @@ CHIP_ERROR ExtractSKIDFromChipCert(const ByteSpan & chipCert, CertificateKeyId & return CHIP_NO_ERROR; } +CHIP_ERROR ExtractSubjectDNFromChipCert(const ByteSpan & chipCert, ChipDN & dn) +{ + ChipCertificateSet certSet; + ChipCertificateData certData; + + ReturnErrorOnFailure(certSet.Init(&certData, 1)); + + ReturnErrorOnFailure(certSet.LoadCert(chipCert, BitFlags())); + + dn = certData.mSubjectDN; + + return CHIP_NO_ERROR; +} + +CHIP_ERROR ExtractSubjectDNFromX509Cert(const ByteSpan & x509Cert, ChipDN & dn) +{ + CHIP_ERROR err; + ASN1Reader reader; + + VerifyOrReturnError(CanCastTo(x509Cert.size()), CHIP_ERROR_INVALID_ARGUMENT); + + reader.Init(x509Cert); + + // Certificate ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + // tbsCertificate TBSCertificate, + // TBSCertificate ::= SEQUENCE + ASN1_PARSE_ENTER_SEQUENCE + { + // Skip version [0] EXPLICIT Version DEFAULT v1 + ASN1_PARSE_ELEMENT(kASN1TagClass_ContextSpecific, 0); + + // Skip serialNumber CertificateSerialNumber + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Integer); + + // Skip signature AlgorithmIdentifier + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence); + + // Skip issuer Name + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence); + + // Skip validity Validity, + ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence); + + // Decode subject Name, + ReturnErrorOnFailure(dn.DecodeFromASN1(reader)); + } + ASN1_SKIP_AND_EXIT_SEQUENCE; + } + ASN1_SKIP_AND_EXIT_SEQUENCE; + +exit: + return err; +} + } // namespace Credentials } // namespace chip diff --git a/src/credentials/CHIPCert.h b/src/credentials/CHIPCert.h index 872a6e59f9262d..27e4380f886937 100644 --- a/src/credentials/CHIPCert.h +++ b/src/credentials/CHIPCert.h @@ -276,6 +276,11 @@ class ChipDN **/ CHIP_ERROR GetCertFabricId(uint64_t & fabricId) const; + /** + * @brief Encode ChipDN attributes in TLV form. + **/ + CHIP_ERROR EncodeToTLV(chip::TLV::TLVWriter & writer, TLV::Tag tag) const; + /** * @brief Decode ChipDN attributes from TLV encoded format. * @@ -288,6 +293,13 @@ class ChipDN **/ CHIP_ERROR EncodeToASN1(ASN1::ASN1Writer & writer) const; + /** + * @brief Decode ChipDN attributes from ASN1 encoded format. + * + * @param reader A ASN1Reader positioned at the ChipDN ASN1 list. + **/ + CHIP_ERROR DecodeFromASN1(ASN1::ASN1Reader & reader); + bool IsEqual(const ChipDN & other) const; /** @@ -868,7 +880,7 @@ CHIP_ERROR ExtractNodeIdFabricIdFromOpCert(const ByteSpan & opcert, NodeId * nod /** * Extract Public Key from a chip certificate in ByteSpan TLV-encoded form. * This does not perform any sort of validation on the certificate structure - * structure than parsing it. + * other than parsing it. * * Can return any error that can be returned from parsing the cert. */ @@ -877,11 +889,27 @@ CHIP_ERROR ExtractPublicKeyFromChipCert(const ByteSpan & chipCert, P256PublicKey /** * Extract Subject Key Identifier from a chip certificate in ByteSpan TLV-encoded form. * This does not perform any sort of validation on the certificate structure - * structure than parsing it. + * other than parsing it. * * Can return any error that can be returned from parsing the cert. */ CHIP_ERROR ExtractSKIDFromChipCert(const ByteSpan & chipCert, CertificateKeyId & skid); +/** + * Extract Subject Distinguished Name (DN) from a chip certificate in ByteSpan TLV-encoded form. + * It is required that the certificate in the chipCert buffer stays valid while the `dn` output is used. + * + * Can return any error that can be returned from parsing the cert. + */ +CHIP_ERROR ExtractSubjectDNFromChipCert(const ByteSpan & chipCert, ChipDN & dn); + +/** + * Extract Subject Distinguished Name (DN) from a chip certificate in ByteSpan X509 DER-encoded form. + * It is required that the certificate in the chipCert buffer stays valid while the `dn` output is used. + * + * Can return any error that can be returned from converting and parsing the cert. + */ +CHIP_ERROR ExtractSubjectDNFromX509Cert(const ByteSpan & x509Cert, ChipDN & dn); + } // namespace Credentials } // namespace chip diff --git a/src/credentials/CHIPCertFromX509.cpp b/src/credentials/CHIPCertFromX509.cpp index 31c64e3ef7c36c..dc8e315b3abd45 100644 --- a/src/credentials/CHIPCertFromX509.cpp +++ b/src/credentials/CHIPCertFromX509.cpp @@ -50,136 +50,11 @@ using namespace chip::TLV; using namespace chip::Protocols; using namespace chip::Crypto; -static CHIP_ERROR ConvertDistinguishedName(ASN1Reader & reader, TLVWriter & writer, Tag tag, uint64_t & subjectOrIssuer, - Optional & fabric) +static CHIP_ERROR ConvertDistinguishedName(ASN1Reader & reader, TLVWriter & writer, Tag tag) { - CHIP_ERROR err; - TLVType outerContainer; - OID attrOID; - - err = writer.StartContainer(tag, kTLVType_List, outerContainer); - SuccessOrExit(err); - - // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName - ASN1_PARSE_ENTER_SEQUENCE - { - while ((err = reader.Next()) == CHIP_NO_ERROR) - { - // RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue - ASN1_ENTER_SET - { - // AttributeTypeAndValue ::= SEQUENCE - ASN1_PARSE_ENTER_SEQUENCE - { - // type AttributeType - // AttributeType ::= OBJECT IDENTIFIER - ASN1_PARSE_OBJECT_ID(attrOID); - VerifyOrExit(GetOIDCategory(attrOID) == kOIDCategory_AttributeType, err = ASN1_ERROR_INVALID_ENCODING); - - // AttributeValue ::= ANY -- DEFINED BY AttributeType - ASN1_PARSE_ANY; - - // Can only support UTF8String, PrintableString and IA5String. - VerifyOrExit(reader.GetClass() == kASN1TagClass_Universal && - (reader.GetTag() == kASN1UniversalTag_PrintableString || - reader.GetTag() == kASN1UniversalTag_UTF8String || - reader.GetTag() == kASN1UniversalTag_IA5String), - err = ASN1_ERROR_UNSUPPORTED_ENCODING); - - // CHIP attributes must be UTF8Strings. - if (IsChipDNAttr(attrOID)) - { - VerifyOrExit(reader.GetTag() == kASN1UniversalTag_UTF8String, err = ASN1_ERROR_INVALID_ENCODING); - } - - // Derive the TLV tag number from the enum value assigned to the attribute type OID. For attributes that can be - // either UTF8String or PrintableString, use the high bit in the tag number to distinguish the two. - uint8_t tlvTagNum = GetOIDEnum(attrOID); - if (reader.GetTag() == kASN1UniversalTag_PrintableString) - { - tlvTagNum |= 0x80; - } - - // If 64-bit CHIP attribute. - if (IsChip64bitDNAttr(attrOID)) - { - uint64_t chipAttr; - VerifyOrReturnError(Encoding::UppercaseHexToUint64(reinterpret_cast(reader.GetValue()), - static_cast(reader.GetValueLen()), - chipAttr) == sizeof(uint64_t), - err = ASN1_ERROR_INVALID_ENCODING); - - // Certificates use a combination of OIDs for Issuer and Subject. - // NOC: Issuer = kOID_AttributeType_ChipRootId or kOID_AttributeType_ChipICAId - // Subject = kOID_AttributeType_ChipNodeId - // ICA: Issuer = kOID_AttributeType_ChipRootId - // Subject = kOID_AttributeType_ChipICAId - // Root: Issuer = kOID_AttributeType_ChipRootId - // Subject = kOID_AttributeType_ChipRootId - // - // This function is called first for the Issuer DN, and later for Subject DN. - // Since the caller knows if Issuer or Subject DN is being parsed, it's left up to - // the caller to use the returned value (subjectOrIssuer) appropriately. - if (attrOID == chip::ASN1::kOID_AttributeType_ChipNodeId || - attrOID == chip::ASN1::kOID_AttributeType_ChipICAId || - attrOID == chip::ASN1::kOID_AttributeType_ChipRootId) - { - if (attrOID == chip::ASN1::kOID_AttributeType_ChipNodeId) - { - VerifyOrReturnError(IsOperationalNodeId(chipAttr), CHIP_ERROR_WRONG_CERT_DN); - } - subjectOrIssuer = chipAttr; - } - else if (attrOID == chip::ASN1::kOID_AttributeType_ChipFabricId) - { - VerifyOrReturnError(IsValidFabricId(chipAttr), CHIP_ERROR_WRONG_CERT_DN); - fabric.SetValue(chipAttr); - } - - ReturnErrorOnFailure(writer.Put(ContextTag(tlvTagNum), chipAttr)); - } - // If 32-bit CHIP attribute. - else if (IsChip32bitDNAttr(attrOID)) - { - CASEAuthTag chipAttr; - VerifyOrReturnError(Encoding::UppercaseHexToUint32(reinterpret_cast(reader.GetValue()), - reader.GetValueLen(), chipAttr) == sizeof(CASEAuthTag), - err = ASN1_ERROR_INVALID_ENCODING); - - VerifyOrReturnError(IsValidCASEAuthTag(chipAttr), CHIP_ERROR_WRONG_CERT_DN); - - ReturnErrorOnFailure(writer.Put(ContextTag(tlvTagNum), chipAttr)); - } - // Otherwise, it is a string. - else - { - ReturnErrorOnFailure( - writer.PutString(ContextTag(tlvTagNum), Uint8::to_const_char(reader.GetValue()), reader.GetValueLen())); - } - } - ASN1_EXIT_SEQUENCE; - - // Only one AttributeTypeAndValue allowed per RDN. - err = reader.Next(); - if (err == CHIP_NO_ERROR) - { - ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); - } - if (err != ASN1_END) - { - ExitNow(); - } - } - ASN1_EXIT_SET; - } - } - ASN1_EXIT_SEQUENCE; - - err = writer.EndContainer(outerContainer); - SuccessOrExit(err); - -exit: - return err; + ChipDN dn; + ReturnErrorOnFailure(dn.DecodeFromASN1(reader)); + return dn.EncodeToTLV(writer, tag); } static CHIP_ERROR ConvertValidity(ASN1Reader & reader, TLVWriter & writer) @@ -553,8 +428,7 @@ CHIP_ERROR ConvertECDSASignatureDERToRaw(ASN1Reader & reader, TLVWriter & writer return err; } -static CHIP_ERROR ConvertCertificate(ASN1Reader & reader, TLVWriter & writer, Tag tag, uint64_t & issuer, uint64_t & subject, - Optional & fabric) +static CHIP_ERROR ConvertCertificate(ASN1Reader & reader, TLVWriter & writer, Tag tag) { CHIP_ERROR err; int64_t version; @@ -603,7 +477,7 @@ static CHIP_ERROR ConvertCertificate(ASN1Reader & reader, TLVWriter & writer, Ta ASN1_EXIT_SEQUENCE; // issuer Name - err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Issuer), issuer, fabric); + err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Issuer)); SuccessOrExit(err); // validity Validity, @@ -611,7 +485,7 @@ static CHIP_ERROR ConvertCertificate(ASN1Reader & reader, TLVWriter & writer, Ta SuccessOrExit(err); // subject Name, - err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Subject), subject, fabric); + err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Subject)); SuccessOrExit(err); err = ConvertSubjectPublicKeyInfo(reader, writer); @@ -686,9 +560,6 @@ CHIP_ERROR ConvertX509CertToChipCert(const ByteSpan x509Cert, MutableByteSpan & ASN1Reader reader; TLVWriter writer; - uint64_t issuer, subject; - Optional fabric; - VerifyOrReturnError(!x509Cert.empty(), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(CanCastTo(x509Cert.size()), CHIP_ERROR_INVALID_ARGUMENT); @@ -696,7 +567,7 @@ CHIP_ERROR ConvertX509CertToChipCert(const ByteSpan x509Cert, MutableByteSpan & writer.Init(chipCert); - ReturnErrorOnFailure(ConvertCertificate(reader, writer, AnonymousTag(), issuer, subject, fabric)); + ReturnErrorOnFailure(ConvertCertificate(reader, writer, AnonymousTag())); ReturnErrorOnFailure(writer.Finalize()); diff --git a/src/credentials/tests/TestChipCert.cpp b/src/credentials/tests/TestChipCert.cpp index d532e33cba72ac..0d0953d24a7ab7 100644 --- a/src/credentials/tests/TestChipCert.cpp +++ b/src/credentials/tests/TestChipCert.cpp @@ -1421,6 +1421,99 @@ static void TestChipCert_ExtractCATsFromOpCert(nlTestSuite * inSuite, void * inC } } +static void TestChipCert_ExtractSubjectDNFromChipCert(nlTestSuite * inSuite, void * inContext) +{ + struct TestCase + { + uint8_t Cert; + ChipDN ExpectedSubjectDN; + }; + + ChipDN expectedSubjectDN_Root01; + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Root01.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, 0xCACACACA00000001) == + CHIP_NO_ERROR); + + ChipDN expectedSubjectDN_Root02; + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Root02.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, 0xCACACACA00000002) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Root02.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, 0xFAB000000000001D) == + CHIP_NO_ERROR); + + ChipDN expectedSubjectDN_ICA02; + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_ICA02.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, 0xCACACACA00000004) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_ICA02.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, 0xFAB000000000001D) == + CHIP_NO_ERROR); + + ChipDN expectedSubjectDN_Node01_01; + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node01_01.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, 0xDEDEDEDE00010001) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node01_01.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, 0xFAB000000000001D) == + CHIP_NO_ERROR); + + const static char commonName_RDN[] = "TestCert02_03"; + + ChipDN expectedSubjectDN_Node02_03; + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node02_03.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, 0xDEDEDEDE00020003) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node02_03.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, 0xFAB000000000001D) == + CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node02_03.AddAttribute(chip::ASN1::kOID_AttributeType_CommonName, + CharSpan(commonName_RDN, strlen(commonName_RDN)), + false) == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, + expectedSubjectDN_Node02_03.AddAttribute(chip::ASN1::kOID_AttributeType_ChipCASEAuthenticatedTag, 0xABCD0001) == + CHIP_NO_ERROR); + + // clang-format off + TestCase sTestCases[] = { + // Cert SubjectDN + // ============================================================================ + { TestCert::kRoot01, expectedSubjectDN_Root01 }, + { TestCert::kRoot02, expectedSubjectDN_Root02 }, + { TestCert::kICA02, expectedSubjectDN_ICA02 }, + { TestCert::kNode01_01, expectedSubjectDN_Node01_01 }, + { TestCert::kNode02_03, expectedSubjectDN_Node02_03 }, + }; + // clang-format on + + // Test extraction from the raw ByteSpan form. + for (auto & testCase : sTestCases) + { + ByteSpan cert; + CHIP_ERROR err = GetTestCert(testCase.Cert, sNullLoadFlag, cert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ChipDN subjectDN; + err = ExtractSubjectDNFromChipCert(cert, subjectDN); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subjectDN.IsEqual(testCase.ExpectedSubjectDN)); + } + + // Test extraction from the X509 ByteSpan form. + for (auto & testCase : sTestCases) + { + ByteSpan cert; + CHIP_ERROR err = GetTestCert(testCase.Cert, TestCertLoadFlags::kDERForm, cert); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + + ChipDN subjectDN; + err = ExtractSubjectDNFromX509Cert(cert, subjectDN); + NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR); + NL_TEST_ASSERT(inSuite, subjectDN.IsEqual(testCase.ExpectedSubjectDN)); + } +} + static void TestChipCert_ExtractPublicKeyAndSKID(nlTestSuite * inSuite, void * inContext) { struct TestCase @@ -1518,6 +1611,7 @@ static const nlTest sTests[] = { NL_TEST_DEF("Test extracting Node ID and Fabric ID from node certificate", TestChipCert_ExtractNodeIdFabricId), NL_TEST_DEF("Test extracting Operational Discovery ID from node and root certificate", TestChipCert_ExtractOperationalDiscoveryId), NL_TEST_DEF("Test extracting CASE Authenticated Tags from node certificate", TestChipCert_ExtractCATsFromOpCert), + 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_SENTINEL() }; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h index f2d92873ebf4f8..0d038673093835 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.h @@ -67,7 +67,7 @@ class CHIPOperationalCredentialsDelegate : public chip::Controller::OperationalC bool ToChipEpochTime(uint32_t offset, uint32_t & epoch); ChipP256KeypairPtr mIssuerKey; - uint32_t mIssuerId = 1234; + uint64_t mIssuerId = 1234; const uint32_t kCertificateValiditySecs = 365 * 24 * 60 * 60; const NSString * kCHIPCAKeyChainLabel = @"matter.nodeopcerts.CA:0"; diff --git a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm index de132ee91df2a4..988dea237afc82 100644 --- a/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm +++ b/src/darwin/Framework/CHIP/CHIPOperationalCredentialsDelegate.mm @@ -87,14 +87,14 @@ static BOOL isRunningTests(void) return CHIP_ERROR_INVALID_ARGUMENT; } - char issuerIdString[16]; - uint16_t idStringLen = sizeof(issuerIdString); - if (CHIP_NO_ERROR != storage->SyncGetKeyValue(CHIP_COMMISSIONER_CA_ISSUER_ID, issuerIdString, idStringLen)) { + uint16_t issuerIdLen = sizeof(mIssuerId); + if (CHIP_NO_ERROR != storage->SyncGetKeyValue(CHIP_COMMISSIONER_CA_ISSUER_ID, &mIssuerId, issuerIdLen)) { mIssuerId = arc4random(); - CHIP_LOG_ERROR("Assigned %d certificate issuer ID to the commissioner", mIssuerId); + mIssuerId = mIssuerId << 32 | arc4random(); + CHIP_LOG_ERROR("Assigned %llx certificate issuer ID to the commissioner", mIssuerId); storage->SyncSetKeyValue(CHIP_COMMISSIONER_CA_ISSUER_ID, &mIssuerId, sizeof(mIssuerId)); } else { - CHIP_LOG_ERROR("Found %d certificate issuer ID for the commissioner", mIssuerId); + CHIP_LOG_ERROR("Found %llx certificate issuer ID for the commissioner", mIssuerId); } return CHIP_NO_ERROR; @@ -203,40 +203,37 @@ static BOOL isRunningTests(void) return CHIP_ERROR_INTERNAL; } - ChipDN noc_dn; - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); - ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); - ReturnErrorOnFailure(noc_dn.AddCATs(cats)); ChipDN rcac_dn; - ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); - ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); + if (!mGenerateRootCert) { + uint16_t rcacBufLen = static_cast(std::min(rcac.size(), static_cast(UINT16_MAX))); + PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, + ReturnErrorOnFailure(mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen))); + rcac.reduce_size(rcacBufLen); + ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn)); + } else { + ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId)); + ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); - X509CertRequestParams noc_request = { 1, validityStart, validityEnd, noc_dn, rcac_dn }; - ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, *mIssuerKey, noc)); - icac.reduce_size(0); + NSLog(@"Generating RCAC"); + X509CertRequestParams rcac_request = { 0, validityStart, validityEnd, rcac_dn, rcac_dn }; + ReturnErrorOnFailure(NewRootX509Cert(rcac_request, *mIssuerKey, rcac)); - uint16_t rcacBufLen = static_cast(std::min(rcac.size(), static_cast(UINT16_MAX))); - CHIP_ERROR err = CHIP_NO_ERROR; - if (!mGenerateRootCert) { + VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, - err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen)); - if (err == CHIP_NO_ERROR) { - // Found root certificate in the storage. - rcac.reduce_size(rcacBufLen); - return CHIP_NO_ERROR; - } - } + ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size())))); - X509CertRequestParams rcac_request = { 0, validityStart, validityEnd, rcac_dn, rcac_dn }; - ReturnErrorOnFailure(NewRootX509Cert(rcac_request, *mIssuerKey, rcac)); + mGenerateRootCert = false; + } - VerifyOrReturnError(CanCastTo(rcac.size()), CHIP_ERROR_INTERNAL); - PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key, - err = mStorage->SyncSetKeyValue(key, rcac.data(), static_cast(rcac.size()))); + icac.reduce_size(0); - mGenerateRootCert = false; + ChipDN noc_dn; + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId)); + ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId)); + ReturnErrorOnFailure(noc_dn.AddCATs(cats)); - return err; + X509CertRequestParams noc_request = { 1, validityStart, validityEnd, noc_dn, rcac_dn }; + return NewNodeOperationalX509Cert(noc_request, pubkey, *mIssuerKey, noc); } CHIP_ERROR CHIPOperationalCredentialsDelegate::GenerateNOCChain(const chip::ByteSpan & csrElements, diff --git a/src/lib/asn1/ASN1Macros.h b/src/lib/asn1/ASN1Macros.h index 1a38c47303d6b8..5107515dda0425 100644 --- a/src/lib/asn1/ASN1Macros.h +++ b/src/lib/asn1/ASN1Macros.h @@ -86,18 +86,28 @@ } \ while (0) +#define ASN1_SKIP_AND_EXIT_CONSTRUCTED \ + ASN1_ERR = ASN1_READER.ExitConstructedType(); \ + SuccessOrExit(ASN1_ERR); \ + } \ + while (0) + #define ASN1_PARSE_ENTER_SEQUENCE ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_Universal, kASN1UniversalTag_Sequence) #define ASN1_ENTER_SEQUENCE ASN1_ENTER_CONSTRUCTED(kASN1TagClass_Universal, kASN1UniversalTag_Sequence) #define ASN1_EXIT_SEQUENCE ASN1_EXIT_CONSTRUCTED +#define ASN1_SKIP_AND_EXIT_SEQUENCE ASN1_SKIP_AND_EXIT_CONSTRUCTED + #define ASN1_PARSE_ENTER_SET ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_Universal, kASN1UniversalTag_Set) #define ASN1_ENTER_SET ASN1_ENTER_CONSTRUCTED(kASN1TagClass_Universal, kASN1UniversalTag_Set) #define ASN1_EXIT_SET ASN1_EXIT_CONSTRUCTED +#define ASN1_SKIP_AND_EXIT_SET ASN1_SKIP_AND_EXIT_CONSTRUCTED + #define ASN1_ENTER_ENCAPSULATED(CLASS, TAG) \ do \ { \