| /* |
| * |
| * Copyright (c) 2020-2022 Project CHIP Authors |
| * Copyright (c) 2019 Google LLC. |
| * Copyright (c) 2013-2017 Nest Labs, Inc. |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @file |
| * This file implements objects for modeling and working with |
| * CHIP certificates. |
| * |
| */ |
| |
| #include <stddef.h> |
| |
| #include <credentials/CHIPCert_Internal.h> |
| #include <credentials/CHIPCertificateSet.h> |
| #include <lib/asn1/ASN1.h> |
| #include <lib/asn1/ASN1Macros.h> |
| #include <lib/core/CHIPCore.h> |
| #include <lib/core/CHIPSafeCasts.h> |
| #include <lib/core/TLV.h> |
| #include <lib/support/BytesToHex.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/SafeInt.h> |
| #include <lib/support/ScopedBuffer.h> |
| #include <lib/support/TimeUtils.h> |
| #include <protocols/Protocols.h> |
| |
| namespace chip { |
| namespace Credentials { |
| |
| using namespace chip::ASN1; |
| using namespace chip::TLV; |
| using namespace chip::Protocols; |
| using namespace chip::Crypto; |
| |
| ChipCertificateSet::ChipCertificateSet() |
| { |
| mCerts = nullptr; |
| mCertCount = 0; |
| mMaxCerts = 0; |
| mMemoryAllocInternal = false; |
| } |
| |
| ChipCertificateSet::~ChipCertificateSet() |
| { |
| Release(); |
| } |
| |
| CHIP_ERROR ChipCertificateSet::Init(uint8_t maxCertsArraySize) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(maxCertsArraySize > 0, err = CHIP_ERROR_INVALID_ARGUMENT); |
| mCerts = reinterpret_cast<ChipCertificateData *>(chip::Platform::MemoryAlloc(sizeof(ChipCertificateData) * maxCertsArraySize)); |
| VerifyOrExit(mCerts != nullptr, err = CHIP_ERROR_NO_MEMORY); |
| |
| mMaxCerts = maxCertsArraySize; |
| mMemoryAllocInternal = true; |
| |
| Clear(); |
| |
| exit: |
| if (err != CHIP_NO_ERROR) |
| { |
| Release(); |
| } |
| |
| return err; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::Init(ChipCertificateData * certsArray, uint8_t certsArraySize) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| VerifyOrExit(certsArray != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrExit(certsArraySize > 0, err = CHIP_ERROR_INVALID_ARGUMENT); |
| |
| mCerts = certsArray; |
| mMaxCerts = certsArraySize; |
| mMemoryAllocInternal = false; |
| |
| Clear(); |
| |
| exit: |
| return err; |
| } |
| |
| void ChipCertificateSet::Release() |
| { |
| if (mMemoryAllocInternal) |
| { |
| if (mCerts != nullptr) |
| { |
| Clear(); |
| chip::Platform::MemoryFree(mCerts); |
| mCerts = nullptr; |
| } |
| } |
| } |
| |
| void ChipCertificateSet::Clear() |
| { |
| for (int i = 0; i < mMaxCerts; i++) |
| { |
| mCerts[i].Clear(); |
| } |
| |
| mCertCount = 0; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::LoadCert(const ByteSpan chipCert, BitFlags<CertDecodeFlags> decodeFlags) |
| { |
| TLVReader reader; |
| |
| reader.Init(chipCert); |
| return LoadCert(reader, decodeFlags, chipCert); |
| } |
| |
| CHIP_ERROR ChipCertificateSet::LoadCert(TLVReader & reader, BitFlags<CertDecodeFlags> decodeFlags, ByteSpan chipCert) |
| { |
| ChipCertificateData cert; |
| ReturnErrorOnFailure(DecodeChipCert(reader, cert, decodeFlags)); |
| |
| // 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); |
| |
| // Check if this cert matches any currently loaded certificates |
| for (uint32_t i = 0; i < mCertCount; i++) |
| { |
| if (cert.IsEqual(mCerts[i])) |
| { |
| // This cert is already loaded. Let's skip adding this cert. |
| return CHIP_NO_ERROR; |
| } |
| } |
| |
| // Verify we have room for the new certificate. |
| VerifyOrReturnError(mCertCount < mMaxCerts, CHIP_ERROR_NO_MEMORY); |
| |
| new (&mCerts[mCertCount]) ChipCertificateData(cert); |
| mCertCount++; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::ReleaseLastCert() |
| { |
| ChipCertificateData * lastCert = (mCertCount > 0) ? &mCerts[mCertCount - 1] : nullptr; |
| VerifyOrReturnError(lastCert != nullptr, CHIP_ERROR_INTERNAL); |
| |
| lastCert->~ChipCertificateData(); |
| --mCertCount; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| const ChipCertificateData * ChipCertificateSet::FindCert(const CertificateKeyId & subjectKeyId) const |
| { |
| for (uint8_t i = 0; i < mCertCount; i++) |
| { |
| ChipCertificateData & cert = mCerts[i]; |
| if (cert.mSubjectKeyId.data_equal(subjectKeyId)) |
| { |
| return &cert; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| bool ChipCertificateSet::IsCertInTheSet(const ChipCertificateData * cert) const |
| { |
| for (uint8_t i = 0; i < mCertCount; i++) |
| { |
| if (cert == &mCerts[i]) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, ValidationContext & context) |
| { |
| VerifyOrReturnError(IsCertInTheSet(cert), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| context.mTrustAnchor = nullptr; |
| |
| return ValidateCert(cert, context, 0); |
| } |
| |
| CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, |
| ValidationContext & context, const ChipCertificateData ** certData) |
| { |
| context.mTrustAnchor = nullptr; |
| |
| return FindValidCert(subjectDN, subjectKeyId, context, 0, certData); |
| } |
| |
| CHIP_ERROR ChipCertificateSet::VerifySignature(const ChipCertificateData * cert, const ChipCertificateData * caCert) |
| { |
| VerifyOrReturnError((cert != nullptr) && (caCert != nullptr), CHIP_ERROR_INVALID_ARGUMENT); |
| return VerifyCertSignature(*cert, *caCert); |
| } |
| |
| CHIP_ERROR VerifyCertSignature(const ChipCertificateData & cert, const ChipCertificateData & signer) |
| { |
| VerifyOrReturnError(cert.mCertFlags.Has(CertFlags::kTBSHashPresent), CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(cert.mSigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, CHIP_ERROR_UNSUPPORTED_SIGNATURE_TYPE); |
| |
| #ifdef ENABLE_HSM_ECDSA_VERIFY |
| P256PublicKeyHSM signerPublicKey; |
| #else |
| P256PublicKey signerPublicKey; |
| #endif |
| P256ECDSASignature signature; |
| |
| ReturnErrorOnFailure(signature.SetLength(cert.mSignature.size())); |
| memcpy(signature.Bytes(), cert.mSignature.data(), cert.mSignature.size()); |
| |
| memcpy(signerPublicKey, signer.mPublicKey.data(), signer.mPublicKey.size()); |
| |
| ReturnErrorOnFailure( |
| signerPublicKey.ECDSA_validate_hash_signature(cert.mTBSHash, chip::Crypto::kSHA256_Hash_Length, signature)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::ValidateCert(const ChipCertificateData * cert, ValidationContext & context, uint8_t depth) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| const ChipCertificateData * caCert = nullptr; |
| CertType certType; |
| |
| err = cert->mSubjectDN.GetCertType(certType); |
| SuccessOrExit(err); |
| |
| // Certificate with future-extension marked as "critical" is not allowed. |
| VerifyOrExit(!cert->mCertFlags.Has(CertFlags::kExtPresent_FutureIsCritical), err = CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| |
| // If the depth is greater than 0 then the certificate is required to be a CA certificate... |
| if (depth > 0) |
| { |
| // Verify the isCA flag is present. |
| VerifyOrExit(cert->mCertFlags.Has(CertFlags::kIsCA), err = CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| |
| // Verify the key usage extension is present and contains the 'keyCertSign' flag. |
| VerifyOrExit(cert->mCertFlags.Has(CertFlags::kExtPresent_KeyUsage) && cert->mKeyUsageFlags.Has(KeyUsageFlags::kKeyCertSign), |
| err = CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| |
| // Verify that the certificate type is set to Root or ICA. |
| VerifyOrExit(certType == CertType::kICA || certType == CertType::kRoot, err = CHIP_ERROR_WRONG_CERT_TYPE); |
| |
| // If a path length constraint was included, verify the cert depth vs. the specified constraint. |
| // |
| // From the RFC, the path length constraint "gives the maximum number of non-self-issued |
| // intermediate certificates that may follow this certificate in a valid certification path. |
| // (Note: The last certificate in the certification path is not an intermediate certificate, |
| // and is not included in this limit...)" |
| // |
| if (cert->mCertFlags.Has(CertFlags::kPathLenConstraintPresent)) |
| { |
| VerifyOrExit((depth - 1) <= cert->mPathLenConstraint, err = CHIP_ERROR_CERT_PATH_LEN_CONSTRAINT_EXCEEDED); |
| } |
| } |
| |
| // Otherwise verify the desired certificate usages/purposes/type given in the validation context... |
| else |
| { |
| // If a set of desired key usages has been specified, verify that the key usage extension exists |
| // in the certificate and that the corresponding usages are supported. |
| if (context.mRequiredKeyUsages.HasAny()) |
| { |
| VerifyOrExit(cert->mCertFlags.Has(CertFlags::kExtPresent_KeyUsage) && |
| cert->mKeyUsageFlags.HasAll(context.mRequiredKeyUsages), |
| err = CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| } |
| |
| // If a set of desired key purposes has been specified, verify that the extended key usage extension |
| // exists in the certificate and that the corresponding purposes are supported. |
| if (context.mRequiredKeyPurposes.HasAny()) |
| { |
| VerifyOrExit(cert->mCertFlags.Has(CertFlags::kExtPresent_ExtendedKeyUsage) && |
| cert->mKeyPurposeFlags.HasAll(context.mRequiredKeyPurposes), |
| err = CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| } |
| |
| // If a required certificate type has been specified, verify it against the current certificate's type. |
| if (context.mRequiredCertType != CertType::kNotSpecified) |
| { |
| VerifyOrExit(certType == context.mRequiredCertType, err = CHIP_ERROR_WRONG_CERT_TYPE); |
| } |
| } |
| |
| // Verify NotBefore and NotAfter validity of the certificates. |
| // |
| // See also ASN1ToChipEpochTime(). |
| // |
| // X.509/RFC5280 defines the special time 99991231235959Z to mean 'no |
| // well-defined expiration date'. In CHIP TLV-encoded certificates, this |
| // special value is represented as a CHIP Epoch time value of 0 sec |
| // (2000-01-01 00:00:00 UTC). |
| CertificateValidityResult validityResult; |
| if (context.mEffectiveTime.Is<CurrentChipEpochTime>()) |
| { |
| if (context.mEffectiveTime.Get<CurrentChipEpochTime>().count() < cert->mNotBeforeTime) |
| { |
| ChipLogDetail(SecureChannel, "Certificate's mNotBeforeTime (%" PRIu32 ") is after current time (%" PRIu32 ")", |
| cert->mNotBeforeTime, context.mEffectiveTime.Get<CurrentChipEpochTime>().count()); |
| validityResult = CertificateValidityResult::kNotYetValid; |
| } |
| else if (cert->mNotAfterTime != kNullCertTime && |
| context.mEffectiveTime.Get<CurrentChipEpochTime>().count() > cert->mNotAfterTime) |
| { |
| ChipLogDetail(SecureChannel, "Certificate's mNotAfterTime (%" PRIu32 ") is before current time (%" PRIu32 ")", |
| cert->mNotAfterTime, context.mEffectiveTime.Get<CurrentChipEpochTime>().count()); |
| validityResult = CertificateValidityResult::kExpired; |
| } |
| else |
| { |
| validityResult = CertificateValidityResult::kValid; |
| } |
| } |
| else if (context.mEffectiveTime.Is<LastKnownGoodChipEpochTime>()) |
| { |
| // Last Known Good Time may not be moved forward except at the time of |
| // commissioning or firmware update, so we can't use it to validate |
| // NotBefore. However, so long as firmware build times are properly |
| // recorded and certificates loaded during commissioning are in fact |
| // valid at the time of commissioning, observing a NotAfter that falls |
| // before Last Known Good Time is a reliable indicator that the |
| // certificate in question is expired. Check for this. |
| if (cert->mNotAfterTime != 0 && context.mEffectiveTime.Get<LastKnownGoodChipEpochTime>().count() > cert->mNotAfterTime) |
| { |
| ChipLogDetail(SecureChannel, "Certificate's mNotAfterTime (%" PRIu32 ") is before last known good time (%" PRIu32 ")", |
| cert->mNotAfterTime, context.mEffectiveTime.Get<LastKnownGoodChipEpochTime>().count()); |
| validityResult = CertificateValidityResult::kExpiredAtLastKnownGoodTime; |
| } |
| else |
| { |
| validityResult = CertificateValidityResult::kNotExpiredAtLastKnownGoodTime; |
| } |
| } |
| else |
| { |
| validityResult = CertificateValidityResult::kTimeUnknown; |
| } |
| |
| if (context.mValidityPolicy != nullptr) |
| { |
| SuccessOrExit(err = context.mValidityPolicy->ApplyCertificateValidityPolicy(cert, depth, validityResult)); |
| } |
| else |
| { |
| SuccessOrExit(err = CertificateValidityPolicy::ApplyDefaultPolicy(cert, depth, validityResult)); |
| } |
| |
| // If the certificate itself is trusted, then it is implicitly valid. Record this certificate as the trust |
| // anchor and return success. |
| if (cert->mCertFlags.Has(CertFlags::kIsTrustAnchor)) |
| { |
| context.mTrustAnchor = cert; |
| ExitNow(err = CHIP_NO_ERROR); |
| } |
| |
| // Otherwise we must validate the certificate by looking for a chain of valid certificates up to a trusted |
| // certificate known as the 'trust anchor'. |
| |
| // Fail validation if the certificate is self-signed. Since we don't trust this certificate (see the check above) and |
| // it has no path we can follow to a trust anchor, it can't be considered valid. |
| if (cert->mIssuerDN.IsEqual(cert->mSubjectDN) && cert->mAuthKeyId.data_equal(cert->mSubjectKeyId)) |
| { |
| ExitNow(err = CHIP_ERROR_CERT_NOT_TRUSTED); |
| } |
| |
| // Verify that the certificate depth is less than the total number of certificates. It is technically possible to create |
| // a circular chain of certificates. Limiting the maximum depth of the certificate path prevents infinite |
| // recursion in such a case. |
| VerifyOrExit(depth < mCertCount, err = CHIP_ERROR_CERT_PATH_TOO_LONG); |
| |
| // Search for a valid CA certificate that matches the Issuer DN and Authority Key Id of the current certificate. |
| // Fail if no acceptable certificate is found. |
| err = FindValidCert(cert->mIssuerDN, cert->mAuthKeyId, context, static_cast<uint8_t>(depth + 1), &caCert); |
| if (err != CHIP_NO_ERROR) |
| { |
| ExitNow(err = CHIP_ERROR_CA_CERT_NOT_FOUND); |
| } |
| |
| // Verify signature of the current certificate against public key of the CA certificate. If signature verification |
| // succeeds, the current certificate is valid. |
| err = VerifyCertSignature(*cert, *caCert); |
| SuccessOrExit(err); |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR ChipCertificateSet::FindValidCert(const ChipDN & subjectDN, const CertificateKeyId & subjectKeyId, |
| ValidationContext & context, uint8_t depth, const ChipCertificateData ** certData) |
| { |
| CHIP_ERROR err; |
| |
| *certData = nullptr; |
| |
| // Default error if we don't find any matching cert. |
| err = (depth > 0) ? CHIP_ERROR_CA_CERT_NOT_FOUND : CHIP_ERROR_CERT_NOT_FOUND; |
| |
| // For each cert in the set... |
| for (uint8_t i = 0; i < mCertCount; i++) |
| { |
| ChipCertificateData * candidateCert = &mCerts[i]; |
| |
| // Skip the certificate if its subject DN and key id do not match the input criteria. |
| if (!candidateCert->mSubjectDN.IsEqual(subjectDN)) |
| { |
| continue; |
| } |
| if (!candidateCert->mSubjectKeyId.data_equal(subjectKeyId)) |
| { |
| continue; |
| } |
| |
| // Attempt to validate the cert. If the cert is valid, return it to the caller. Otherwise, |
| // save the returned error and continue searching. If there are no other matching certs this |
| // will be the error returned to the caller. |
| err = ValidateCert(candidateCert, context, depth); |
| if (err == CHIP_NO_ERROR) |
| { |
| *certData = candidateCert; |
| ExitNow(); |
| } |
| } |
| |
| exit: |
| return err; |
| } |
| |
| ChipCertificateData::ChipCertificateData() {} |
| |
| ChipCertificateData::~ChipCertificateData() {} |
| |
| void ChipCertificateData::Clear() |
| { |
| mSerialNumber = ByteSpan(); |
| mSubjectDN.Clear(); |
| mIssuerDN.Clear(); |
| mSubjectKeyId = CertificateKeyId(); |
| mAuthKeyId = CertificateKeyId(); |
| mNotBeforeTime = 0; |
| mNotAfterTime = 0; |
| mPublicKey = P256PublicKeySpan(); |
| mPubKeyCurveOID = 0; |
| mPubKeyAlgoOID = 0; |
| mSigAlgoOID = 0; |
| mPathLenConstraint = 0; |
| mCertFlags.ClearAll(); |
| mKeyUsageFlags.ClearAll(); |
| mKeyPurposeFlags.ClearAll(); |
| mSignature = P256ECDSASignatureSpan(); |
| |
| memset(mTBSHash, 0, sizeof(mTBSHash)); |
| } |
| |
| bool ChipCertificateData::IsEqual(const ChipCertificateData & other) const |
| { |
| // TODO - Add an operator== on BitFlags class. |
| return mSubjectDN.IsEqual(other.mSubjectDN) && mIssuerDN.IsEqual(other.mIssuerDN) && |
| mSubjectKeyId.data_equal(other.mSubjectKeyId) && mAuthKeyId.data_equal(other.mAuthKeyId) && |
| (mNotBeforeTime == other.mNotBeforeTime) && (mNotAfterTime == other.mNotAfterTime) && |
| mPublicKey.data_equal(other.mPublicKey) && (mPubKeyCurveOID == other.mPubKeyCurveOID) && |
| (mPubKeyAlgoOID == other.mPubKeyAlgoOID) && (mSigAlgoOID == other.mSigAlgoOID) && |
| (mCertFlags.Raw() == other.mCertFlags.Raw()) && (mKeyUsageFlags.Raw() == other.mKeyUsageFlags.Raw()) && |
| (mKeyPurposeFlags.Raw() == other.mKeyPurposeFlags.Raw()) && (mPathLenConstraint == other.mPathLenConstraint) && |
| mSignature.data_equal(other.mSignature) && (memcmp(mTBSHash, other.mTBSHash, sizeof(mTBSHash)) == 0); |
| } |
| |
| void ValidationContext::Reset() |
| { |
| mEffectiveTime = EffectiveTime{}; |
| mTrustAnchor = nullptr; |
| mValidityPolicy = nullptr; |
| mRequiredKeyUsages.ClearAll(); |
| mRequiredKeyPurposes.ClearAll(); |
| mRequiredCertType = CertType::kNotSpecified; |
| } |
| |
| bool ChipRDN::IsEqual(const ChipRDN & other) const |
| { |
| if (mAttrOID == kOID_Unknown || mAttrOID == kOID_NotSpecified || mAttrOID != other.mAttrOID || |
| mAttrIsPrintableString != other.mAttrIsPrintableString) |
| { |
| return false; |
| } |
| |
| if (IsChipDNAttr(mAttrOID)) |
| { |
| return mChipVal == other.mChipVal; |
| } |
| |
| return mString.data_equal(other.mString); |
| } |
| |
| ChipDN::ChipDN() |
| { |
| Clear(); |
| } |
| |
| ChipDN::~ChipDN() {} |
| |
| void ChipDN::Clear() |
| { |
| for (auto & dn : rdn) |
| { |
| dn.Clear(); |
| } |
| } |
| |
| uint8_t ChipDN::RDNCount() const |
| { |
| uint8_t count; |
| |
| for (count = 0; count < CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES; count++) |
| { |
| if (rdn[count].IsEmpty()) |
| { |
| break; |
| } |
| } |
| |
| return count; |
| } |
| |
| CHIP_ERROR ChipDN::AddAttribute(chip::ASN1::OID oid, uint64_t val) |
| { |
| uint8_t rdnCount = RDNCount(); |
| |
| VerifyOrReturnError(rdnCount < CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES, CHIP_ERROR_NO_MEMORY); |
| VerifyOrReturnError(IsChipDNAttr(oid), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (IsChip32bitDNAttr(oid)) |
| { |
| VerifyOrReturnError(CanCastTo<uint32_t>(val), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| rdn[rdnCount].mAttrOID = oid; |
| rdn[rdnCount].mChipVal = val; |
| rdn[rdnCount].mAttrIsPrintableString = false; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::AddCATs(const chip::CATValues & cats) |
| { |
| VerifyOrReturnError(cats.AreValid(), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| for (auto & cat : cats.values) |
| { |
| if (cat != kUndefinedCAT) |
| { |
| ReturnErrorOnFailure(AddAttribute_MatterCASEAuthTag(cat)); |
| } |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::AddAttribute(chip::ASN1::OID oid, CharSpan val, bool isPrintableString) |
| { |
| uint8_t rdnCount = RDNCount(); |
| |
| VerifyOrReturnError(rdnCount < CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES, CHIP_ERROR_NO_MEMORY); |
| VerifyOrReturnError(!IsChipDNAttr(oid), CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(oid != kOID_NotSpecified, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| rdn[rdnCount].mAttrOID = oid; |
| rdn[rdnCount].mString = val; |
| rdn[rdnCount].mAttrIsPrintableString = isPrintableString; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::GetCertType(CertType & certType) const |
| { |
| CertType lCertType = CertType::kNotSpecified; |
| bool fabricIdPresent = false; |
| bool catsPresent = false; |
| uint8_t rdnCount = RDNCount(); |
| |
| if (rdnCount == 1 && rdn[0].mAttrOID == kOID_AttributeType_CommonName && !rdn[0].mAttrIsPrintableString && |
| rdn[0].mString.data_equal(kNetworkIdentityCN)) |
| { |
| certType = CertType::kNetworkIdentity; |
| return CHIP_NO_ERROR; |
| } |
| |
| certType = CertType::kNotSpecified; |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| if (rdn[i].mAttrOID == kOID_AttributeType_MatterRCACId) |
| { |
| VerifyOrReturnError(lCertType == CertType::kNotSpecified, CHIP_ERROR_WRONG_CERT_DN); |
| |
| lCertType = CertType::kRoot; |
| } |
| else if (rdn[i].mAttrOID == kOID_AttributeType_MatterICACId) |
| { |
| VerifyOrReturnError(lCertType == CertType::kNotSpecified, CHIP_ERROR_WRONG_CERT_DN); |
| |
| lCertType = CertType::kICA; |
| } |
| else if (rdn[i].mAttrOID == kOID_AttributeType_MatterNodeId) |
| { |
| VerifyOrReturnError(lCertType == CertType::kNotSpecified, CHIP_ERROR_WRONG_CERT_DN); |
| VerifyOrReturnError(IsOperationalNodeId(rdn[i].mChipVal), CHIP_ERROR_WRONG_NODE_ID); |
| lCertType = CertType::kNode; |
| } |
| else if (rdn[i].mAttrOID == kOID_AttributeType_MatterFirmwareSigningId) |
| { |
| VerifyOrReturnError(lCertType == CertType::kNotSpecified, CHIP_ERROR_WRONG_CERT_DN); |
| |
| lCertType = CertType::kFirmwareSigning; |
| } |
| else if (rdn[i].mAttrOID == kOID_AttributeType_MatterFabricId) |
| { |
| // Only one fabricId attribute is allowed per DN. |
| VerifyOrReturnError(!fabricIdPresent, CHIP_ERROR_WRONG_CERT_DN); |
| VerifyOrReturnError(IsValidFabricId(rdn[i].mChipVal), CHIP_ERROR_WRONG_CERT_DN); |
| fabricIdPresent = true; |
| } |
| else if (rdn[i].mAttrOID == kOID_AttributeType_MatterCASEAuthTag) |
| { |
| VerifyOrReturnError(CanCastTo<CASEAuthTag>(rdn[i].mChipVal), CHIP_ERROR_WRONG_CERT_DN); |
| VerifyOrReturnError(IsValidCASEAuthTag(static_cast<CASEAuthTag>(rdn[i].mChipVal)), CHIP_ERROR_WRONG_CERT_DN); |
| catsPresent = true; |
| } |
| } |
| |
| if (lCertType == CertType::kNode) |
| { |
| VerifyOrReturnError(fabricIdPresent, CHIP_ERROR_WRONG_CERT_DN); |
| } |
| else |
| { |
| VerifyOrReturnError(!catsPresent, CHIP_ERROR_WRONG_CERT_DN); |
| } |
| |
| certType = lCertType; |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::GetCertChipId(uint64_t & chipId) const |
| { |
| uint8_t rdnCount = RDNCount(); |
| bool foundId = false; |
| |
| chipId = 0; |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| switch (rdn[i].mAttrOID) |
| { |
| case kOID_AttributeType_MatterRCACId: |
| case kOID_AttributeType_MatterICACId: |
| case kOID_AttributeType_MatterNodeId: |
| case kOID_AttributeType_MatterFirmwareSigningId: |
| VerifyOrReturnError(!foundId, CHIP_ERROR_WRONG_CERT_DN); |
| |
| chipId = rdn[i].mChipVal; |
| foundId = true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| VerifyOrReturnError(foundId, CHIP_ERROR_WRONG_CERT_DN); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::GetCertFabricId(uint64_t & fabricId) const |
| { |
| uint8_t rdnCount = RDNCount(); |
| |
| fabricId = kUndefinedFabricId; |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| switch (rdn[i].mAttrOID) |
| { |
| case kOID_AttributeType_MatterFabricId: |
| // Ensure only one FabricID RDN present, since start value is kUndefinedFabricId, which is reserved and never seen. |
| VerifyOrReturnError(fabricId == kUndefinedFabricId, CHIP_ERROR_WRONG_CERT_DN); |
| VerifyOrReturnError(IsValidFabricId(rdn[i].mChipVal), CHIP_ERROR_WRONG_CERT_DN); |
| fabricId = rdn[i].mChipVal; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| VerifyOrReturnError(IsValidFabricId(fabricId), CHIP_ERROR_WRONG_CERT_DN); |
| 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; |
| TLVType outerContainer; |
| |
| static constexpr uint32_t kOID_AttributeIsPrintableString_Flag = 0x00000080; |
| static constexpr uint32_t kOID_AttributeType_Mask = 0x0000007F; |
| |
| VerifyOrReturnError(reader.GetType() == kTLVType_List, CHIP_ERROR_WRONG_TLV_TYPE); |
| |
| // Enter the List TLV element that represents the DN in TLV format. |
| ReturnErrorOnFailure(reader.EnterContainer(outerContainer)); |
| |
| // Read the RDN attributes in the List. |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| // Get the TLV tag, make sure it is a context tag and extract the context tag number. |
| Tag tlvTag = reader.GetTag(); |
| VerifyOrReturnError(IsContextTag(tlvTag), CHIP_ERROR_INVALID_TLV_TAG); |
| uint32_t tlvTagNum = TagNumFromTag(tlvTag); |
| |
| // Derive the OID of the corresponding ASN.1 attribute from the TLV tag number. |
| // The numeric value of the OID is encoded in the bottom 7 bits of the TLV tag number. |
| // This eliminates the need for a translation table/switch statement but has the |
| // effect of tying the two encodings together. |
| // |
| // NOTE: In the event that the computed OID value is not one that we recognize |
| // (specifically, is not in the table of OIDs defined in ASN1OID.h) then the |
| // macro call below that encodes the attribute's object id (ASN1_ENCODE_OBJECT_ID) |
| // will fail for lack of the OID's encoded representation. Given this there's no |
| // need to test the validity of the OID here. |
| // |
| OID attrOID = GetOID(kOIDCategory_AttributeType, static_cast<uint8_t>(tlvTagNum & kOID_AttributeType_Mask)); |
| |
| bool attrIsPrintableString = (tlvTagNum & kOID_AttributeIsPrintableString_Flag) == kOID_AttributeIsPrintableString_Flag; |
| |
| // For 64-bit CHIP-defined DN attributes. |
| if (IsChip64bitDNAttr(attrOID)) |
| { |
| uint64_t chipAttr; |
| VerifyOrReturnError(attrIsPrintableString == false, CHIP_ERROR_INVALID_TLV_TAG); |
| ReturnErrorOnFailure(reader.Get(chipAttr)); |
| if (attrOID == chip::ASN1::kOID_AttributeType_MatterNodeId) |
| { |
| VerifyOrReturnError(IsOperationalNodeId(chipAttr), CHIP_ERROR_WRONG_NODE_ID); |
| } |
| else if (attrOID == chip::ASN1::kOID_AttributeType_MatterFabricId) |
| { |
| VerifyOrReturnError(IsValidFabricId(chipAttr), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr)); |
| } |
| // For 32-bit CHIP-defined DN attributes. |
| else if (IsChip32bitDNAttr(attrOID)) |
| { |
| uint32_t chipAttr; |
| VerifyOrReturnError(attrIsPrintableString == false, CHIP_ERROR_INVALID_TLV_TAG); |
| ReturnErrorOnFailure(reader.Get(chipAttr)); |
| if (attrOID == chip::ASN1::kOID_AttributeType_MatterCASEAuthTag) |
| { |
| VerifyOrReturnError(IsValidCASEAuthTag(chipAttr), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr)); |
| } |
| // Otherwise the attribute is one of the supported X.509 attributes |
| else |
| { |
| CharSpan asn1Attr; |
| ReturnErrorOnFailure(reader.Get(asn1Attr)); |
| ReturnErrorOnFailure(AddAttribute(attrOID, asn1Attr, attrIsPrintableString)); |
| } |
| } |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| ReturnErrorOnFailure(reader.ExitContainer(outerContainer)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ChipDN::EncodeToASN1(ASN1Writer & writer) const |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| uint8_t rdnCount = RDNCount(); |
| |
| ASN1_START_SEQUENCE |
| { |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| ASN1_START_SET |
| { |
| char chipAttrStr[kChip64bitAttrUTF8Length]; |
| CharSpan asn1Attr; |
| uint8_t asn1Tag; |
| chip::ASN1::OID attrOID = rdn[i].mAttrOID; |
| |
| if (IsChip64bitDNAttr(attrOID)) |
| { |
| ReturnErrorOnFailure( |
| Encoding::Uint64ToHex(rdn[i].mChipVal, chipAttrStr, sizeof(chipAttrStr), Encoding::HexFlags::kUppercase)); |
| asn1Attr = CharSpan(chipAttrStr, kChip64bitAttrUTF8Length); |
| asn1Tag = kASN1UniversalTag_UTF8String; |
| } |
| else if (IsChip32bitDNAttr(attrOID)) |
| { |
| ReturnErrorOnFailure(Encoding::Uint32ToHex(static_cast<uint32_t>(rdn[i].mChipVal), chipAttrStr, |
| sizeof(chipAttrStr), Encoding::HexFlags::kUppercase)); |
| asn1Attr = CharSpan(chipAttrStr, kChip32bitAttrUTF8Length); |
| asn1Tag = kASN1UniversalTag_UTF8String; |
| } |
| else |
| { |
| asn1Attr = rdn[i].mString; |
| |
| // Determine the appropriate ASN.1 tag for the DN attribute. |
| // - DomainComponent is always an IA5String. |
| // - For all other ASN.1 defined attributes, bit 0x80 in the TLV tag value conveys whether the attribute |
| // is a UTF8String or a PrintableString (in some cases the certificate generator has a choice). |
| if (attrOID == kOID_AttributeType_DomainComponent) |
| { |
| asn1Tag = kASN1UniversalTag_IA5String; |
| } |
| else |
| { |
| asn1Tag = rdn[i].mAttrIsPrintableString ? kASN1UniversalTag_PrintableString : kASN1UniversalTag_UTF8String; |
| } |
| } |
| |
| // AttributeTypeAndValue ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // type AttributeType |
| // AttributeType ::= OBJECT IDENTIFIER |
| ASN1_ENCODE_OBJECT_ID(attrOID); |
| |
| VerifyOrReturnError(CanCastTo<uint16_t>(asn1Attr.size()), CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
| |
| // value AttributeValue |
| // AttributeValue ::= ANY -- DEFINED BY AttributeType |
| ReturnErrorOnFailure(writer.PutString(asn1Tag, asn1Attr.data(), static_cast<uint16_t>(asn1Attr.size()))); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_SET; |
| } |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| 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<const char *>(reader.GetValue()), |
| static_cast<size_t>(reader.GetValueLen()), |
| chipAttr) == sizeof(uint64_t), |
| ASN1_ERROR_INVALID_ENCODING); |
| |
| if (attrOID == chip::ASN1::kOID_AttributeType_MatterNodeId) |
| { |
| VerifyOrReturnError(IsOperationalNodeId(chipAttr), CHIP_ERROR_WRONG_NODE_ID); |
| } |
| else if (attrOID == chip::ASN1::kOID_AttributeType_MatterFabricId) |
| { |
| 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<const char *>(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(); |
| VerifyOrReturnError(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; |
| uint8_t rdnCount = RDNCount(); |
| |
| VerifyOrExit(rdnCount > 0, res = false); |
| VerifyOrExit(rdnCount == other.RDNCount(), res = false); |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| VerifyOrExit(rdn[i].IsEqual(other.rdn[i]), res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| DLL_EXPORT CHIP_ERROR ASN1ToChipEpochTime(const chip::ASN1::ASN1UniversalTime & asn1Time, uint32_t & epochTime) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| // X.509/RFC5280 defines the special time 99991231235959Z to mean 'no well-defined expiration date'. |
| // In CHIP certificate it is represented as a CHIP Epoch time value of 0 sec (2000-01-01 00:00:00 UTC). |
| // |
| // While it is not conventional to use this special value for NotBefore, for simplicity we convert all |
| // time values of 99991231235959Z to CHIP epoch zero seconds. ChipEpochToASN1Time performs the inverse |
| // translation for conversions in the other direction. |
| // |
| // If in a conversion from X509 to CHIP TLV format the input X509 certificate encloses 99991231235959Z |
| // for NotBefore, this will be converted to the CHIP Epoch time value of 0 and consuming code will |
| // handle this transparently, as logic considering a NotBefore time at the CHIP epoch will evaluate all |
| // possible unsigned offsets from the CHIP epoch as valid, which is equivalent to ignoring NotBefore. |
| // |
| // If in a conversion from X509 to CHIP TLV format the input X509 certificate encloses a NotBefore time |
| // at the CHIP epoch itself, 2000-01-01 00:00:00, a resultant conversion to CHIP TLV certificate format |
| // will appear to have an invalid TBS signature when the symmetric ChipEpochToASN1Time produces |
| // 99991231235959Z for NotBefore during signature validation. |
| // |
| // Thus such certificates, when passing through this code, will not appear valid. This should be |
| // immediately evident at commissioning time. |
| if ((asn1Time.Year == kX509NoWellDefinedExpirationDateYear) && (asn1Time.Month == kMonthsPerYear) && |
| (asn1Time.Day == kMaxDaysPerMonth) && (asn1Time.Hour == kHoursPerDay - 1) && (asn1Time.Minute == kMinutesPerHour - 1) && |
| (asn1Time.Second == kSecondsPerMinute - 1)) |
| { |
| epochTime = kNullCertTime; |
| } |
| else |
| { |
| if (!CalendarToChipEpochTime(asn1Time.Year, asn1Time.Month, asn1Time.Day, asn1Time.Hour, asn1Time.Minute, asn1Time.Second, |
| epochTime)) |
| { |
| ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); |
| } |
| } |
| |
| exit: |
| return err; |
| } |
| |
| DLL_EXPORT CHIP_ERROR ChipEpochToASN1Time(uint32_t epochTime, chip::ASN1::ASN1UniversalTime & asn1Time) |
| { |
| // X.509/RFC5280 defines the special time 99991231235959Z to mean 'no well-defined expiration date'. |
| // In CHIP certificate it is represented as a CHIP Epoch time value of 0 secs (2000-01-01 00:00:00 UTC). |
| // |
| // For simplicity and symmetry with ASN1ToChipEpochTime, this method makes this conversion for all |
| // times, which in consuming code can create a conversion from CHIP epoch 0 seconds to 99991231235959Z |
| // for NotBefore, which is not conventional. |
| // |
| // If an original X509 certificate encloses a NotBefore time that is the CHIP Epoch itself, 2000-01-01 |
| // 00:00:00, the resultant X509 certificate in a conversion back from CHIP TLV format using this time |
| // conversion method will instead enclose the NotBefore time 99991231235959Z, which will invalidiate the |
| // TBS signature. Thus, certificates with this specific attribute are not usable with this code. |
| // Attempted installation of such certficates will fail during commissioning. |
| if (epochTime == kNullCertTime) |
| { |
| asn1Time.Year = kX509NoWellDefinedExpirationDateYear; |
| asn1Time.Month = kMonthsPerYear; |
| asn1Time.Day = kMaxDaysPerMonth; |
| asn1Time.Hour = kHoursPerDay - 1; |
| asn1Time.Minute = kMinutesPerHour - 1; |
| asn1Time.Second = kSecondsPerMinute - 1; |
| } |
| else |
| { |
| ChipEpochToCalendarTime(epochTime, asn1Time.Year, asn1Time.Month, asn1Time.Day, asn1Time.Hour, asn1Time.Minute, |
| asn1Time.Second); |
| } |
| |
| 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; |
| |
| // 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. |
| |
| ReturnErrorOnFailure(certSet.Init(&certData, 1)); |
| |
| ReturnErrorOnFailure(certSet.LoadCert(rcac, CertDecodeFlags::kGenerateTBSHash)); |
| |
| ReturnErrorOnFailure(ValidateCertificateType(certData, CertType::kRoot)); |
| |
| VerifyOrReturnError(certData.mSubjectDN.IsEqual(certData.mIssuerDN), CHIP_ERROR_WRONG_CERT_TYPE); |
| |
| VerifyOrReturnError(certData.mSubjectKeyId.data_equal(certData.mAuthKeyId), CHIP_ERROR_WRONG_CERT_TYPE); |
| |
| VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kIsCA), CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| if (certData.mCertFlags.Has(CertFlags::kPathLenConstraintPresent)) |
| { |
| VerifyOrReturnError(certData.mPathLenConstraint <= 1, CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| } |
| |
| VerifyOrReturnError(certData.mKeyUsageFlags.Has(KeyUsageFlags::kKeyCertSign), CHIP_ERROR_CERT_USAGE_NOT_ALLOWED); |
| |
| return VerifyCertSignature(certData, certData); |
| } |
| |
| CHIP_ERROR ConvertIntegerDERToRaw(ByteSpan derInt, uint8_t * rawInt, const uint16_t rawIntLen) |
| { |
| VerifyOrReturnError(!derInt.empty(), CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(rawInt != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| const uint8_t * derIntData = derInt.data(); |
| size_t derIntLen = derInt.size(); |
| |
| /* one leading zero is allowed for positive integer in ASN1 DER format */ |
| if (*derIntData == 0) |
| { |
| derIntData++; |
| derIntLen--; |
| } |
| |
| VerifyOrReturnError(derIntLen <= rawIntLen, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| if (derIntLen > 0) |
| { |
| VerifyOrReturnError(*derIntData != 0, CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| memset(rawInt, 0, (rawIntLen - derIntLen)); |
| memcpy(rawInt + (rawIntLen - derIntLen), derIntData, derIntLen); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ConvertECDSASignatureRawToDER(P256ECDSASignatureSpan rawSig, MutableByteSpan & derSig) |
| { |
| VerifyOrReturnError(derSig.size() >= kMax_ECDSA_Signature_Length_Der, CHIP_ERROR_BUFFER_TOO_SMALL); |
| |
| ASN1Writer writer; |
| writer.Init(derSig); |
| ReturnErrorOnFailure(ConvertECDSASignatureRawToDER(rawSig, writer)); |
| derSig.reduce_size(writer.GetLengthWritten()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ConvertECDSASignatureRawToDER(P256ECDSASignatureSpan rawSig, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| uint8_t derInt[kP256_FE_Length + kEmitDerIntegerWithoutTagOverhead]; |
| |
| // Ecdsa-Sig-Value ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // r INTEGER |
| { |
| MutableByteSpan derIntSpan(derInt, sizeof(derInt)); |
| ReturnErrorOnFailure(ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data()), derIntSpan)); |
| ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false, derIntSpan.data(), |
| static_cast<uint16_t>(derIntSpan.size()))); |
| } |
| |
| // s INTEGER |
| { |
| MutableByteSpan derIntSpan(derInt, sizeof(derInt)); |
| ReturnErrorOnFailure(ConvertIntegerRawToDerWithoutTag(P256IntegerSpan(rawSig.data() + kP256_FE_Length), derIntSpan)); |
| ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false, derIntSpan.data(), |
| static_cast<uint16_t>(derIntSpan.size()))); |
| } |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR ConvertECDSAKeypairRawToDER(const P256SerializedKeypair & rawKeypair, MutableByteSpan & outDerKeypair) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| // The raw key pair contains the public key followed by the private key |
| VerifyOrReturnError(rawKeypair.Length() == kP256_PublicKey_Length + kP256_PrivateKey_Length, CHIP_ERROR_INVALID_ARGUMENT); |
| FixedByteSpan<kP256_PublicKey_Length> publicKey(rawKeypair.ConstBytes()); |
| FixedByteSpan<kP256_PrivateKey_Length> privateKey(rawKeypair.ConstBytes() + kP256_PublicKey_Length); |
| |
| ASN1Writer writer; |
| writer.Init(outDerKeypair); |
| |
| // ECPrivateKey ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1) |
| ASN1_ENCODE_INTEGER(1); |
| |
| // privateKey OCTET STRING |
| ASN1_ENCODE_OCTET_STRING(privateKey.data(), privateKey.size()); |
| |
| // parameters [0] ECParameters {{ NamedCurve }} OPTIONAL |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0); |
| { |
| ASN1_ENCODE_OBJECT_ID(kOID_EllipticCurve_prime256v1); |
| } |
| ASN1_END_CONSTRUCTED; |
| |
| // publicKey [1] BIT STRING OPTIONAL |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 1); |
| { |
| ReturnErrorOnFailure(writer.PutBitString(0, publicKey.data(), publicKey.size())); |
| } |
| ASN1_END_CONSTRUCTED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| outDerKeypair.reduce_size(writer.GetLengthWritten()); |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR ExtractNodeIdFabricIdFromOpCert(const ChipCertificateData & opcert, NodeId * outNodeId, FabricId * outFabricId) |
| { |
| // Since we assume the cert is pre-validated, we are going to assume that |
| // its subject in fact has both a node id and a fabric id. |
| VerifyOrReturnError(outNodeId != nullptr && outFabricId != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| NodeId nodeId = 0; |
| FabricId fabricId = kUndefinedFabricId; |
| bool foundNodeId = false; |
| bool foundFabricId = false; |
| |
| const ChipDN & subjectDN = opcert.mSubjectDN; |
| for (uint8_t i = 0; i < subjectDN.RDNCount(); ++i) |
| { |
| const auto & rdn = subjectDN.rdn[i]; |
| if (rdn.mAttrOID == ASN1::kOID_AttributeType_MatterNodeId) |
| { |
| nodeId = rdn.mChipVal; |
| foundNodeId = true; |
| } |
| else if (rdn.mAttrOID == ASN1::kOID_AttributeType_MatterFabricId) |
| { |
| fabricId = rdn.mChipVal; |
| foundFabricId = true; |
| } |
| } |
| if (!foundNodeId || !foundFabricId) |
| { |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| *outNodeId = nodeId; |
| *outFabricId = fabricId; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractNodeIdFabricIdCompressedFabricIdFromOpCerts(ByteSpan rcac, ByteSpan noc, CompressedFabricId & compressedFabricId, |
| FabricId & fabricId, NodeId & nodeId) |
| { |
| Crypto::P256PublicKey rootPubKey; |
| Credentials::P256PublicKeySpan rootPubKeySpan; |
| ReturnErrorOnFailure(ExtractPublicKeyFromChipCert(rcac, rootPubKeySpan)); |
| rootPubKey = Crypto::P256PublicKey(rootPubKeySpan); |
| ReturnErrorOnFailure(Credentials::ExtractNodeIdFabricIdFromOpCert(noc, &nodeId, &fabricId)); |
| ReturnErrorOnFailure(GenerateCompressedFabricId(rootPubKey, fabricId, compressedFabricId)); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractNodeIdCompressedFabricIdFromOpCerts(ByteSpan rcac, ByteSpan noc, CompressedFabricId & compressedFabricId, |
| NodeId & nodeId) |
| { |
| FabricId fabricId; |
| ReturnErrorOnFailure(ExtractNodeIdFabricIdCompressedFabricIdFromOpCerts(rcac, noc, compressedFabricId, fabricId, nodeId)); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractFabricIdFromCert(const ChipCertificateData & cert, FabricId * fabricId) |
| { |
| const ChipDN & subjectDN = cert.mSubjectDN; |
| for (uint8_t i = 0; i < subjectDN.RDNCount(); ++i) |
| { |
| const auto & rdn = subjectDN.rdn[i]; |
| if (rdn.mAttrOID == ASN1::kOID_AttributeType_MatterFabricId) |
| { |
| *fabricId = rdn.mChipVal; |
| return CHIP_NO_ERROR; |
| } |
| } |
| |
| return CHIP_ERROR_NOT_FOUND; |
| } |
| |
| CHIP_ERROR ExtractCATsFromOpCert(const ByteSpan & opcert, CATValues & cats) |
| { |
| ChipCertificateSet certSet; |
| ChipCertificateData certData; |
| |
| ReturnErrorOnFailure(certSet.Init(&certData, 1)); |
| |
| ReturnErrorOnFailure(certSet.LoadCert(opcert, BitFlags<CertDecodeFlags>())); |
| |
| return ExtractCATsFromOpCert(certData, cats); |
| } |
| |
| CHIP_ERROR ExtractCATsFromOpCert(const ChipCertificateData & opcert, CATValues & cats) |
| { |
| uint8_t catCount = 0; |
| CertType certType; |
| |
| ReturnErrorOnFailure(opcert.mSubjectDN.GetCertType(certType)); |
| VerifyOrReturnError(certType == CertType::kNode, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| const ChipDN & subjectDN = opcert.mSubjectDN; |
| for (uint8_t i = 0; i < subjectDN.RDNCount(); ++i) |
| { |
| const auto & rdn = subjectDN.rdn[i]; |
| if (rdn.mAttrOID == ASN1::kOID_AttributeType_MatterCASEAuthTag) |
| { |
| // This error should never happen in practice because valid NOC cannot have more |
| // than kMaxSubjectCATAttributeCount CATs in its subject. The check that it is |
| // valid NOC was done above. |
| VerifyOrReturnError(catCount != cats.size(), CHIP_ERROR_BUFFER_TOO_SMALL); |
| VerifyOrReturnError(CanCastTo<CASEAuthTag>(rdn.mChipVal), CHIP_ERROR_INVALID_ARGUMENT); |
| cats.values[catCount++] = static_cast<CASEAuthTag>(rdn.mChipVal); |
| } |
| } |
| for (size_t i = catCount; i < cats.size(); ++i) |
| { |
| cats.values[i] = kUndefinedCAT; |
| } |
| |
| // Make sure the set contained valid data, otherwise it's an invalid cert |
| VerifyOrReturnError(cats.AreValid(), CHIP_ERROR_WRONG_CERT_DN); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractFabricIdFromCert(const ByteSpan & opcert, FabricId * fabricId) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(opcert, certData)); |
| return ExtractFabricIdFromCert(certData, fabricId); |
| } |
| |
| CHIP_ERROR ExtractNodeIdFabricIdFromOpCert(const ByteSpan & opcert, NodeId * nodeId, FabricId * fabricId) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(opcert, certData)); |
| return ExtractNodeIdFabricIdFromOpCert(certData, nodeId, fabricId); |
| } |
| |
| CHIP_ERROR ExtractPublicKeyFromChipCert(const ByteSpan & chipCert, P256PublicKeySpan & publicKey) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(chipCert, certData)); |
| publicKey = certData.mPublicKey; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractNotBeforeFromChipCert(const ByteSpan & chipCert, chip::System::Clock::Seconds32 & notBeforeChipEpochTime) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(chipCert, certData)); |
| notBeforeChipEpochTime = chip::System::Clock::Seconds32(certData.mNotBeforeTime); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractSKIDFromChipCert(const ByteSpan & chipCert, CertificateKeyId & skid) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(chipCert, certData)); |
| VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kExtPresent_AuthKeyId), CHIP_ERROR_NOT_FOUND); |
| skid = certData.mSubjectKeyId; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractSubjectDNFromChipCert(const ByteSpan & chipCert, ChipDN & dn) |
| { |
| ChipCertificateData certData; |
| ReturnErrorOnFailure(DecodeChipCert(chipCert, certData)); |
| dn = certData.mSubjectDN; |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR ExtractSubjectDNFromX509Cert(const ByteSpan & x509Cert, ChipDN & dn) |
| { |
| CHIP_ERROR err; |
| ASN1Reader reader; |
| |
| VerifyOrReturnError(CanCastTo<uint32_t>(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; |
| } |
| |
| CHIP_ERROR CertificateValidityPolicy::ApplyDefaultPolicy(const ChipCertificateData * cert, uint8_t depth, |
| CertificateValidityResult result) |
| { |
| switch (result) |
| { |
| case CertificateValidityResult::kValid: |
| case CertificateValidityResult::kNotExpiredAtLastKnownGoodTime: |
| // By default, we do not enforce certificate validity based upon a Last |
| // Known Good Time source. However, implementations may always inject a |
| // policy that does enforce based upon this. |
| case CertificateValidityResult::kExpiredAtLastKnownGoodTime: |
| case CertificateValidityResult::kTimeUnknown: |
| return CHIP_NO_ERROR; |
| |
| case CertificateValidityResult::kNotYetValid: |
| return CHIP_ERROR_CERT_NOT_VALID_YET; |
| |
| case CertificateValidityResult::kExpired: |
| return CHIP_ERROR_CERT_EXPIRED; |
| |
| default: |
| return CHIP_ERROR_INTERNAL; |
| } |
| } |
| |
| void InitNetworkIdentitySubject(ChipDN & name) |
| { |
| name.Clear(); |
| CHIP_ERROR err = name.AddAttribute_CommonName(kNetworkIdentityCN, /* not printable */ false); |
| VerifyOrDie(err == CHIP_NO_ERROR); // AddAttribute can't fail in this case |
| } |
| |
| 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(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); |
| |
| VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kExtPresent_BasicConstraints) && |
| !certData.mCertFlags.Has(CertFlags::kIsCA), |
| CHIP_ERROR_WRONG_CERT_TYPE); |
| VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kExtPresent_KeyUsage) && |
| certData.mKeyUsageFlags == kNetworkIdentityKeyUsage, |
| CHIP_ERROR_WRONG_CERT_TYPE); |
| VerifyOrReturnError(certData.mCertFlags.Has(CertFlags::kExtPresent_ExtendedKeyUsage) && |
| certData.mKeyPurposeFlags == kNetworkIdentityKeyPurpose, |
| CHIP_ERROR_WRONG_CERT_TYPE); |
| |
| ReturnErrorOnFailure(VerifyCertSignature(certData, certData)); |
| 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 |