| /* |
| * |
| * Copyright (c) 2020-2022 Project CHIP Authors |
| * 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 methods for converting a CHIP |
| * TLV-encoded certificate to a standard X.509 certificate. |
| * |
| */ |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| |
| #include <inttypes.h> |
| #include <stddef.h> |
| |
| #include <credentials/CHIPCert_Internal.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/CodeUtils.h> |
| #include <lib/support/DLLUtil.h> |
| #include <lib/support/SafeInt.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; |
| |
| static CHIP_ERROR DecodeConvertDN(TLVReader & reader, ASN1Writer & writer, ChipDN & dn) |
| { |
| ReturnErrorOnFailure(dn.DecodeFromTLV(reader)); |
| ReturnErrorOnFailure(dn.EncodeToASN1(writer)); |
| return CHIP_NO_ERROR; |
| } |
| |
| static CHIP_ERROR DecodeConvertValidity(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| ASN1UniversalTime asn1Time; |
| |
| ASN1_START_SEQUENCE |
| { |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_NotBefore))); |
| ReturnErrorOnFailure(reader.Get(certData.mNotBeforeTime)); |
| ReturnErrorOnFailure(ChipEpochToASN1Time(certData.mNotBeforeTime, asn1Time)); |
| ASN1_ENCODE_TIME(asn1Time); |
| |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_NotAfter))); |
| ReturnErrorOnFailure(reader.Get(certData.mNotAfterTime)); |
| ReturnErrorOnFailure(ChipEpochToASN1Time(certData.mNotAfterTime, asn1Time)); |
| ASN1_ENCODE_TIME(asn1Time); |
| |
| // Perform this check if NotAfter value is different from Never-Expire value. |
| if (certData.mNotAfterTime != kNullCertTime) |
| { |
| VerifyOrReturnError(certData.mNotBeforeTime < certData.mNotAfterTime, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
| } |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertSubjectPublicKeyInfo(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| uint8_t pubKeyAlgoId, pubKeyCurveId; |
| |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_PublicKeyAlgorithm))); |
| ReturnErrorOnFailure(reader.Get(pubKeyAlgoId)); |
| |
| certData.mPubKeyAlgoOID = GetOID(kOIDCategory_PubKeyAlgo, pubKeyAlgoId); |
| VerifyOrReturnError(certData.mPubKeyAlgoOID == kOID_PubKeyAlgo_ECPublicKey, CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
| |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_EllipticCurveIdentifier))); |
| ReturnErrorOnFailure(reader.Get(pubKeyCurveId)); |
| |
| certData.mPubKeyCurveOID = GetOID(kOIDCategory_EllipticCurve, pubKeyCurveId); |
| VerifyOrReturnError(certData.mPubKeyCurveOID == kOID_EllipticCurve_prime256v1, CHIP_ERROR_UNSUPPORTED_ELLIPTIC_CURVE); |
| |
| // subjectPublicKeyInfo SubjectPublicKeyInfo, |
| ASN1_START_SEQUENCE |
| { |
| // algorithm AlgorithmIdentifier, |
| // AlgorithmIdentifier ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // algorithm OBJECT IDENTIFIER, |
| ASN1_ENCODE_OBJECT_ID(certData.mPubKeyAlgoOID); |
| |
| // EcpkParameters ::= CHOICE { |
| // ecParameters ECParameters, |
| // namedCurve OBJECT IDENTIFIER, |
| // implicitlyCA NULL } |
| // |
| // (Only namedCurve supported). |
| // |
| ASN1_ENCODE_OBJECT_ID(certData.mPubKeyCurveOID); |
| } |
| ASN1_END_SEQUENCE; |
| |
| ReturnErrorOnFailure(reader.Next(kTLVType_ByteString, ContextTag(kTag_EllipticCurvePublicKey))); |
| ReturnErrorOnFailure(reader.Get(certData.mPublicKey)); |
| |
| static_assert(P256PublicKeySpan().size() <= UINT16_MAX, "Public key size doesn't fit in a uint16_t"); |
| |
| // For EC certs, the subjectPublicKey BIT STRING contains the X9.62 encoded EC point. |
| ReturnErrorOnFailure(writer.PutBitString(0, certData.mPublicKey.data(), static_cast<uint16_t>(certData.mPublicKey.size()))); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertAuthorityKeyIdentifierExtension(TLVReader & reader, ASN1Writer & writer, |
| ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| |
| certData.mCertFlags.Set(CertFlags::kExtPresent_AuthKeyId); |
| |
| // AuthorityKeyIdentifier extension MUST be marked as non-critical (default). |
| |
| // AuthorityKeyIdentifier ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // keyIdentifier [0] IMPLICIT KeyIdentifier |
| // KeyIdentifier ::= OCTET STRING |
| ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_AuthorityKeyIdentifier))); |
| ReturnErrorOnFailure(reader.Get(certData.mAuthKeyId)); |
| |
| static_assert(CertificateKeyId().size() <= UINT16_MAX, "Authority key id size doesn't fit in a uint16_t"); |
| |
| ReturnErrorOnFailure(writer.PutOctetString(kASN1TagClass_ContextSpecific, 0, certData.mAuthKeyId.data(), |
| static_cast<uint16_t>(certData.mAuthKeyId.size()))); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertSubjectKeyIdentifierExtension(TLVReader & reader, ASN1Writer & writer, |
| ChipCertificateData & certData) |
| { |
| certData.mCertFlags.Set(CertFlags::kExtPresent_SubjectKeyId); |
| |
| // SubjectKeyIdentifier extension MUST be marked as non-critical (default). |
| |
| // SubjectKeyIdentifier ::= KeyIdentifier |
| // KeyIdentifier ::= OCTET STRING |
| ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_SubjectKeyIdentifier))); |
| ReturnErrorOnFailure(reader.Get(certData.mSubjectKeyId)); |
| |
| static_assert(CertificateKeyId().size() <= UINT16_MAX, "Subject key id size doesn't fit in a uint16_t"); |
| |
| ReturnErrorOnFailure( |
| writer.PutOctetString(certData.mSubjectKeyId.data(), static_cast<uint16_t>(certData.mSubjectKeyId.size()))); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| static CHIP_ERROR DecodeConvertKeyUsageExtension(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| uint16_t keyUsageBits; |
| |
| certData.mCertFlags.Set(CertFlags::kExtPresent_KeyUsage); |
| |
| // KeyUsage ::= BIT STRING |
| ReturnErrorOnFailure(reader.Expect(ContextTag(kTag_KeyUsage))); |
| ReturnErrorOnFailure(reader.Get(keyUsageBits)); |
| |
| { |
| BitFlags<KeyUsageFlags> keyUsageFlags(keyUsageBits); |
| VerifyOrReturnError( |
| keyUsageFlags.HasOnly(KeyUsageFlags::kDigitalSignature, KeyUsageFlags::kNonRepudiation, KeyUsageFlags::kKeyEncipherment, |
| KeyUsageFlags::kDataEncipherment, KeyUsageFlags::kKeyAgreement, KeyUsageFlags::kKeyCertSign, |
| KeyUsageFlags::kCRLSign, KeyUsageFlags::kEncipherOnly, KeyUsageFlags::kEncipherOnly), |
| CHIP_ERROR_UNSUPPORTED_CERT_FORMAT); |
| |
| ASN1_ENCODE_BIT_STRING(keyUsageBits); |
| |
| certData.mKeyUsageFlags = keyUsageFlags; |
| } |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertBasicConstraintsExtension(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| TLVType outerContainer; |
| |
| certData.mCertFlags.Set(CertFlags::kExtPresent_BasicConstraints); |
| |
| // BasicConstraints ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| ReturnErrorOnFailure(reader.Expect(kTLVType_Structure, ContextTag(kTag_BasicConstraints))); |
| ReturnErrorOnFailure(reader.EnterContainer(outerContainer)); |
| |
| // cA BOOLEAN DEFAULT FALSE |
| { |
| bool isCA; |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_BasicConstraints_IsCA))); |
| ReturnErrorOnFailure(reader.Get(isCA)); |
| |
| if (isCA) |
| { |
| ASN1_ENCODE_BOOLEAN(true); |
| certData.mCertFlags.Set(CertFlags::kIsCA); |
| } |
| |
| err = reader.Next(); |
| VerifyOrReturnError(err == CHIP_NO_ERROR || err == CHIP_END_OF_TLV, err); |
| } |
| |
| // pathLenConstraint INTEGER (0..MAX) OPTIONAL |
| if (reader.GetTag() == ContextTag(kTag_BasicConstraints_PathLenConstraint)) |
| { |
| ReturnErrorOnFailure(reader.Get(certData.mPathLenConstraint)); |
| |
| ASN1_ENCODE_INTEGER(certData.mPathLenConstraint); |
| |
| certData.mCertFlags.Set(CertFlags::kPathLenConstraintPresent); |
| |
| err = reader.Next(); |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| } |
| |
| ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
| ReturnErrorOnFailure(reader.ExitContainer(outerContainer)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertExtendedKeyUsageExtension(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| TLVType outerContainer; |
| |
| certData.mCertFlags.Set(CertFlags::kExtPresent_ExtendedKeyUsage); |
| |
| // ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId |
| ASN1_START_SEQUENCE |
| { |
| ReturnErrorOnFailure(reader.Expect(kTLVType_Array, ContextTag(kTag_ExtendedKeyUsage))); |
| ReturnErrorOnFailure(reader.EnterContainer(outerContainer)); |
| |
| while ((err = reader.Next(AnonymousTag())) == CHIP_NO_ERROR) |
| { |
| uint8_t keyPurposeId; |
| ReturnErrorOnFailure(reader.Get(keyPurposeId)); |
| |
| // KeyPurposeId ::= OBJECT IDENTIFIER |
| ASN1_ENCODE_OBJECT_ID(GetOID(kOIDCategory_KeyPurpose, keyPurposeId)); |
| |
| certData.mKeyPurposeFlags.Set(static_cast<KeyPurposeFlags>(0x01 << (keyPurposeId - 1))); |
| } |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| ReturnErrorOnFailure(reader.ExitContainer(outerContainer)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertFutureExtension(TLVReader & tlvReader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| ByteSpan extensionSequence; |
| ASN1Reader reader; |
| |
| ReturnErrorOnFailure(tlvReader.Expect(kTLVType_ByteString, ContextTag(kTag_FutureExtension))); |
| ReturnErrorOnFailure(tlvReader.Get(extensionSequence)); |
| |
| reader.Init(extensionSequence); |
| |
| // Extension ::= SEQUENCE |
| ASN1_PARSE_ENTER_SEQUENCE |
| { |
| OID extensionOID; |
| bool critical = false; |
| |
| ASN1_PARSE_OBJECT_ID(extensionOID); |
| |
| VerifyOrReturnError(extensionOID == kOID_Unknown, ASN1_ERROR_UNSUPPORTED_ENCODING); |
| |
| // critical BOOLEAN DEFAULT FALSE, |
| ASN1_PARSE_ANY; |
| if (reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Boolean) |
| { |
| ASN1_GET_BOOLEAN(critical); |
| |
| if (critical) |
| { |
| certData.mCertFlags.Set(CertFlags::kExtPresent_FutureIsCritical); |
| } |
| |
| ASN1_PARSE_ANY; |
| } |
| } |
| ASN1_EXIT_SEQUENCE; |
| |
| VerifyOrReturnError(CanCastTo<uint16_t>(extensionSequence.size()), ASN1_ERROR_INVALID_ENCODING); |
| |
| // FutureExtension SEQUENCE |
| ReturnErrorOnFailure(writer.PutConstructedType(extensionSequence.data(), static_cast<uint16_t>(extensionSequence.size()))); |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertExtension(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| Tag tlvTag; |
| uint32_t extensionTagNum; |
| |
| tlvTag = reader.GetTag(); |
| VerifyOrReturnError(IsContextTag(tlvTag), CHIP_ERROR_INVALID_TLV_TAG); |
| extensionTagNum = TagNumFromTag(tlvTag); |
| |
| if (extensionTagNum == kTag_FutureExtension) |
| { |
| ReturnErrorOnFailure(DecodeConvertFutureExtension(reader, writer, certData)); |
| } |
| else |
| { |
| // Extension ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // extnID OBJECT IDENTIFIER, |
| ASN1_ENCODE_OBJECT_ID(GetOID(kOIDCategory_Extension, static_cast<uint8_t>(extensionTagNum))); |
| |
| // BasicConstraints, KeyUsage and ExtKeyUsage extensions MUST be marked as critical. |
| if (extensionTagNum == kTag_KeyUsage || extensionTagNum == kTag_BasicConstraints || |
| extensionTagNum == kTag_ExtendedKeyUsage) |
| { |
| ASN1_ENCODE_BOOLEAN(true); |
| } |
| |
| // extnValue OCTET STRING |
| // -- contains the DER encoding of an ASN.1 value |
| // -- corresponding to the extension type identified |
| // -- by extnID |
| ASN1_START_OCTET_STRING_ENCAPSULATED |
| { |
| if (extensionTagNum == kTag_AuthorityKeyIdentifier) |
| { |
| ReturnErrorOnFailure(DecodeConvertAuthorityKeyIdentifierExtension(reader, writer, certData)); |
| } |
| else if (extensionTagNum == kTag_SubjectKeyIdentifier) |
| { |
| ReturnErrorOnFailure(DecodeConvertSubjectKeyIdentifierExtension(reader, writer, certData)); |
| } |
| else if (extensionTagNum == kTag_KeyUsage) |
| { |
| ReturnErrorOnFailure(DecodeConvertKeyUsageExtension(reader, writer, certData)); |
| } |
| else if (extensionTagNum == kTag_BasicConstraints) |
| { |
| ReturnErrorOnFailure(DecodeConvertBasicConstraintsExtension(reader, writer, certData)); |
| } |
| else if (extensionTagNum == kTag_ExtendedKeyUsage) |
| { |
| ReturnErrorOnFailure(DecodeConvertExtendedKeyUsageExtension(reader, writer, certData)); |
| } |
| else |
| { |
| return CHIP_ERROR_UNSUPPORTED_CERT_FORMAT; |
| } |
| } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| } |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeConvertExtensions(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| TLVType outerContainer; |
| |
| ReturnErrorOnFailure(reader.Next(kTLVType_List, ContextTag(kTag_Extensions))); |
| ReturnErrorOnFailure(reader.EnterContainer(outerContainer)); |
| |
| // extensions [3] EXPLICIT Extensions OPTIONAL |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 3) |
| { |
| // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension |
| ASN1_START_SEQUENCE |
| { |
| // Read certificate extension in the List. |
| while ((err = reader.Next()) == CHIP_NO_ERROR) |
| { |
| ReturnErrorOnFailure(DecodeConvertExtension(reader, writer, certData)); |
| } |
| VerifyOrReturnError(err == CHIP_END_OF_TLV, err); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_CONSTRUCTED; |
| |
| ReturnErrorOnFailure(reader.ExitContainer(outerContainer)); |
| |
| exit: |
| return err; |
| } |
| |
| static CHIP_ERROR DecodeECDSASignature(TLVReader & reader, ChipCertificateData & certData) |
| { |
| ReturnErrorOnFailure(reader.Next(kTLVType_ByteString, ContextTag(kTag_ECDSASignature))); |
| ReturnErrorOnFailure(reader.Get(certData.mSignature)); |
| return CHIP_NO_ERROR; |
| } |
| |
| static CHIP_ERROR DecodeConvertECDSASignature(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ReturnErrorOnFailure(DecodeECDSASignature(reader, certData)); |
| |
| // Converting the signature is a bit of work, so explicitly check if we have a null writer |
| ReturnErrorCodeIf(writer.IsNullWriter(), CHIP_NO_ERROR); |
| |
| // signatureValue BIT STRING |
| // Per RFC3279, the ECDSA signature value is encoded in DER encapsulated in the signatureValue BIT STRING. |
| ASN1_START_BIT_STRING_ENCAPSULATED |
| { |
| ReturnErrorOnFailure(ConvertECDSASignatureRawToDER(certData.mSignature, writer)); |
| } |
| ASN1_END_ENCAPSULATED; |
| |
| exit: |
| return err; |
| } |
| |
| /** |
| * @brief Decode and convert the To-Be-Signed (TBS) portion of the CHIP certificate |
| * into X.509 DER encoded form. |
| * |
| * @param reader A TLVReader positioned at the beginning of the TBS portion |
| * (certificate serial number) of the CHIP certificates. |
| * @param writer A reference to the ASN1Writer to store DER encoded TBS portion of |
| * the CHIP certificate. |
| * @param certData Structure containing data extracted from the TBS portion of the |
| * CHIP certificate. |
| * |
| * Note: The reader must be positioned on the SerialNumber element. |
| * |
| * @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise |
| **/ |
| static CHIP_ERROR DecodeConvertTBSCert(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| |
| // tbsCertificate TBSCertificate, |
| // TBSCertificate ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // version [0] EXPLICIT Version DEFAULT v1 |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) |
| { |
| // Version ::= INTEGER { v1(0), v2(1), v3(2) } |
| ASN1_ENCODE_INTEGER(2); |
| } |
| ASN1_END_CONSTRUCTED; |
| |
| // serialNumber CertificateSerialNumber |
| // CertificateSerialNumber ::= INTEGER |
| ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_SerialNumber))); |
| ReturnErrorOnFailure(reader.Get(certData.mSerialNumber)); |
| ReturnErrorOnFailure(writer.PutValue(kASN1TagClass_Universal, kASN1UniversalTag_Integer, false, |
| certData.mSerialNumber.data(), static_cast<uint16_t>(certData.mSerialNumber.size()))); |
| |
| // signature AlgorithmIdentifier |
| // AlgorithmIdentifier ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| uint8_t sigAlgoId; |
| ReturnErrorOnFailure(reader.Next(ContextTag(kTag_SignatureAlgorithm))); |
| ReturnErrorOnFailure(reader.Get(sigAlgoId)); |
| |
| certData.mSigAlgoOID = GetOID(kOIDCategory_SigAlgo, sigAlgoId); |
| ASN1_ENCODE_OBJECT_ID(certData.mSigAlgoOID); |
| } |
| ASN1_END_SEQUENCE; |
| |
| // issuer Name |
| ReturnErrorOnFailure(reader.Next(kTLVType_List, ContextTag(kTag_Issuer))); |
| ReturnErrorOnFailure(DecodeConvertDN(reader, writer, certData.mIssuerDN)); |
| |
| // validity Validity, |
| ReturnErrorOnFailure(DecodeConvertValidity(reader, writer, certData)); |
| |
| // subject Name |
| ReturnErrorOnFailure(reader.Next(kTLVType_List, ContextTag(kTag_Subject))); |
| ReturnErrorOnFailure(DecodeConvertDN(reader, writer, certData.mSubjectDN)); |
| |
| // subjectPublicKeyInfo SubjectPublicKeyInfo, |
| ReturnErrorOnFailure(DecodeConvertSubjectPublicKeyInfo(reader, writer, certData)); |
| |
| // certificate extensions |
| ReturnErrorOnFailure(DecodeConvertExtensions(reader, writer, certData)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| /** |
| * Variant of DecodeConvertTBSCert that handles reading a compact-pdc-identity |
| * where only the subject public key is actually encoded. All other values are |
| * populated / written as the well-known values mandated by the specification. |
| * |
| * Note: The reader must be positioned on the EllipticCurvePublicKey element. |
| */ |
| static CHIP_ERROR DecodeConvertTBSCertCompactIdentity(TLVReader & reader, ASN1Writer & writer, ChipCertificateData & certData) |
| { |
| // Decode the public key, everything else is rigid |
| ReturnErrorOnFailure(reader.Expect(kTLVType_ByteString, ContextTag(kTag_EllipticCurvePublicKey))); |
| ReturnErrorOnFailure(reader.Get(certData.mPublicKey)); |
| |
| // Populate rigid ChipCertificateData fields |
| certData.mSerialNumber = kNetworkIdentitySerialNumberBytes; |
| certData.mSigAlgoOID = kOID_SigAlgo_ECDSAWithSHA256; |
| InitNetworkIdentitySubject(certData.mIssuerDN); |
| certData.mNotBeforeTime = kNetworkIdentityNotBeforeTime; |
| certData.mNotAfterTime = kNetworkIdentityNotAfterTime; |
| InitNetworkIdentitySubject(certData.mSubjectDN); |
| certData.mPubKeyAlgoOID = kOID_PubKeyAlgo_ECPublicKey; |
| certData.mPubKeyCurveOID = kOID_EllipticCurve_prime256v1; |
| certData.mCertFlags.Set(CertFlags::kExtPresent_BasicConstraints); |
| certData.mCertFlags.Set(CertFlags::kExtPresent_KeyUsage); |
| certData.mKeyUsageFlags = kNetworkIdentityKeyUsage; |
| certData.mCertFlags.Set(CertFlags::kExtPresent_ExtendedKeyUsage); |
| certData.mKeyPurposeFlags = kNetworkIdentityKeyPurpose; |
| |
| if (!writer.IsNullWriter()) |
| { |
| ReturnErrorOnFailure(EncodeNetworkIdentityTBSCert(certData.mPublicKey, writer)); |
| } |
| return CHIP_NO_ERROR; |
| } |
| |
| /** |
| * Decode a CHIP TLV certificate and convert it to X.509 DER form. |
| * |
| * This helper function takes separate ASN1Writers for the whole Certificate |
| * and the TBSCertificate, to allow the caller to control which part (if any) |
| * to capture. |
| * |
| * If `writer` is NOT a null writer, then `tbsWriter` MUST be a reference |
| * to the same writer, otherwise the overall Certificate written will not be |
| * valid. |
| */ |
| static CHIP_ERROR DecodeConvertCert(TLVReader & reader, ASN1Writer & writer, ASN1Writer & tbsWriter, ChipCertificateData & certData) |
| { |
| CHIP_ERROR err; |
| TLVType containerType; |
| |
| if (reader.GetType() == kTLVType_NotSpecified) |
| { |
| ReturnErrorOnFailure(reader.Next()); |
| } |
| ReturnErrorOnFailure(reader.Expect(kTLVType_Structure, AnonymousTag())); |
| ReturnErrorOnFailure(reader.EnterContainer(containerType)); |
| |
| // Certificate ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| // tbsCertificate TBSCertificate, |
| reader.Next(); |
| if (reader.GetTag() == ContextTag(kTag_EllipticCurvePublicKey)) |
| { |
| // If the struct starts with the ec-pub-key we're dealing with a |
| // Network (Client) Identity in compact-pdc-identity format. |
| DecodeConvertTBSCertCompactIdentity(reader, tbsWriter, certData); |
| } |
| else |
| { |
| ReturnErrorOnFailure(DecodeConvertTBSCert(reader, tbsWriter, certData)); |
| } |
| |
| // signatureAlgorithm AlgorithmIdentifier |
| // AlgorithmIdentifier ::= SEQUENCE |
| ASN1_START_SEQUENCE |
| { |
| ASN1_ENCODE_OBJECT_ID(static_cast<OID>(certData.mSigAlgoOID)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| // signatureValue BIT STRING |
| ReturnErrorOnFailure(DecodeConvertECDSASignature(reader, writer, certData)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| // Verify no more elements in certificate. |
| ReturnErrorOnFailure(reader.VerifyEndOfContainer()); |
| ReturnErrorOnFailure(reader.ExitContainer(containerType)); |
| |
| exit: |
| return err; |
| } |
| |
| DLL_EXPORT CHIP_ERROR ConvertChipCertToX509Cert(const ByteSpan chipCert, MutableByteSpan & x509Cert) |
| { |
| TLVReader reader; |
| ASN1Writer writer; |
| ChipCertificateData certData; |
| |
| reader.Init(chipCert); |
| |
| writer.Init(x509Cert); |
| |
| certData.Clear(); |
| |
| ReturnErrorOnFailure(DecodeConvertCert(reader, writer, writer, certData)); |
| |
| x509Cert.reduce_size(writer.GetLengthWritten()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR DecodeChipCert(const ByteSpan chipCert, ChipCertificateData & certData, BitFlags<CertDecodeFlags> decodeFlags) |
| { |
| TLVReader reader; |
| |
| reader.Init(chipCert); |
| return DecodeChipCert(reader, certData, decodeFlags); |
| } |
| |
| CHIP_ERROR DecodeChipCert(TLVReader & reader, ChipCertificateData & certData, BitFlags<CertDecodeFlags> decodeFlags) |
| { |
| ASN1Writer nullWriter; |
| nullWriter.InitNullWriter(); |
| |
| certData.Clear(); |
| |
| if (decodeFlags.Has(CertDecodeFlags::kGenerateTBSHash)) |
| { |
| // Create a buffer and writer to capture the TBS (to-be-signed) portion of the certificate |
| // when we decode (and convert) the certificate, so we can hash it to create the TBSHash. |
| chip::Platform::ScopedMemoryBuffer<uint8_t> asn1TBSBuf; |
| VerifyOrReturnError(asn1TBSBuf.Alloc(kMaxCHIPCertDecodeBufLength), CHIP_ERROR_NO_MEMORY); |
| ASN1Writer tbsWriter; |
| tbsWriter.Init(asn1TBSBuf.Get(), kMaxCHIPCertDecodeBufLength); |
| |
| ReturnErrorOnFailure(DecodeConvertCert(reader, nullWriter, tbsWriter, certData)); |
| |
| // Hash the encoded TBS certificate. Only SHA256 is supported. |
| VerifyOrReturnError(certData.mSigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, CHIP_ERROR_UNSUPPORTED_SIGNATURE_TYPE); |
| ReturnErrorOnFailure(Hash_SHA256(asn1TBSBuf.Get(), tbsWriter.GetLengthWritten(), certData.mTBSHash)); |
| certData.mCertFlags.Set(CertFlags::kTBSHashPresent); |
| } |
| else |
| { |
| ReturnErrorOnFailure(DecodeConvertCert(reader, nullWriter, nullWriter, certData)); |
| } |
| |
| // If requested by the caller, mark the certificate as trusted. |
| if (decodeFlags.Has(CertDecodeFlags::kIsTrustAnchor)) |
| { |
| certData.mCertFlags.Set(CertFlags::kIsTrustAnchor); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR DecodeChipDN(TLVReader & reader, ChipDN & dn) |
| { |
| ASN1Writer writer; |
| |
| writer.InitNullWriter(); |
| |
| dn.Clear(); |
| |
| return DecodeConvertDN(reader, writer, dn); |
| } |
| |
| } // namespace Credentials |
| } // namespace chip |