| /* |
| * |
| * Copyright (c) 2021-2023 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 utility functions for reading, writing, |
| * parsing, resigning, encoding, and decoding CHIP certificates. |
| * |
| */ |
| |
| #define __STDC_FORMAT_MACROS |
| |
| #include "chip-cert.h" |
| #include <lib/core/CHIPEncoding.h> |
| #include <lib/support/BytesToHex.h> |
| |
| #include <string> |
| |
| using namespace chip; |
| using namespace chip::Credentials; |
| using namespace chip::ASN1; |
| using namespace chip::TLV; |
| using namespace chip::Encoding; |
| |
| bool ToolChipDN::SetCertName(X509_NAME * name) const |
| { |
| bool res = true; |
| uint8_t rdnCount = RDNCount(); |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| int attrNID; |
| switch (rdn[i].mAttrOID) |
| { |
| case kOID_AttributeType_CommonName: |
| attrNID = NID_commonName; |
| break; |
| case kOID_AttributeType_Surname: |
| attrNID = NID_surname; |
| break; |
| case kOID_AttributeType_SerialNumber: |
| attrNID = NID_serialNumber; |
| break; |
| case kOID_AttributeType_CountryName: |
| attrNID = NID_countryName; |
| break; |
| case kOID_AttributeType_LocalityName: |
| attrNID = NID_localityName; |
| break; |
| case kOID_AttributeType_StateOrProvinceName: |
| attrNID = NID_stateOrProvinceName; |
| break; |
| case kOID_AttributeType_OrganizationName: |
| attrNID = NID_organizationName; |
| break; |
| case kOID_AttributeType_OrganizationalUnitName: |
| attrNID = NID_organizationalUnitName; |
| break; |
| case kOID_AttributeType_Title: |
| attrNID = NID_title; |
| break; |
| case kOID_AttributeType_Name: |
| attrNID = NID_name; |
| break; |
| case kOID_AttributeType_GivenName: |
| attrNID = NID_givenName; |
| break; |
| case kOID_AttributeType_Initials: |
| attrNID = NID_initials; |
| break; |
| case kOID_AttributeType_GenerationQualifier: |
| attrNID = NID_generationQualifier; |
| break; |
| case kOID_AttributeType_DNQualifier: |
| attrNID = NID_dnQualifier; |
| break; |
| case kOID_AttributeType_Pseudonym: |
| attrNID = NID_pseudonym; |
| break; |
| case kOID_AttributeType_DomainComponent: |
| attrNID = NID_domainComponent; |
| break; |
| case kOID_AttributeType_MatterNodeId: |
| attrNID = gNIDChipNodeId; |
| break; |
| case kOID_AttributeType_MatterFirmwareSigningId: |
| attrNID = gNIDChipFirmwareSigningId; |
| break; |
| case kOID_AttributeType_MatterICACId: |
| attrNID = gNIDChipICAId; |
| break; |
| case kOID_AttributeType_MatterRCACId: |
| attrNID = gNIDChipRootId; |
| break; |
| case kOID_AttributeType_MatterFabricId: |
| attrNID = gNIDChipFabricId; |
| break; |
| case kOID_AttributeType_MatterCASEAuthTag: |
| attrNID = gNIDChipCASEAuthenticatedTag; |
| break; |
| default: |
| ExitNow(res = false); |
| } |
| |
| char chipAttrStr[std::max(kChip64bitAttrUTF8Length, kChip32bitAttrUTF8Length)] = { 0 }; |
| int type = V_ASN1_UTF8STRING; |
| uint8_t * attrStr = reinterpret_cast<uint8_t *>(chipAttrStr); |
| int attrLen = 0; |
| |
| if (IsChip64bitDNAttr(rdn[i].mAttrOID)) |
| { |
| VerifyOrReturnError(Encoding::Uint64ToHex(rdn[i].mChipVal, chipAttrStr, kChip64bitAttrUTF8Length, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| attrLen = kChip64bitAttrUTF8Length; |
| } |
| else if (IsChip32bitDNAttr(rdn[i].mAttrOID)) |
| { |
| VerifyOrReturnError(Encoding::Uint32ToHex(static_cast<uint32_t>(rdn[i].mChipVal), chipAttrStr, kChip32bitAttrUTF8Length, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| attrLen = kChip32bitAttrUTF8Length; |
| } |
| else |
| { |
| if (rdn[i].mAttrOID == kOID_AttributeType_DomainComponent) |
| { |
| type = V_ASN1_IA5STRING; |
| } |
| else if (rdn[i].mAttrIsPrintableString) |
| { |
| type = V_ASN1_PRINTABLESTRING; |
| } |
| attrStr = reinterpret_cast<uint8_t *>(const_cast<char *>(rdn[i].mString.data())); |
| attrLen = static_cast<int>(rdn[i].mString.size()); |
| } |
| |
| if (!X509_NAME_add_entry_by_NID(name, attrNID, type, attrStr, attrLen, -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool ToolChipDN::HasAttr(chip::ASN1::OID oid) const |
| { |
| uint8_t rdnCount = RDNCount(); |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| if (oid == rdn[i].mAttrOID) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void ToolChipDN::PrintDN(FILE * file, const char * name) const |
| { |
| uint8_t rdnCount = RDNCount(); |
| char valueStr[128]; |
| const char * certDesc = nullptr; |
| |
| fprintf(file, "%s: ", name); |
| |
| for (uint8_t i = 0; i < rdnCount; i++) |
| { |
| if (IsChip64bitDNAttr(rdn[i].mAttrOID)) |
| { |
| Encoding::Uint64ToHex(rdn[i].mChipVal, valueStr, sizeof(valueStr), Encoding::HexFlags::kUppercaseAndNullTerminate); |
| } |
| else if (IsChip32bitDNAttr(rdn[i].mAttrOID)) |
| { |
| Encoding::Uint32ToHex(static_cast<uint32_t>(rdn[i].mChipVal), valueStr, sizeof(valueStr), |
| Encoding::HexFlags::kUppercaseAndNullTerminate); |
| } |
| else |
| { |
| size_t len = rdn[i].mString.size(); |
| if (len > sizeof(valueStr) - 1) |
| { |
| len = sizeof(valueStr) - 1; |
| } |
| memcpy(valueStr, rdn[i].mString.data(), len); |
| valueStr[len] = 0; |
| } |
| |
| fprintf(file, "%s=%s", chip::ASN1::GetOIDName(rdn[i].mAttrOID), valueStr); |
| if (certDesc != nullptr) |
| { |
| fprintf(file, " (%s)", certDesc); |
| } |
| } |
| } |
| |
| namespace { |
| |
| template <size_t N> |
| bool HasRawPrefix(const uint8_t * buffer, size_t len, const uint8_t (&prefix)[N]) |
| { |
| return len >= N && memcmp(buffer, prefix, N) == 0; |
| } |
| |
| bool HasStringPrefix(const uint8_t * buffer, size_t len, const char * prefix) |
| { |
| size_t prefixLen = strlen(prefix); |
| return len >= prefixLen && memcmp(buffer, prefix, prefixLen) == 0; |
| } |
| |
| CertFormat DetectCertFormat(const uint8_t * cert, uint32_t certLen) |
| { |
| static const uint8_t chipRawPrefix[] = { 0x15, 0x30, 0x01 }; |
| static const char * chipHexPrefix = "153001"; |
| static const char * chipB64Prefix = "FTAB"; |
| static const uint8_t chipCompactPdcRawPrefix[] = { 0x15, 0x30, 0x09 }; |
| static const char * chipCompactPdcHexPrefix = "153009"; |
| static const char * chipCompactPdcB64Prefix = "FTAJ"; |
| static const uint8_t derRawPrefix[] = { 0x30, 0x82 }; |
| static const char * derHexPrefix = "30820"; |
| static const char * pemMarker = "-----BEGIN CERTIFICATE-----"; |
| |
| VerifyOrReturnError(cert != nullptr, kCertFormat_Unknown); |
| |
| if (HasRawPrefix(cert, certLen, chipRawPrefix) || HasRawPrefix(cert, certLen, chipCompactPdcRawPrefix)) |
| { |
| return kCertFormat_Chip_Raw; |
| } |
| |
| if (HasStringPrefix(cert, certLen, chipHexPrefix) || HasStringPrefix(cert, certLen, chipCompactPdcHexPrefix)) |
| { |
| return kCertFormat_Chip_Hex; |
| } |
| |
| if (HasStringPrefix(cert, certLen, chipB64Prefix) || HasStringPrefix(cert, certLen, chipCompactPdcB64Prefix)) |
| { |
| return kCertFormat_Chip_Base64; |
| } |
| |
| if (HasRawPrefix(cert, certLen, derRawPrefix)) |
| { |
| return kCertFormat_X509_DER; |
| } |
| |
| if (HasStringPrefix(cert, certLen, derHexPrefix)) |
| { |
| return kCertFormat_X509_Hex; |
| } |
| |
| if (ContainsPEMMarker(pemMarker, cert, certLen)) |
| { |
| return kCertFormat_X509_PEM; |
| } |
| |
| return kCertFormat_Unknown; |
| } |
| |
| bool SetCertSerialNumber(X509 * cert, uint64_t value = kUseRandomSerialNumber) |
| { |
| bool res = true; |
| uint64_t rnd; |
| ASN1_INTEGER * snInt = X509_get_serialNumber(cert); |
| |
| if (value == kUseRandomSerialNumber) |
| { |
| // Generate a random value to be used as the serial number. |
| if (!RAND_bytes(reinterpret_cast<uint8_t *>(&rnd), sizeof(rnd))) |
| { |
| ReportOpenSSLErrorAndExit("RAND_bytes", res = false); |
| } |
| |
| // Avoid negative numbers. |
| value = rnd & 0x7FFFFFFFFFFFFFFFULL; |
| } |
| |
| // Store the serial number as an ASN1 integer value within the certificate. |
| if (ASN1_INTEGER_set_uint64(snInt, value) == 0) |
| { |
| ReportOpenSSLErrorAndExit("ASN1_INTEGER_set_uint64", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool SetCertTimeField(ASN1_TIME * asn1Time, const struct tm & value) |
| { |
| char timeStr[ASN1UniversalTime::kASN1TimeStringMaxLength + 1]; |
| MutableCharSpan timeSpan(timeStr); |
| ASN1UniversalTime val = { .Year = static_cast<uint16_t>((value.tm_year == kX509NoWellDefinedExpirationDateYear) |
| ? kX509NoWellDefinedExpirationDateYear |
| : (value.tm_year + 1900)), |
| .Month = static_cast<uint8_t>(value.tm_mon + 1), |
| .Day = static_cast<uint8_t>(value.tm_mday), |
| .Hour = static_cast<uint8_t>(value.tm_hour), |
| .Minute = static_cast<uint8_t>(value.tm_min), |
| .Second = static_cast<uint8_t>(value.tm_sec) }; |
| |
| if (val.ExportTo_ASN1_TIME_string(timeSpan) != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "ExportTo_ASN1_TIME_string() failed\n"); |
| return false; |
| } |
| |
| timeSpan.data()[timeSpan.size()] = '\0'; |
| |
| if (!ASN1_TIME_set_string(asn1Time, timeStr)) |
| { |
| fprintf(stderr, "OpenSSL ASN1_TIME_set_string() failed\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SetValidityTime(X509 * cert, const struct tm & validFrom, uint32_t validDays, CertStructConfig & certConfig) |
| { |
| bool res = true; |
| struct tm validFromLocal; |
| struct tm validTo; |
| time_t validToTime; |
| |
| // Compute the validity end date. |
| // Note that this computation is done in local time, despite the fact that the certificate validity times are |
| // UTC. This is because the standard posix time functions do not make it easy to convert a struct tm containing |
| // UTC to a time_t value without manipulating the TZ environment variable. |
| if (validDays == kCertValidDays_NoWellDefinedExpiration) |
| { |
| validTo.tm_year = kX509NoWellDefinedExpirationDateYear; |
| validTo.tm_mon = kMonthsPerYear - 1; |
| validTo.tm_mday = kMaxDaysPerMonth; |
| validTo.tm_hour = kHoursPerDay - 1; |
| validTo.tm_min = kMinutesPerHour - 1; |
| validTo.tm_sec = kSecondsPerMinute - 1; |
| validTo.tm_isdst = -1; |
| } |
| else |
| { |
| validTo = validFrom; |
| validTo.tm_mday += validDays; |
| validTo.tm_sec -= 1; // Ensure validity period is exactly a multiple of a day. |
| validTo.tm_isdst = -1; |
| validToTime = mktime(&validTo); |
| |
| if (validToTime == static_cast<time_t>(-1)) |
| { |
| fprintf(stderr, "mktime() failed\n"); |
| ExitNow(res = false); |
| } |
| localtime_r(&validToTime, &validTo); |
| } |
| |
| if (certConfig.IsValidityCorrect()) |
| { |
| validFromLocal = validFrom; |
| } |
| else |
| { |
| // Switch values if error flag is set. |
| validFromLocal = validTo; |
| validTo = validFrom; |
| } |
| |
| // Set the certificate's notBefore date. |
| if (certConfig.IsValidityNotBeforePresent()) |
| { |
| res = SetCertTimeField(X509_get_notBefore(cert), validFromLocal); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Set the certificate's notAfter date. |
| if (certConfig.IsValidityNotAfterPresent()) |
| { |
| res = SetCertTimeField(X509_get_notAfter(cert), validTo); |
| VerifyTrueOrExit(res); |
| } |
| |
| exit: |
| return true; |
| } |
| |
| bool AddExtension(X509 * cert, int extNID, const char * extStr) |
| { |
| bool res = true; |
| std::unique_ptr<X509_EXTENSION, void (*)(X509_EXTENSION *)> ex( |
| X509V3_EXT_nconf_nid(nullptr, nullptr, extNID, const_cast<char *>(extStr)), &X509_EXTENSION_free); |
| |
| if (!X509_add_ext(cert, ex.get(), -1)) |
| { |
| ReportOpenSSLErrorAndExit("X509_add_ext", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool SetBasicConstraintsExtension(X509 * cert, bool isCA, int pathLen, CertStructConfig & certConfig) |
| { |
| if (!certConfig.IsExtensionBasicPresent()) |
| { |
| return true; |
| } |
| |
| std::string basicConstraintsExt; |
| |
| if (certConfig.IsExtensionBasicCriticalPresent() && certConfig.IsExtensionBasicCritical()) |
| { |
| basicConstraintsExt += "critical"; |
| } |
| |
| if (certConfig.IsExtensionBasicCAPresent()) |
| { |
| if (!basicConstraintsExt.empty()) |
| { |
| basicConstraintsExt += ","; |
| } |
| if ((certConfig.IsExtensionBasicCACorrect() && !isCA) || (!certConfig.IsExtensionBasicCACorrect() && isCA)) |
| { |
| basicConstraintsExt += "CA:FALSE"; |
| } |
| else |
| { |
| basicConstraintsExt += "CA:TRUE"; |
| } |
| } |
| |
| if (pathLen != kPathLength_NotSpecified) |
| { |
| if (!basicConstraintsExt.empty()) |
| { |
| basicConstraintsExt += ","; |
| } |
| basicConstraintsExt.append("pathlen:" + std::to_string(pathLen)); |
| } |
| |
| return AddExtension(cert, NID_basic_constraints, basicConstraintsExt.c_str()); |
| } |
| |
| bool SetKeyUsageExtension(X509 * cert, bool isCA, CertStructConfig & certConfig) |
| { |
| if (!certConfig.IsExtensionKeyUsagePresent()) |
| { |
| return true; |
| } |
| |
| std::string keyUsageExt; |
| |
| if (certConfig.IsExtensionKeyUsageCriticalPresent() && certConfig.IsExtensionKeyUsageCritical()) |
| { |
| keyUsageExt += "critical"; |
| } |
| |
| if ((certConfig.IsExtensionKeyUsageDigitalSigCorrect() && !isCA) || |
| (!certConfig.IsExtensionKeyUsageDigitalSigCorrect() && isCA)) |
| { |
| if (!keyUsageExt.empty()) |
| { |
| keyUsageExt += ","; |
| } |
| keyUsageExt += "digitalSignature"; |
| } |
| |
| if ((certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && isCA) || |
| (!certConfig.IsExtensionKeyUsageKeyCertSignCorrect() && !isCA)) |
| { |
| if (!keyUsageExt.empty()) |
| { |
| keyUsageExt += ","; |
| } |
| keyUsageExt += "keyCertSign"; |
| } |
| |
| if ((certConfig.IsExtensionKeyUsageCRLSignCorrect() && isCA) || (!certConfig.IsExtensionKeyUsageCRLSignCorrect() && !isCA)) |
| { |
| if (!keyUsageExt.empty()) |
| { |
| keyUsageExt += ","; |
| } |
| keyUsageExt += "cRLSign"; |
| } |
| |
| // In test mode only: just add an extra extension flag to prevent empty extantion. |
| if (certConfig.IsErrorTestCaseEnabled() && (keyUsageExt.empty() || (keyUsageExt.compare("critical") == 0))) |
| { |
| if (!keyUsageExt.empty()) |
| { |
| keyUsageExt += ","; |
| } |
| keyUsageExt += "keyEncipherment"; |
| } |
| |
| return AddExtension(cert, NID_key_usage, keyUsageExt.c_str()); |
| } |
| |
| /** The key identifier field is derived from the public key using method (1) per RFC5280 (section 4.2.1.2): |
| * |
| * (1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the |
| * value of the BIT STRING subjectPublicKey (excluding the tag, |
| * length, and number of unused bits). |
| */ |
| bool AddSubjectKeyId(X509 * cert, bool isSKIDLengthValid) |
| { |
| bool res = true; |
| ASN1_BIT_STRING * pk = X509_get0_pubkey_bitstr(cert); |
| unsigned char pkHash[EVP_MAX_MD_SIZE]; |
| unsigned int pkHashLen; |
| std::unique_ptr<ASN1_STRING, void (*)(ASN1_STRING *)> pkHashOS(ASN1_STRING_type_new(V_ASN1_OCTET_STRING), &ASN1_STRING_free); |
| |
| if (!EVP_Digest(pk->data, static_cast<size_t>(pk->length), pkHash, &pkHashLen, EVP_sha1(), nullptr)) |
| { |
| ReportOpenSSLErrorAndExit("EVP_Digest", res = false); |
| } |
| |
| if (pkHashLen != kKeyIdentifierLength) |
| { |
| fprintf(stderr, "Unexpected hash length returned from EVP_Digest()\n"); |
| ExitNow(res = false); |
| } |
| |
| if (!isSKIDLengthValid) |
| { |
| pkHashLen--; |
| } |
| |
| if (!ASN1_STRING_set(pkHashOS.get(), pkHash, static_cast<int>(pkHashLen))) |
| { |
| ReportOpenSSLErrorAndExit("ASN1_STRING_set", res = false); |
| } |
| |
| if (!X509_add1_ext_i2d(cert, NID_subject_key_identifier, pkHashOS.get(), 0, X509V3_ADD_APPEND)) |
| { |
| ReportOpenSSLErrorAndExit("X509_add1_ext_i2d", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool AddAuthorityKeyId(X509 * cert, X509 * caCert, bool isAKIDLengthValid) |
| { |
| bool res = true; |
| int isCritical; |
| int index = 0; |
| std::unique_ptr<AUTHORITY_KEYID, void (*)(AUTHORITY_KEYID *)> akid(AUTHORITY_KEYID_new(), &AUTHORITY_KEYID_free); |
| |
| akid->keyid = reinterpret_cast<ASN1_OCTET_STRING *>(X509_get_ext_d2i(caCert, NID_subject_key_identifier, &isCritical, &index)); |
| if (akid->keyid == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("X509_get_ext_d2i", res = false); |
| } |
| |
| if (!isAKIDLengthValid) |
| { |
| akid->keyid->length = 19; |
| } |
| |
| if (!X509_add1_ext_i2d(cert, NID_authority_key_identifier, akid.get(), 0, X509V3_ADD_APPEND)) |
| { |
| ReportOpenSSLErrorAndExit("X509_add1_ext_i2d", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| } // namespace |
| |
| bool ReadCert(const char * fileNameOrStr, std::unique_ptr<X509, void (*)(X509 *)> & cert) |
| { |
| CertFormat origCertFmt; |
| return ReadCert(fileNameOrStr, cert, origCertFmt); |
| } |
| |
| bool ReadCert(const char * fileNameOrStr, std::unique_ptr<X509, void (*)(X509 *)> & cert, CertFormat & certFmt) |
| { |
| bool res = true; |
| uint32_t certLen = 0; |
| std::unique_ptr<uint8_t[]> certBuf; |
| |
| // If fileNameOrStr is a file name |
| if (access(fileNameOrStr, R_OK) == 0) |
| { |
| res = ReadFileIntoMem(fileNameOrStr, nullptr, certLen); |
| VerifyTrueOrExit(res); |
| |
| certBuf = std::unique_ptr<uint8_t[]>(new uint8_t[certLen]); |
| |
| res = ReadFileIntoMem(fileNameOrStr, certBuf.get(), certLen); |
| VerifyTrueOrExit(res); |
| |
| certFmt = DetectCertFormat(certBuf.get(), certLen); |
| if (certFmt == kCertFormat_Unknown) |
| { |
| fprintf(stderr, "Unrecognized Cert Format in File: %s\n", fileNameOrStr); |
| return false; |
| } |
| } |
| // Otherwise, treat fileNameOrStr as a pointer to the certificate string |
| else |
| { |
| certLen = static_cast<uint32_t>(strlen(fileNameOrStr)); |
| |
| certFmt = DetectCertFormat(reinterpret_cast<const uint8_t *>(fileNameOrStr), certLen); |
| if (certFmt == kCertFormat_Unknown) |
| { |
| fprintf(stderr, "Unrecognized Cert Format in the Input Argument: %s\n", fileNameOrStr); |
| return false; |
| } |
| |
| certBuf = std::unique_ptr<uint8_t[]>(new uint8_t[certLen]); |
| memcpy(certBuf.get(), fileNameOrStr, certLen); |
| } |
| |
| if ((certFmt == kCertFormat_X509_Hex) || (certFmt == kCertFormat_Chip_Hex)) |
| { |
| size_t len = chip::Encoding::HexToBytes(Uint8::to_char(certBuf.get()), certLen, certBuf.get(), certLen); |
| VerifyOrReturnError(CanCastTo<uint32_t>(2 * len), false); |
| VerifyOrReturnError(2 * len == certLen, false); |
| certLen = static_cast<uint32_t>(len); |
| } |
| |
| if (certFmt == kCertFormat_X509_PEM) |
| { |
| VerifyOrReturnError(chip::CanCastTo<int>(certLen), false); |
| |
| std::unique_ptr<BIO, void (*)(BIO *)> certBIO( |
| BIO_new_mem_buf(static_cast<const void *>(certBuf.get()), static_cast<int>(certLen)), &BIO_free_all); |
| |
| cert.reset(PEM_read_bio_X509(certBIO.get(), nullptr, nullptr, nullptr)); |
| if (cert.get() == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("PEM_read_bio_X509", res = false); |
| } |
| } |
| else if ((certFmt == kCertFormat_X509_DER) || (certFmt == kCertFormat_X509_Hex)) |
| { |
| VerifyOrReturnError(chip::CanCastTo<int>(certLen), false); |
| |
| const uint8_t * outCert = certBuf.get(); |
| |
| cert.reset(d2i_X509(nullptr, &outCert, static_cast<int>(certLen))); |
| if (cert.get() == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("d2i_X509", res = false); |
| } |
| } |
| // Otherwise, it is either CHIP TLV in raw, Base64, or hex encoded format. |
| else |
| { |
| if (certFmt == kCertFormat_Chip_Base64) |
| { |
| res = Base64Decode(certBuf.get(), certLen, certBuf.get(), certLen, certLen); |
| VerifyTrueOrExit(res); |
| } |
| |
| std::unique_ptr<uint8_t[]> x509CertBuf(new uint8_t[kMaxDERCertLength]); |
| MutableByteSpan x509Cert(x509CertBuf.get(), kMaxDERCertLength); |
| |
| CHIP_ERROR err = ConvertChipCertToX509Cert(ByteSpan(certBuf.get(), certLen), x509Cert); |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "Error converting certificate: %s\n", chip::ErrorStr(err)); |
| ExitNow(res = false); |
| } |
| |
| const uint8_t * outCert = x509Cert.data(); |
| |
| VerifyOrReturnError(chip::CanCastTo<int>(x509Cert.size()), false); |
| |
| cert.reset(d2i_X509(nullptr, &outCert, static_cast<int>(x509Cert.size()))); |
| if (cert.get() == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("d2i_X509", res = false); |
| } |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool ReadCertDER(const char * fileNameOrStr, MutableByteSpan & cert) |
| { |
| bool res = true; |
| std::unique_ptr<X509, void (*)(X509 *)> certX509(nullptr, &X509_free); |
| |
| VerifyOrReturnError(ReadCert(fileNameOrStr, certX509), false); |
| |
| uint8_t * certPtr = cert.data(); |
| int certLen = i2d_X509(certX509.get(), &certPtr); |
| if (certLen < 0) |
| { |
| ReportOpenSSLErrorAndExit("i2d_X509", res = false); |
| } |
| |
| VerifyOrReturnError(chip::CanCastTo<size_t>(certLen), false); |
| cert.reduce_size(static_cast<size_t>(certLen)); |
| |
| exit: |
| return res; |
| } |
| |
| bool X509ToChipCert(X509 * cert, MutableByteSpan & chipCert) |
| { |
| bool res = true; |
| CHIP_ERROR err; |
| uint8_t * derCert = nullptr; |
| int derCertLen; |
| |
| derCertLen = i2d_X509(cert, &derCert); |
| if (derCertLen < 0) |
| { |
| ReportOpenSSLErrorAndExit("i2d_X509", res = false); |
| } |
| |
| VerifyOrReturnError(chip::CanCastTo<size_t>(derCertLen), false); |
| |
| err = ConvertX509CertToChipCert(ByteSpan(derCert, static_cast<size_t>(derCertLen)), chipCert); |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "ConvertX509CertToChipCert() failed\n%s\n", chip::ErrorStr(err)); |
| ExitNow(res = false); |
| } |
| |
| exit: |
| OPENSSL_free(derCert); |
| return res; |
| } |
| |
| bool LoadChipCert(const char * fileNameOrStr, bool isTrused, ChipCertificateSet & certSet, MutableByteSpan & chipCert) |
| { |
| bool res = true; |
| CHIP_ERROR err; |
| BitFlags<CertDecodeFlags> decodeFlags; |
| std::unique_ptr<X509, void (*)(X509 *)> cert(nullptr, &X509_free); |
| |
| res = ReadCert(fileNameOrStr, cert); |
| VerifyTrueOrExit(res); |
| |
| res = X509ToChipCert(cert.get(), chipCert); |
| VerifyTrueOrExit(res); |
| |
| if (isTrused) |
| { |
| decodeFlags.Set(CertDecodeFlags::kIsTrustAnchor); |
| } |
| else |
| { |
| decodeFlags.Set(CertDecodeFlags::kGenerateTBSHash); |
| } |
| |
| err = certSet.LoadCert(chipCert, decodeFlags); |
| if (err != CHIP_NO_ERROR) |
| { |
| fprintf(stderr, "Error reading %s\n%s\n", fileNameOrStr, chip::ErrorStr(err)); |
| ExitNow(res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool WriteCert(const char * fileName, X509 * cert, CertFormat certFmt) |
| { |
| bool res = true; |
| FILE * file = nullptr; |
| uint8_t * derCert = nullptr; |
| |
| VerifyOrReturnError(cert != nullptr, false); |
| VerifyOrReturnError(certFmt != kCertFormat_Unknown, false); |
| |
| if (IsChipCertFormat(certFmt)) |
| { |
| uint8_t chipCertBuf[kMaxCHIPCertLength]; |
| MutableByteSpan chipCert(chipCertBuf); |
| |
| VerifyOrReturnError(X509ToChipCert(cert, chipCert), false); |
| |
| return WriteChipCert(fileName, chipCert, certFmt); |
| } |
| |
| if (certFmt == kCertFormat_X509_Hex) |
| { |
| int derCertLen = i2d_X509(cert, &derCert); |
| if (derCertLen < 0) |
| { |
| ReportOpenSSLErrorAndExit("i2d_X509", res = false); |
| } |
| |
| VerifyOrExit(CanCastTo<uint32_t>(derCertLen), res = false); |
| VerifyOrExit(WriteDataIntoFile(fileName, derCert, static_cast<uint32_t>(derCertLen), kDataFormat_Hex), res = false); |
| ExitNow(res = true); |
| } |
| |
| VerifyOrExit(OpenFile(fileName, file, true), res = false); |
| |
| if (certFmt == kCertFormat_X509_PEM) |
| { |
| if (PEM_write_X509(file, cert) == 0) |
| { |
| ReportOpenSSLErrorAndExit("PEM_write_X509", res = false); |
| } |
| } |
| else if (certFmt == kCertFormat_X509_DER) |
| { |
| if (i2d_X509_fp(file, cert) == 0) |
| { |
| ReportOpenSSLErrorAndExit("i2d_X509_fp", res = false); |
| } |
| } |
| else |
| { |
| fprintf(stderr, "Unsupported certificate format\n"); |
| ExitNow(res = false); |
| } |
| |
| exit: |
| OPENSSL_free(derCert); |
| CloseFile(file); |
| return res; |
| } |
| |
| bool WriteChipCert(const char * fileName, const ByteSpan & chipCert, CertFormat certFmt) |
| { |
| DataFormat dataFormat = kDataFormat_Unknown; |
| |
| VerifyOrReturnError(IsChipCertFormat(certFmt), false); |
| |
| if (certFmt == kCertFormat_Chip_Raw) |
| dataFormat = kDataFormat_Raw; |
| else if (certFmt == kCertFormat_Chip_Base64) |
| dataFormat = kDataFormat_Base64; |
| else |
| dataFormat = kDataFormat_Hex; |
| |
| return WriteDataIntoFile(fileName, chipCert.data(), static_cast<uint32_t>(chipCert.size()), dataFormat); |
| } |
| |
| bool MakeCert(CertType certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, |
| uint32_t validDays, int pathLen, const FutureExtensionWithNID * futureExts, uint8_t futureExtsCount, X509 * newCert, |
| EVP_PKEY * newKey, CertStructConfig & certConfig) |
| { |
| bool res = true; |
| bool isCA = (certType == CertType::kRoot || certType == CertType::kICA); |
| |
| VerifyOrExit(subjectDN != nullptr, res = false); |
| VerifyOrExit(caCert != nullptr, res = false); |
| VerifyOrExit(caKey != nullptr, res = false); |
| VerifyOrExit(newCert != nullptr, res = false); |
| VerifyOrExit(newKey != nullptr, res = false); |
| |
| // Set the certificate version (must be 2, a.k.a. v3). |
| if (!X509_set_version(newCert, certConfig.GetCertVersion())) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_version", res = false); |
| } |
| |
| // Generate a serial number for the cert. |
| if (certConfig.IsSerialNumberPresent()) |
| { |
| res = SetCertSerialNumber(newCert, (certType == CertType::kNetworkIdentity ? 1 : kUseRandomSerialNumber)); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Set the issuer name for the certificate. In the case of a self-signed cert, this will be |
| // the new cert's subject name. |
| if (certConfig.IsIssuerPresent()) |
| { |
| if (certType == CertType::kRoot) |
| { |
| res = subjectDN->SetCertIssuerDN(newCert); |
| VerifyTrueOrExit(res); |
| } |
| else |
| { |
| if (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert))) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); |
| } |
| } |
| } |
| |
| // Set the certificate validity time. |
| res = SetValidityTime(newCert, validFrom, validDays, certConfig); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate's public key. |
| if (!X509_set_pubkey(newCert, newKey)) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false); |
| } |
| |
| // Injuct error into public key value. |
| if (certConfig.IsPublicKeyError()) |
| { |
| ASN1_BIT_STRING * pk = X509_get0_pubkey_bitstr(newCert); |
| pk->data[CertStructConfig::kPublicKeyErrorByte] ^= 0xFF; |
| } |
| |
| // Set certificate subject DN. |
| if (certConfig.IsSubjectPresent()) |
| { |
| res = subjectDN->SetCertSubjectDN(newCert); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Add basic constraints certificate extensions. |
| if (certConfig.IsExtensionBasicPathLenPresent() || !certConfig.IsExtensionBasicCAPresent()) |
| { |
| pathLen = certConfig.GetExtensionBasicPathLenValue(certType); |
| } |
| res = SetBasicConstraintsExtension(newCert, isCA, pathLen, certConfig); |
| VerifyTrueOrExit(res); |
| |
| // Add key usage certificate extensions. |
| res = SetKeyUsageExtension(newCert, isCA, certConfig); |
| VerifyTrueOrExit(res); |
| |
| // Add extended key usage certificate extensions. |
| if (!certConfig.IsExtensionExtendedKeyUsageMissing()) |
| { |
| if (certType == CertType::kNode) |
| { |
| res = AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); |
| VerifyTrueOrExit(res); |
| } |
| else if (certType == CertType::kFirmwareSigning) |
| { |
| res = AddExtension(newCert, NID_ext_key_usage, "critical,codeSigning"); |
| VerifyTrueOrExit(res); |
| } |
| else if (certType == CertType::kNetworkIdentity) |
| { |
| res = AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); |
| VerifyTrueOrExit(res); |
| } |
| } |
| |
| // Add a subject key id extension for the certificate. |
| if (certType != CertType::kNetworkIdentity && certConfig.IsExtensionSKIDPresent()) |
| { |
| res = AddSubjectKeyId(newCert, certConfig.IsExtensionSKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Add the authority key id extension from the signing certificate. For self-signed cert's this will |
| // be the same as new cert's subject key id extension. |
| if (certType != CertType::kNetworkIdentity && certConfig.IsExtensionAKIDPresent()) |
| { |
| if ((certType == CertType::kRoot) && !certConfig.IsExtensionSKIDPresent()) |
| { |
| res = AddSubjectKeyId(newCert, certConfig.IsExtensionSKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| res = AddAuthorityKeyId(newCert, newCert, certConfig.IsExtensionAKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| |
| // Remove that temporary added subject key id |
| int authKeyIdExtLoc = X509_get_ext_by_NID(newCert, NID_subject_key_identifier, -1); |
| if (authKeyIdExtLoc != -1) |
| { |
| if (X509_delete_ext(newCert, authKeyIdExtLoc) == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("X509_delete_ext", res = false); |
| } |
| } |
| } |
| else |
| { |
| res = AddAuthorityKeyId(newCert, caCert, certConfig.IsExtensionAKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| } |
| } |
| for (uint8_t i = 0; i < futureExtsCount; i++) |
| { |
| res = AddExtension(newCert, futureExts[i].nid, futureExts[i].info); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Sign the new certificate. |
| if (!X509_sign(newCert, caKey, certConfig.GetSignatureAlgorithmDER())) |
| { |
| ReportOpenSSLErrorAndExit("X509_sign", res = false); |
| } |
| |
| // Injuct error into signature value. |
| if (certConfig.IsSignatureError()) |
| { |
| const ASN1_BIT_STRING * sig = nullptr; |
| X509_get0_signature(&sig, nullptr, newCert); |
| sig->data[20] ^= 0xFF; |
| } |
| |
| exit: |
| return res; |
| } |
| |
| CHIP_ERROR MakeCertTLV(CertType certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, |
| const struct tm & validFrom, uint32_t validDays, int pathLen, const FutureExtensionWithNID * futureExts, |
| uint8_t futureExtsCount, X509 * x509Cert, EVP_PKEY * newKey, CertStructConfig & certConfig, |
| MutableByteSpan & chipCert) |
| { |
| TLVWriter writer; |
| TLVType containerType; |
| TLVType containerType2; |
| TLVType containerType3; |
| uint8_t subjectPubkey[chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES] = { 0 }; |
| uint8_t issuerPubkey[chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES] = { 0 }; |
| uint8_t keyid[chip::Crypto::kSHA1_Hash_Length] = { 0 }; |
| bool isCA; |
| |
| VerifyOrReturnError(subjectDN != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(caCert != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(caKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(x509Cert != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(newKey != nullptr, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| isCA = (certType == CertType::kICA || certType == CertType::kRoot); |
| |
| // If error testing is enabled, let field inclusion be controlled that way, |
| // otherwise use compact identity format for Network (Client) Identities. |
| bool useCompactIdentityFormat = (!certConfig.IsErrorTestCaseEnabled() && certType == CertType::kNetworkIdentity); |
| |
| uint8_t * p = subjectPubkey; |
| VerifyOrReturnError(i2o_ECPublicKey(EVP_PKEY_get0_EC_KEY(newKey), &p) == chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| |
| p = issuerPubkey; |
| VerifyOrReturnError(i2o_ECPublicKey(EVP_PKEY_get0_EC_KEY(caKey), &p) == chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES, |
| CHIP_ERROR_INVALID_ARGUMENT); |
| |
| writer.Init(chipCert); |
| |
| ReturnErrorOnFailure(writer.StartContainer(AnonymousTag(), kTLVType_Structure, containerType)); |
| |
| if (!useCompactIdentityFormat) |
| { |
| // serial number |
| if (certConfig.IsSerialNumberPresent()) |
| { |
| ASN1_INTEGER * asn1Integer = X509_get_serialNumber(x509Cert); |
| uint64_t serialNumber; |
| uint8_t serialNumberArray[sizeof(uint64_t)]; |
| VerifyOrReturnError(1 == ASN1_INTEGER_get_uint64(&serialNumber, asn1Integer), CHIP_ERROR_INVALID_ARGUMENT); |
| Encoding::BigEndian::Put64(serialNumberArray, serialNumber); |
| ReturnErrorOnFailure(writer.PutBytes(ContextTag(kTag_SerialNumber), serialNumberArray, sizeof(serialNumberArray))); |
| } |
| |
| // signature algorithm |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SignatureAlgorithm), certConfig.GetSignatureAlgorithmTLVEnum())); |
| |
| // issuer Name |
| if (certConfig.IsIssuerPresent()) |
| { |
| if (certType == CertType::kRoot) |
| { |
| ReturnErrorOnFailure(subjectDN->EncodeToTLV(writer, ContextTag(kTag_Issuer))); |
| } |
| else |
| { |
| uint8_t caChipCertBuf[kMaxCHIPCertLength]; |
| MutableByteSpan caChipCert(caChipCertBuf); |
| VerifyOrReturnError(true == X509ToChipCert(caCert, caChipCert), CHIP_ERROR_INVALID_ARGUMENT); |
| ChipDN issuerDN; |
| ReturnErrorOnFailure(ExtractSubjectDNFromChipCert(caChipCert, issuerDN)); |
| ReturnErrorOnFailure(issuerDN.EncodeToTLV(writer, ContextTag(kTag_Issuer))); |
| } |
| } |
| |
| // validity |
| uint32_t validFromChipEpoch; |
| uint32_t validToChipEpoch; |
| |
| VerifyOrReturnError( |
| true == |
| CalendarToChipEpochTime(static_cast<uint16_t>(validFrom.tm_year + 1900), static_cast<uint8_t>(validFrom.tm_mon + 1), |
| static_cast<uint8_t>(validFrom.tm_mday), static_cast<uint8_t>(validFrom.tm_hour), |
| static_cast<uint8_t>(validFrom.tm_min), static_cast<uint8_t>(validFrom.tm_sec), |
| validFromChipEpoch), |
| CHIP_ERROR_INVALID_ARGUMENT); |
| if (validDays == kCertValidDays_NoWellDefinedExpiration) |
| { |
| validToChipEpoch = 0; |
| } |
| else |
| { |
| VerifyOrReturnError(CanCastTo<uint32_t>(validFromChipEpoch + validDays * kSecondsPerDay - 1), |
| CHIP_ERROR_INVALID_ARGUMENT); |
| validToChipEpoch = validFromChipEpoch + validDays * kSecondsPerDay - 1; |
| } |
| if (!certConfig.IsValidityCorrect()) |
| { |
| uint32_t validTemp = validFromChipEpoch; |
| validFromChipEpoch = validToChipEpoch; |
| validToChipEpoch = validTemp; |
| } |
| if (certConfig.IsValidityNotBeforePresent()) |
| { |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotBefore), validFromChipEpoch)); |
| } |
| if (certConfig.IsValidityNotAfterPresent()) |
| { |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_NotAfter), validToChipEpoch)); |
| } |
| |
| // subject Name |
| if (certConfig.IsSubjectPresent()) |
| { |
| ReturnErrorOnFailure(subjectDN->EncodeToTLV(writer, ContextTag(kTag_Subject))); |
| } |
| |
| // public key algorithm |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_PublicKeyAlgorithm), GetOIDEnum(kOID_PubKeyAlgo_ECPublicKey))); |
| |
| // public key curve Id |
| uint8_t ecCurveEnum = certConfig.IsSigCurveWrong() ? 0x02 : GetOIDEnum(kOID_EllipticCurve_prime256v1); |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_EllipticCurveIdentifier), ecCurveEnum)); |
| } |
| |
| // public key |
| if (certConfig.IsPublicKeyError()) |
| { |
| subjectPubkey[CertStructConfig::kPublicKeyErrorByte] ^= 0xFF; |
| } |
| ReturnErrorOnFailure( |
| writer.PutBytes(ContextTag(kTag_EllipticCurvePublicKey), subjectPubkey, chip::Crypto::CHIP_CRYPTO_PUBLIC_KEY_SIZE_BYTES)); |
| |
| // extensions |
| if (!useCompactIdentityFormat) |
| { |
| ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_Extensions), kTLVType_List, containerType2)); |
| { |
| if (isCA) |
| { |
| // basic constraints |
| if (certConfig.IsExtensionBasicPresent()) |
| { |
| ReturnErrorOnFailure( |
| writer.StartContainer(ContextTag(kTag_BasicConstraints), kTLVType_Structure, containerType3)); |
| if (certConfig.IsExtensionBasicCAPresent()) |
| { |
| ReturnErrorOnFailure(writer.PutBoolean(ContextTag(kTag_BasicConstraints_IsCA), |
| certConfig.IsExtensionBasicCACorrect() ? isCA : !isCA)); |
| } |
| if (pathLen != kPathLength_NotSpecified) |
| { |
| ReturnErrorOnFailure( |
| writer.Put(ContextTag(kTag_BasicConstraints_PathLenConstraint), static_cast<uint8_t>(pathLen))); |
| } |
| ReturnErrorOnFailure(writer.EndContainer(containerType3)); |
| } |
| |
| // key usage |
| if (certConfig.IsExtensionKeyUsagePresent()) |
| { |
| BitFlags<KeyUsageFlags> keyUsage; |
| if (!certConfig.IsExtensionKeyUsageDigitalSigCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kDigitalSignature); |
| } |
| if (certConfig.IsExtensionKeyUsageKeyCertSignCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kKeyCertSign); |
| } |
| if (certConfig.IsExtensionKeyUsageCRLSignCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kCRLSign); |
| } |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_KeyUsage), keyUsage.Raw())); |
| } |
| } |
| else |
| { |
| // basic constraints |
| if (certConfig.IsExtensionBasicPresent()) |
| { |
| ReturnErrorOnFailure( |
| writer.StartContainer(ContextTag(kTag_BasicConstraints), kTLVType_Structure, containerType3)); |
| if (certConfig.IsExtensionBasicCAPresent()) |
| { |
| ReturnErrorOnFailure(writer.PutBoolean(ContextTag(kTag_BasicConstraints_IsCA), |
| certConfig.IsExtensionBasicCACorrect() ? isCA : !isCA)); |
| } |
| ReturnErrorOnFailure(writer.EndContainer(containerType3)); |
| } |
| |
| // key usage |
| if (certConfig.IsExtensionKeyUsagePresent()) |
| { |
| BitFlags<KeyUsageFlags> keyUsage; |
| if (certConfig.IsExtensionKeyUsageDigitalSigCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kDigitalSignature); |
| } |
| if (!certConfig.IsExtensionKeyUsageKeyCertSignCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kKeyCertSign); |
| } |
| if (!certConfig.IsExtensionKeyUsageCRLSignCorrect()) |
| { |
| keyUsage.Set(KeyUsageFlags::kCRLSign); |
| } |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_KeyUsage), keyUsage)); |
| } |
| |
| // extended key usage |
| if (!certConfig.IsExtensionExtendedKeyUsageMissing() && (certType == CertType::kNode)) |
| { |
| ReturnErrorOnFailure(writer.StartContainer(ContextTag(kTag_ExtendedKeyUsage), kTLVType_Array, containerType3)); |
| if (certType == CertType::kNode) |
| { |
| ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_ClientAuth))); |
| ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_ServerAuth))); |
| } |
| else if (certType == CertType::kFirmwareSigning) |
| { |
| ReturnErrorOnFailure(writer.Put(AnonymousTag(), GetOIDEnum(kOID_KeyPurpose_CodeSigning))); |
| } |
| ReturnErrorOnFailure(writer.EndContainer(containerType3)); |
| } |
| } |
| |
| // subject key identifier |
| if (certConfig.IsExtensionSKIDPresent()) |
| { |
| ReturnErrorOnFailure(Crypto::Hash_SHA1(subjectPubkey, sizeof(subjectPubkey), keyid)); |
| size_t keyIdLen = certConfig.IsExtensionSKIDLengthValid() ? sizeof(keyid) : sizeof(keyid) - 1; |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_SubjectKeyIdentifier), ByteSpan(keyid, keyIdLen))); |
| } |
| |
| // authority key identifier |
| if (certConfig.IsExtensionAKIDPresent()) |
| { |
| ReturnErrorOnFailure(Crypto::Hash_SHA1(issuerPubkey, sizeof(issuerPubkey), keyid)); |
| size_t keyIdLen = certConfig.IsExtensionAKIDLengthValid() ? sizeof(keyid) : sizeof(keyid) - 1; |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_AuthorityKeyIdentifier), ByteSpan(keyid, keyIdLen))); |
| } |
| |
| for (uint8_t i = 0; i < futureExtsCount; i++) |
| { |
| ReturnErrorOnFailure( |
| writer.Put(ContextTag(kTag_FutureExtension), |
| ByteSpan(reinterpret_cast<const uint8_t *>(futureExts[i].info), strlen(futureExts[i].info)))); |
| } |
| } |
| ReturnErrorOnFailure(writer.EndContainer(containerType2)); |
| } |
| |
| // signature |
| const ASN1_BIT_STRING * asn1Signature = nullptr; |
| X509_get0_signature(&asn1Signature, nullptr, x509Cert); |
| |
| uint8_t signatureRawBuf[chip::Crypto::kP256_ECDSA_Signature_Length_Raw]; |
| MutableByteSpan signatureRaw(signatureRawBuf); |
| ReturnErrorOnFailure(chip::Crypto::EcdsaAsn1SignatureToRaw( |
| chip::Crypto::kP256_FE_Length, ByteSpan(asn1Signature->data, static_cast<size_t>(asn1Signature->length)), signatureRaw)); |
| |
| ReturnErrorOnFailure(writer.Put(ContextTag(kTag_ECDSASignature), signatureRaw)); |
| |
| ReturnErrorOnFailure(writer.EndContainer(containerType)); |
| |
| ReturnErrorOnFailure(writer.Finalize()); |
| |
| chipCert.reduce_size(writer.GetLengthWritten()); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| bool ResignCert(X509 * cert, X509 * caCert, EVP_PKEY * caKey) |
| { |
| bool res = true; |
| int authKeyIdExtLoc = -1; |
| |
| res = SetCertSerialNumber(cert); |
| VerifyTrueOrExit(res); |
| |
| if (!X509_set_issuer_name(cert, X509_get_subject_name(caCert))) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); |
| } |
| |
| // Remove any existing authority key id |
| authKeyIdExtLoc = X509_get_ext_by_NID(cert, NID_authority_key_identifier, -1); |
| if (authKeyIdExtLoc != -1) |
| { |
| if (X509_delete_ext(cert, authKeyIdExtLoc) == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("X509_delete_ext", res = false); |
| } |
| } |
| |
| res = AddAuthorityKeyId(cert, caCert, true); |
| VerifyTrueOrExit(res); |
| |
| if (!X509_sign(cert, caKey, EVP_sha256())) |
| { |
| ReportOpenSSLErrorAndExit("X509_sign", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool MakeAttCert(AttCertType attCertType, const char * subjectCN, uint16_t subjectVID, uint16_t subjectPID, |
| bool encodeVIDandPIDasCN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, |
| X509 * newCert, EVP_PKEY * newKey, CertStructConfig & certConfig, X509_EXTENSION * cdpExt) |
| { |
| bool res = true; |
| uint16_t vid = certConfig.IsSubjectVIDMismatch() ? static_cast<uint16_t>(subjectVID + 1) : subjectVID; |
| uint16_t pid = certConfig.IsSubjectPIDMismatch() ? static_cast<uint16_t>(subjectPID + 1) : subjectPID; |
| bool isCA = (attCertType != kAttCertType_DAC); |
| |
| VerifyOrReturnError(subjectCN != nullptr, false); |
| VerifyOrReturnError(caCert != nullptr, false); |
| VerifyOrReturnError(caKey != nullptr, false); |
| VerifyOrReturnError(newCert != nullptr, false); |
| VerifyOrReturnError(newKey != nullptr, false); |
| |
| if (!X509_set_version(newCert, certConfig.GetCertVersion())) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_version", res = false); |
| } |
| |
| // Generate a serial number for the cert. |
| res = SetCertSerialNumber(newCert); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate validity time. |
| res = SetValidityTime(newCert, validFrom, validDays, certConfig); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate's public key. |
| if (!X509_set_pubkey(newCert, newKey)) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false); |
| } |
| |
| // Encode Common Name (CN) Attribute. |
| { |
| char cnAttrStr[chip::Crypto::kMax_CommonNameAttr_Length]; |
| size_t cnAttrStrLen = 0; |
| if (subjectCN != nullptr) |
| { |
| VerifyOrReturnError(strlen(subjectCN) <= sizeof(cnAttrStr), false); |
| memcpy(cnAttrStr, subjectCN, strlen(subjectCN)); |
| cnAttrStrLen += strlen(subjectCN); |
| } |
| |
| if (encodeVIDandPIDasCN) |
| { |
| if (subjectVID != VendorId::NotSpecified) |
| { |
| // Add space to separate from the previous string. |
| if (cnAttrStrLen > 0) |
| { |
| VerifyOrReturnError((cnAttrStrLen + 1) <= sizeof(cnAttrStr), false); |
| cnAttrStr[cnAttrStrLen] = ' '; |
| cnAttrStrLen++; |
| } |
| |
| VerifyOrReturnError((cnAttrStrLen + strlen(chip::Crypto::kVIDPrefixForCNEncoding) + |
| chip::Crypto::kVIDandPIDHexLength) <= sizeof(cnAttrStr), |
| false); |
| |
| memcpy(&cnAttrStr[cnAttrStrLen], chip::Crypto::kVIDPrefixForCNEncoding, |
| strlen(chip::Crypto::kVIDPrefixForCNEncoding)); |
| cnAttrStrLen += strlen(chip::Crypto::kVIDPrefixForCNEncoding); |
| |
| VerifyOrReturnError(Encoding::Uint16ToHex(vid, &cnAttrStr[cnAttrStrLen], chip::Crypto::kVIDandPIDHexLength, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| cnAttrStrLen += chip::Crypto::kVIDandPIDHexLength; |
| } |
| |
| if (subjectPID != 0) |
| { |
| // Add space to separate from the previous string. |
| if (cnAttrStrLen > 0) |
| { |
| VerifyOrReturnError((cnAttrStrLen + 1) <= sizeof(cnAttrStr), false); |
| cnAttrStr[cnAttrStrLen++] = ' '; |
| } |
| |
| VerifyOrReturnError((cnAttrStrLen + strlen(chip::Crypto::kPIDPrefixForCNEncoding) + |
| chip::Crypto::kVIDandPIDHexLength) <= sizeof(cnAttrStr), |
| false); |
| |
| memcpy(&cnAttrStr[cnAttrStrLen], chip::Crypto::kPIDPrefixForCNEncoding, |
| strlen(chip::Crypto::kPIDPrefixForCNEncoding)); |
| cnAttrStrLen += strlen(chip::Crypto::kPIDPrefixForCNEncoding); |
| |
| VerifyOrReturnError(Encoding::Uint16ToHex(pid, &cnAttrStr[cnAttrStrLen], chip::Crypto::kVIDandPIDHexLength, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| cnAttrStrLen += chip::Crypto::kVIDandPIDHexLength; |
| } |
| } |
| |
| // Add common name attribute to the certificate subject DN. |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(newCert), NID_commonName, MBSTRING_UTF8, |
| reinterpret_cast<uint8_t *>(cnAttrStr), static_cast<int>(cnAttrStrLen), -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| |
| if (!encodeVIDandPIDasCN) |
| { |
| // Add VID attribute to the certificate subject DN. |
| if (subjectVID != VendorId::NotSpecified) |
| { |
| char chipAttrStr[chip::Crypto::kVIDandPIDHexLength]; |
| VerifyOrReturnError(Encoding::Uint16ToHex(vid, chipAttrStr, chip::Crypto::kVIDandPIDHexLength, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(newCert), gNIDChipAttAttrVID, MBSTRING_UTF8, |
| reinterpret_cast<unsigned char *>(chipAttrStr), sizeof(chipAttrStr), -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| |
| // Add PID attribute to the certificate subject DN. |
| if (subjectPID != 0) |
| { |
| char chipAttrStr[chip::Crypto::kVIDandPIDHexLength]; |
| VerifyOrReturnError(Encoding::Uint16ToHex(pid, chipAttrStr, chip::Crypto::kVIDandPIDHexLength, |
| Encoding::HexFlags::kUppercase) == CHIP_NO_ERROR, |
| false); |
| |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(newCert), gNIDChipAttAttrPID, MBSTRING_UTF8, |
| reinterpret_cast<unsigned char *>(chipAttrStr), sizeof(chipAttrStr), -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| } |
| |
| // Set the issuer name for the certificate. In the case of a self-signed cert, this will be |
| // the new cert's subject name. |
| if (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert))) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); |
| } |
| |
| // Add basic constraints certificate extensions. |
| res = SetBasicConstraintsExtension(newCert, isCA, certConfig.GetExtensionBasicPathLenValue(attCertType), certConfig); |
| VerifyTrueOrExit(res); |
| |
| // Add key usage certificate extensions. |
| res = SetKeyUsageExtension(newCert, isCA, certConfig); |
| VerifyTrueOrExit(res); |
| |
| if (certConfig.IsExtensionSKIDPresent()) |
| { |
| // Add a subject key id extension for the certificate. |
| res = AddSubjectKeyId(newCert, certConfig.IsExtensionSKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| } |
| |
| if (certConfig.IsExtensionAKIDPresent()) |
| { |
| // Add the authority key id extension from the signing certificate. |
| res = AddAuthorityKeyId(newCert, caCert, certConfig.IsExtensionAKIDLengthValid()); |
| VerifyTrueOrExit(res); |
| } |
| |
| if (certConfig.IsExtensionExtendedKeyUsagePresent()) |
| { |
| // Add optional Extended Key Usage extentsion. |
| res = AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); |
| VerifyTrueOrExit(res); |
| } |
| |
| if (certConfig.IsExtensionAuthorityInfoAccessPresent()) |
| { |
| // Add optional Authority Informational Access extentsion. |
| res = AddExtension(newCert, NID_info_access, "OCSP;URI:http://ocsp.example.com/"); |
| VerifyTrueOrExit(res); |
| } |
| |
| if (certConfig.IsExtensionSubjectAltNamePresent()) |
| { |
| // Add optional Subject Alternative Name extentsion. |
| res = AddExtension(newCert, NID_subject_alt_name, "DNS:test.com"); |
| VerifyTrueOrExit(res); |
| } |
| |
| if (cdpExt != nullptr) |
| { |
| int result = X509_add_ext(newCert, cdpExt, -1); |
| VerifyTrueOrExit(result == 1); |
| } |
| |
| if (certConfig.IsExtensionCDPPresent()) |
| { |
| // Add second CDP extension. |
| res = AddExtension(newCert, NID_crl_distribution_points, "URI:http://example.com/test_crl.pem"); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Sign the new certificate. |
| if (!X509_sign(newCert, caKey, certConfig.GetSignatureAlgorithmDER())) |
| { |
| ReportOpenSSLErrorAndExit("X509_sign", res = false); |
| } |
| |
| exit: |
| return res; |
| } |