/* * * 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 standard X.509 * certificate to a CHIP TLV-encoded certificate. * */ #include #include #include #include #include #include #include #include #include #include #include #include namespace chip { namespace Credentials { using namespace chip::ASN1; using namespace chip::TLV; using namespace chip::Protocols; using namespace chip::Crypto; static CHIP_ERROR ConvertDistinguishedName(ASN1Reader & reader, TLVWriter & writer, Tag tag) { ChipDN dn; ReturnErrorOnFailure(dn.DecodeFromASN1(reader)); return dn.EncodeToTLV(writer, tag); } static CHIP_ERROR ConvertValidity(ASN1Reader & reader, TLVWriter & writer) { CHIP_ERROR err; ASN1UniversalTime asn1Time; uint32_t chipEpochTimeNotBefore; uint32_t chipEpochTimeNotAfter; ASN1_PARSE_ENTER_SEQUENCE { ASN1_PARSE_TIME(asn1Time); ReturnErrorOnFailure(ASN1ToChipEpochTime(asn1Time, chipEpochTimeNotBefore)); ASN1_PARSE_TIME(asn1Time); ReturnErrorOnFailure(ASN1ToChipEpochTime(asn1Time, chipEpochTimeNotAfter)); // Perform this check if NotAfter value is different from Never-Expire value. if (chipEpochTimeNotAfter != kNullCertTime) { VerifyOrReturnError(chipEpochTimeNotBefore < chipEpochTimeNotAfter, ASN1_ERROR_INVALID_ENCODING); } ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotBefore), chipEpochTimeNotBefore)); ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotAfter), chipEpochTimeNotAfter)); } ASN1_EXIT_SEQUENCE; exit: return err; } static CHIP_ERROR ConvertSubjectPublicKeyInfo(ASN1Reader & reader, TLVWriter & writer) { CHIP_ERROR err; OID pubKeyAlgoOID, pubKeyCurveOID; // subjectPublicKeyInfo SubjectPublicKeyInfo, ASN1_PARSE_ENTER_SEQUENCE { // algorithm AlgorithmIdentifier, // AlgorithmIdentifier ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { // algorithm OBJECT IDENTIFIER, ASN1_PARSE_OBJECT_ID(pubKeyAlgoOID); // Verify that the algorithm type is supported. VerifyOrExit(pubKeyAlgoOID == kOID_PubKeyAlgo_ECPublicKey, err = ASN1_ERROR_UNSUPPORTED_ENCODING); err = writer.Put(ContextTag(kTag_PublicKeyAlgorithm), GetOIDEnum(pubKeyAlgoOID)); SuccessOrExit(err); // EcpkParameters ::= CHOICE { // ecParameters ECParameters, // namedCurve OBJECT IDENTIFIER, // implicitlyCA NULL } ASN1_PARSE_ANY; // ecParameters and implicitlyCA not supported. if (reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Sequence) { ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); } if (reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Null) { ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); } ASN1_VERIFY_TAG(kASN1TagClass_Universal, kASN1UniversalTag_ObjectId); ASN1_GET_OBJECT_ID(pubKeyCurveOID); // Verify the curve name is recognized. VerifyOrExit(GetOIDCategory(pubKeyCurveOID) == kOIDCategory_EllipticCurve, err = ASN1_ERROR_UNSUPPORTED_ENCODING); err = writer.Put(ContextTag(kTag_EllipticCurveIdentifier), GetOIDEnum(pubKeyCurveOID)); SuccessOrExit(err); } ASN1_EXIT_SEQUENCE; // subjectPublicKey BIT STRING ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_BitString); // Verify public key length. VerifyOrExit(reader.GetValueLen() > 0, err = ASN1_ERROR_INVALID_ENCODING); // The first byte is Unused Bit Count value, which should be zero. VerifyOrExit(reader.GetValue()[0] == 0, err = ASN1_ERROR_INVALID_ENCODING); // Copy the X9.62 encoded EC point into the CHIP certificate as a byte string. // Skip the first Unused Bit Count byte. err = writer.PutBytes(ContextTag(kTag_EllipticCurvePublicKey), reader.GetValue() + 1, reader.GetValueLen() - 1); SuccessOrExit(err); } ASN1_EXIT_SEQUENCE; exit: return err; } static CHIP_ERROR ConvertExtension(ASN1Reader & reader, TLVWriter & writer) { CHIP_ERROR err; TLVType outerContainer; OID extensionOID; bool critical = false; const uint8_t * extensionSequence; uint32_t extensionSequenceLen; err = reader.GetConstructedType(extensionSequence, extensionSequenceLen); SuccessOrExit(err); // Extension ::= SEQUENCE ASN1_ENTER_SEQUENCE { // extnID OBJECT IDENTIFIER, ASN1_PARSE_OBJECT_ID(extensionOID); // The kOID_Unknown will be interpreted and encoded as future-extension. if (extensionOID != kOID_Unknown) { VerifyOrExit(GetOIDCategory(extensionOID) == kOIDCategory_Extension, err = ASN1_ERROR_INVALID_ENCODING); } // critical BOOLEAN DEFAULT FALSE, ASN1_PARSE_ANY; if (reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Boolean) { ASN1_GET_BOOLEAN(critical); VerifyOrExit(critical, err = ASN1_ERROR_INVALID_ENCODING); ASN1_PARSE_ANY; } // extnValue OCTET STRING // -- contains the DER encoding of an ASN.1 value // -- corresponding to the extension type identified // -- by extnID ASN1_ENTER_ENCAPSULATED(kASN1TagClass_Universal, kASN1UniversalTag_OctetString) { if (extensionOID == kOID_Extension_AuthorityKeyIdentifier) { // This extension MUST be marked as non-critical. VerifyOrExit(!critical, err = ASN1_ERROR_INVALID_ENCODING); // AuthorityKeyIdentifier ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { err = reader.Next(); SuccessOrExit(err); // keyIdentifier [0] IMPLICIT KeyIdentifier, // KeyIdentifier ::= OCTET STRING VerifyOrExit(reader.GetClass() == kASN1TagClass_ContextSpecific && reader.GetTag() == 0, err = ASN1_ERROR_INVALID_ENCODING); VerifyOrExit(reader.IsConstructed() == false, err = ASN1_ERROR_INVALID_ENCODING); VerifyOrExit(reader.GetValueLen() == kKeyIdentifierLength, err = ASN1_ERROR_INVALID_ENCODING); err = writer.PutBytes(ContextTag(kTag_AuthorityKeyIdentifier), reader.GetValue(), reader.GetValueLen()); SuccessOrExit(err); err = reader.Next(); VerifyOrExit(err == ASN1_END, err = ASN1_ERROR_INVALID_ENCODING); } ASN1_EXIT_SEQUENCE; } else if (extensionOID == kOID_Extension_SubjectKeyIdentifier) { // This extension MUST be marked as non-critical. VerifyOrExit(!critical, err = ASN1_ERROR_INVALID_ENCODING); // SubjectKeyIdentifier ::= KeyIdentifier // KeyIdentifier ::= OCTET STRING ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_OctetString); VerifyOrExit(reader.GetValueLen() == kKeyIdentifierLength, err = ASN1_ERROR_INVALID_ENCODING); err = writer.PutBytes(ContextTag(kTag_SubjectKeyIdentifier), reader.GetValue(), reader.GetValueLen()); SuccessOrExit(err); } else if (extensionOID == kOID_Extension_KeyUsage) { // This extension MUST be marked as critical. VerifyOrExit(critical, err = ASN1_ERROR_INVALID_ENCODING); // KeyUsage ::= BIT STRING ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_BitString); uint32_t keyUsageBits; err = reader.GetBitString(keyUsageBits); SuccessOrExit(err); VerifyOrExit(CanCastTo(keyUsageBits), err = ASN1_ERROR_INVALID_ENCODING); // Check that only supported flags are set. BitFlags keyUsageFlags(static_cast(keyUsageBits)); VerifyOrExit(keyUsageFlags.HasOnly( KeyUsageFlags::kDigitalSignature, KeyUsageFlags::kNonRepudiation, KeyUsageFlags::kKeyEncipherment, KeyUsageFlags::kDataEncipherment, KeyUsageFlags::kKeyAgreement, KeyUsageFlags::kKeyCertSign, KeyUsageFlags::kCRLSign, KeyUsageFlags::kEncipherOnly, KeyUsageFlags::kEncipherOnly), err = ASN1_ERROR_INVALID_ENCODING); err = writer.Put(ContextTag(kTag_KeyUsage), keyUsageBits); SuccessOrExit(err); } else if (extensionOID == kOID_Extension_BasicConstraints) { // This extension MUST be marked as critical. VerifyOrExit(critical, err = ASN1_ERROR_INVALID_ENCODING); // BasicConstraints ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { bool isCA = false; int64_t pathLenConstraint = -1; // cA BOOLEAN DEFAULT FALSE err = reader.Next(); if (err == CHIP_NO_ERROR && reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Boolean) { ASN1_GET_BOOLEAN(isCA); VerifyOrExit(isCA, err = ASN1_ERROR_INVALID_ENCODING); err = reader.Next(); } // pathLenConstraint INTEGER (0..MAX) OPTIONAL if (err == CHIP_NO_ERROR && reader.GetClass() == kASN1TagClass_Universal && reader.GetTag() == kASN1UniversalTag_Integer) { ASN1_GET_INTEGER(pathLenConstraint); VerifyOrExit(CanCastTo(pathLenConstraint), err = ASN1_ERROR_INVALID_ENCODING); // pathLenConstraint is present only when cA is TRUE VerifyOrExit(isCA, err = ASN1_ERROR_INVALID_ENCODING); } err = writer.StartContainer(ContextTag(kTag_BasicConstraints), kTLVType_Structure, outerContainer); SuccessOrExit(err); // Set also when cA is FALSE err = writer.PutBoolean(ContextTag(kTag_BasicConstraints_IsCA), isCA); SuccessOrExit(err); if (pathLenConstraint != -1) { err = writer.Put(ContextTag(kTag_BasicConstraints_PathLenConstraint), static_cast(pathLenConstraint)); SuccessOrExit(err); } err = writer.EndContainer(outerContainer); SuccessOrExit(err); } ASN1_EXIT_SEQUENCE; } else if (extensionOID == kOID_Extension_ExtendedKeyUsage) { // This extension MUST be marked as critical. VerifyOrExit(critical, err = ASN1_ERROR_INVALID_ENCODING); err = writer.StartContainer(ContextTag(kTag_ExtendedKeyUsage), kTLVType_Array, outerContainer); SuccessOrExit(err); // ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId ASN1_PARSE_ENTER_SEQUENCE { while ((err = reader.Next()) == CHIP_NO_ERROR) { // KeyPurposeId ::= OBJECT IDENTIFIER OID keyPurposeOID; ASN1_GET_OBJECT_ID(keyPurposeOID); VerifyOrExit(keyPurposeOID != kOID_Unknown, err = ASN1_ERROR_UNSUPPORTED_ENCODING); VerifyOrExit(GetOIDCategory(keyPurposeOID) == kOIDCategory_KeyPurpose, err = ASN1_ERROR_INVALID_ENCODING); err = writer.Put(AnonymousTag(), GetOIDEnum(keyPurposeOID)); SuccessOrExit(err); } if (err != ASN1_END) { SuccessOrExit(err); } } ASN1_EXIT_SEQUENCE; err = writer.EndContainer(outerContainer); SuccessOrExit(err); } // Any other extension is treated as FutureExtension else { err = writer.PutBytes(ContextTag(kTag_FutureExtension), extensionSequence, extensionSequenceLen); SuccessOrExit(err); ASN1_PARSE_ANY; } } ASN1_EXIT_ENCAPSULATED; } ASN1_EXIT_SEQUENCE; exit: return err; } static CHIP_ERROR ConvertExtensions(ASN1Reader & reader, TLVWriter & writer) { CHIP_ERROR err; TLVType containerType; err = writer.StartContainer(ContextTag(kTag_Extensions), kTLVType_List, containerType); SuccessOrExit(err); // Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension ASN1_PARSE_ENTER_SEQUENCE { while ((err = reader.Next()) == CHIP_NO_ERROR) { err = ConvertExtension(reader, writer); SuccessOrExit(err); } if (err != ASN1_END) { SuccessOrExit(err); } } ASN1_EXIT_SEQUENCE; err = writer.EndContainer(containerType); SuccessOrExit(err); exit: return err; } CHIP_ERROR ConvertECDSASignatureDERToRaw(ASN1Reader & reader, TLVWriter & writer, Tag tag) { CHIP_ERROR err = CHIP_NO_ERROR; uint8_t rawSig[kP256_ECDSA_Signature_Length_Raw]; // Per RFC3279, the ECDSA signature value is encoded in DER encapsulated in the signatureValue BIT STRING. ASN1_ENTER_ENCAPSULATED(kASN1TagClass_Universal, kASN1UniversalTag_BitString) { // Ecdsa-Sig-Value ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { // r INTEGER ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Integer); ReturnErrorOnFailure( ConvertIntegerDERToRaw(ByteSpan(reader.GetValue(), reader.GetValueLen()), rawSig, kP256_FE_Length)); // s INTEGER ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Integer); ReturnErrorOnFailure(ConvertIntegerDERToRaw(ByteSpan(reader.GetValue(), reader.GetValueLen()), rawSig + kP256_FE_Length, kP256_FE_Length)); } ASN1_EXIT_SEQUENCE; } ASN1_EXIT_ENCAPSULATED; ReturnErrorOnFailure(writer.PutBytes(tag, rawSig, kP256_ECDSA_Signature_Length_Raw)); exit: return err; } static CHIP_ERROR ConvertCertificate(ASN1Reader & reader, TLVWriter & writer, Tag tag) { CHIP_ERROR err; int64_t version; OID sigAlgoOID; TLVType containerType; err = writer.StartContainer(tag, kTLVType_Structure, containerType); SuccessOrExit(err); // Certificate ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { // tbsCertificate TBSCertificate, // TBSCertificate ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { // version [0] EXPLICIT Version DEFAULT v1 ASN1_PARSE_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) { // Version ::= INTEGER { v1(0), v2(1), v3(2) } ASN1_PARSE_INTEGER(version); // Verify that the X.509 certificate version is v3 VerifyOrExit(version == 2, err = ASN1_ERROR_UNSUPPORTED_ENCODING); } ASN1_EXIT_CONSTRUCTED; // serialNumber CertificateSerialNumber // CertificateSerialNumber ::= INTEGER ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Integer); err = writer.PutBytes(ContextTag(kTag_SerialNumber), reader.GetValue(), reader.GetValueLen()); SuccessOrExit(err); // signature AlgorithmIdentifier // AlgorithmIdentifier ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { // algorithm OBJECT IDENTIFIER, ASN1_PARSE_OBJECT_ID(sigAlgoOID); VerifyOrExit(sigAlgoOID == kOID_SigAlgo_ECDSAWithSHA256, err = ASN1_ERROR_UNSUPPORTED_ENCODING); err = writer.Put(ContextTag(kTag_SignatureAlgorithm), GetOIDEnum(sigAlgoOID)); SuccessOrExit(err); } ASN1_EXIT_SEQUENCE; // issuer Name err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Issuer)); SuccessOrExit(err); // validity Validity, err = ConvertValidity(reader, writer); SuccessOrExit(err); // subject Name, err = ConvertDistinguishedName(reader, writer, ContextTag(kTag_Subject)); SuccessOrExit(err); err = ConvertSubjectPublicKeyInfo(reader, writer); SuccessOrExit(err); err = reader.Next(); // issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, // Not supported. if (err == CHIP_NO_ERROR && reader.GetClass() == kASN1TagClass_ContextSpecific && reader.GetTag() == 1) { ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); } // subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, // Not supported. if (err == CHIP_NO_ERROR && reader.GetClass() == kASN1TagClass_ContextSpecific && reader.GetTag() == 2) { ExitNow(err = ASN1_ERROR_UNSUPPORTED_ENCODING); } // extensions [3] EXPLICIT Extensions OPTIONAL if (err == CHIP_NO_ERROR && reader.GetClass() == kASN1TagClass_ContextSpecific && reader.GetTag() == 3) { ASN1_ENTER_CONSTRUCTED(kASN1TagClass_ContextSpecific, 3) { err = ConvertExtensions(reader, writer); SuccessOrExit(err); } ASN1_EXIT_CONSTRUCTED; err = reader.Next(); } if (err != ASN1_END) { ExitNow(); } } ASN1_EXIT_SEQUENCE; // signatureAlgorithm AlgorithmIdentifier // AlgorithmIdentifier ::= SEQUENCE ASN1_PARSE_ENTER_SEQUENCE { OID localSigAlgoOID; // algorithm OBJECT IDENTIFIER, ASN1_PARSE_OBJECT_ID(localSigAlgoOID); // Verify that the signatureAlgorithm is the same as the "signature" field in TBSCertificate. VerifyOrExit(localSigAlgoOID == sigAlgoOID, err = ASN1_ERROR_UNSUPPORTED_ENCODING); } ASN1_EXIT_SEQUENCE; // signatureValue BIT STRING ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_BitString); ReturnErrorOnFailure(ConvertECDSASignatureDERToRaw(reader, writer, ContextTag(kTag_ECDSASignature))); } ASN1_EXIT_SEQUENCE; err = writer.EndContainer(containerType); SuccessOrExit(err); exit: return err; } CHIP_ERROR ConvertX509CertToChipCert(const ByteSpan x509Cert, MutableByteSpan & chipCert) { ASN1Reader reader; TLVWriter writer; VerifyOrReturnError(!x509Cert.empty(), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(CanCastTo(x509Cert.size()), CHIP_ERROR_INVALID_ARGUMENT); reader.Init(x509Cert); writer.Init(chipCert); ReturnErrorOnFailure(ConvertCertificate(reader, writer, AnonymousTag())); ReturnErrorOnFailure(writer.Finalize()); chipCert.reduce_size(writer.GetLengthWritten()); return CHIP_NO_ERROR; } } // namespace Credentials } // namespace chip