| /* |
| * |
| * Copyright (c) 2021 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 <string> |
| |
| using namespace chip; |
| using namespace chip::Credentials; |
| using namespace chip::ASN1; |
| |
| bool ToolChipDN::SetCertSubjectDN(X509 * cert) 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_ChipNodeId: |
| attrNID = gNIDChipNodeId; |
| break; |
| case kOID_AttributeType_ChipFirmwareSigningId: |
| attrNID = gNIDChipFirmwareSigningId; |
| break; |
| case kOID_AttributeType_ChipICAId: |
| attrNID = gNIDChipICAId; |
| break; |
| case kOID_AttributeType_ChipRootId: |
| attrNID = gNIDChipRootId; |
| break; |
| case kOID_AttributeType_ChipFabricId: |
| attrNID = gNIDChipFabricId; |
| break; |
| case kOID_AttributeType_ChipAuthTag1: |
| attrNID = gNIDChipAuthTag1; |
| break; |
| case kOID_AttributeType_ChipAuthTag2: |
| attrNID = gNIDChipAuthTag2; |
| break; |
| default: |
| ExitNow(res = false); |
| } |
| |
| if (IsChipDNAttr(rdn[i].mAttrOID)) |
| { |
| char chipAttrStr[17]; |
| int chipAttrLen; |
| |
| if (IsChip64bitDNAttr(rdn[i].mAttrOID)) |
| { |
| snprintf(chipAttrStr, sizeof(chipAttrStr), "%016" PRIX64 "", rdn[i].mChipVal); |
| chipAttrLen = 16; |
| } |
| else |
| { |
| snprintf(chipAttrStr, sizeof(chipAttrStr), "%08" PRIX32 "", static_cast<uint32_t>(rdn[i].mChipVal)); |
| chipAttrLen = 8; |
| } |
| |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, (unsigned char *) chipAttrStr, |
| chipAttrLen, -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| else |
| { |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, |
| const_cast<uint8_t *>(rdn[i].mString.data()), static_cast<int>(rdn[i].mString.size()), |
| -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)) |
| { |
| snprintf(valueStr, sizeof(valueStr), "%016" PRIX64, rdn[i].mChipVal); |
| } |
| else if (IsChip32bitDNAttr(rdn[i].mAttrOID)) |
| { |
| snprintf(valueStr, sizeof(valueStr), "%08" PRIX32, static_cast<uint32_t>(rdn[i].mChipVal)); |
| } |
| 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 { |
| |
| CertFormat DetectCertFormat(uint8_t * cert, uint32_t certLen) |
| { |
| static const uint8_t chipRawPrefix[] = { 0x15, 0x30, 0x01 }; |
| static const char * chipB64Prefix = "FTABC"; |
| static const size_t chipB64PrefixLen = strlen(chipB64Prefix); |
| static const char * pemMarker = "-----BEGIN CERTIFICATE-----"; |
| |
| if (certLen > sizeof(chipRawPrefix) && memcmp(cert, chipRawPrefix, sizeof(chipRawPrefix)) == 0) |
| { |
| return kCertFormat_Chip_Raw; |
| } |
| |
| if (certLen > chipB64PrefixLen && memcmp(cert, chipB64Prefix, chipB64PrefixLen) == 0) |
| { |
| return kCertFormat_Chip_Base64; |
| } |
| |
| if (ContainsPEMMarker(pemMarker, cert, certLen)) |
| { |
| return kCertFormat_X509_PEM; |
| } |
| |
| return kCertFormat_X509_DER; |
| } |
| |
| bool SetCertSerialNumber(X509 * cert) |
| { |
| bool res = true; |
| uint64_t rnd; |
| ASN1_INTEGER * snInt = X509_get_serialNumber(cert); |
| |
| // 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. |
| rnd &= 0x7FFFFFFFFFFFFFFF; |
| |
| // Store the serial number as an ASN1 integer value within the certificate. |
| if (ASN1_INTEGER_set_uint64(snInt, rnd) == 0) |
| { |
| ReportOpenSSLErrorAndExit("ASN1_INTEGER_set_uint64", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool SetCertTimeField(ASN1_TIME * asn1Time, const struct tm & value) |
| { |
| char timeStr[16]; |
| |
| // Encode the time as a string in the form YYYYMMDDHHMMSSZ. |
| snprintf(timeStr, sizeof(timeStr), "%04d%02d%02d%02d%02d%02dZ", |
| (value.tm_year == kX509NoWellDefinedExpirationDateYear) ? kX509NoWellDefinedExpirationDateYear |
| : (static_cast<uint16_t>(value.tm_year + 1900) % 9999), |
| static_cast<uint8_t>(value.tm_mon) % kMonthsPerYear + 1, static_cast<uint8_t>(value.tm_mday) % (kMaxDaysPerMonth + 1), |
| static_cast<uint8_t>(value.tm_hour) % kHoursPerDay, static_cast<uint8_t>(value.tm_min) % kMinutesPerHour, |
| static_cast<uint8_t>(value.tm_sec) % kSecondsPerMinute); |
| |
| // X.509/RFC-5280 mandates that times before 2050 UTC must be encoded as ASN.1 UTCTime values, while |
| // times equal or greater than 2050 must be encoded as GeneralizedTime values. The only difference |
| // between the two is the number of digits in the year -- 4 for GeneralizedTime, 2 for UTCTime. |
| // |
| // The OpenSSL ASN1_TIME_set_string() function DOES NOT handle picking the correct format based |
| // on the given year. Thus the caller MUST pass a correctly formatted string or the resultant |
| // certificate will be malformed. |
| |
| bool useUTCTime = ((value.tm_year + 1900) < 2050); |
| |
| if (!ASN1_TIME_set_string(asn1Time, timeStr + (useUTCTime ? 2 : 0))) |
| { |
| fprintf(stderr, "OpenSSL ASN1_TIME_set_string() failed\n"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool SetValidityTime(X509 * cert, const struct tm & validFrom, uint32_t validDays) |
| { |
| bool res = true; |
| 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); |
| } |
| |
| // Set the certificate's notBefore date. |
| res = SetCertTimeField(X509_get_notBefore(cert), validFrom); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate's notAfter date. |
| 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; |
| } |
| |
| /** 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 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 (!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 res = true; |
| int isCritical; |
| int index = 0; |
| std::unique_ptr<AUTHORITY_KEYID, void (*)(AUTHORITY_KEYID *)> akid(AUTHORITY_KEYID_new(), &AUTHORITY_KEYID_free); |
| |
| akid.get()->keyid = |
| reinterpret_cast<ASN1_OCTET_STRING *>(X509_get_ext_d2i(caCert, NID_subject_key_identifier, &isCritical, &index)); |
| if (akid.get()->keyid == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("X509_get_ext_d2i", res = false); |
| } |
| |
| 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; |
| } |
| |
| bool ReadCertPEM(const char * fileName, X509 * cert) |
| { |
| bool res = true; |
| FILE * file = nullptr; |
| |
| res = OpenFile(fileName, file); |
| VerifyTrueOrExit(res); |
| |
| if (PEM_read_X509(file, &cert, nullptr, nullptr) == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("PEM_read_X509", res = false); |
| } |
| |
| exit: |
| CloseFile(file); |
| return res; |
| } |
| |
| } // namespace |
| |
| bool ReadCert(const char * fileName, X509 * cert) |
| { |
| CertFormat origCertFmt; |
| return ReadCert(fileName, cert, origCertFmt); |
| } |
| |
| bool ReadCert(const char * fileName, X509 * cert, CertFormat & certFmt) |
| { |
| bool res = true; |
| uint32_t certLen = 0; |
| std::unique_ptr<uint8_t[]> certBuf; |
| |
| res = ReadFileIntoMem(fileName, nullptr, certLen); |
| VerifyTrueOrExit(res); |
| |
| certBuf = std::unique_ptr<uint8_t[]>(new uint8_t[certLen]); |
| |
| res = ReadFileIntoMem(fileName, certBuf.get(), certLen); |
| VerifyTrueOrExit(res); |
| |
| certFmt = DetectCertFormat(certBuf.get(), certLen); |
| |
| if (certFmt == kCertFormat_X509_PEM) |
| { |
| res = ReadCertPEM(fileName, cert); |
| VerifyTrueOrExit(res); |
| } |
| else if (certFmt == kCertFormat_X509_DER) |
| { |
| const uint8_t * outCert = certBuf.get(); |
| |
| VerifyOrReturnError(chip::CanCastTo<int>(certLen), false); |
| |
| if (d2i_X509(&cert, &outCert, static_cast<int>(certLen)) == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("d2i_X509", res = false); |
| } |
| } |
| // Otherwise, it is either CHIP TLV or CHIP TLV Base64 encoded. |
| 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); |
| |
| if (d2i_X509(&cert, &outCert, static_cast<int>(x509Cert.size())) == nullptr) |
| { |
| ReportOpenSSLErrorAndExit("d2i_X509", res = false); |
| } |
| } |
| |
| 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: |
| return res; |
| } |
| |
| bool LoadChipCert(const char * fileName, bool isTrused, ChipCertificateSet & certSet, MutableByteSpan & chipCert) |
| { |
| bool res = true; |
| CHIP_ERROR err; |
| BitFlags<CertDecodeFlags> decodeFlags; |
| std::unique_ptr<X509, void (*)(X509 *)> cert(X509_new(), &X509_free); |
| |
| res = ReadCert(fileName, cert.get()); |
| 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", fileName, chip::ErrorStr(err)); |
| ExitNow(res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| bool WriteCert(const char * fileName, X509 * cert, CertFormat certFmt) |
| { |
| bool res = true; |
| FILE * file = nullptr; |
| |
| VerifyOrExit(cert != nullptr, res = false); |
| |
| res = OpenFile(fileName, file, true); |
| VerifyTrueOrExit(res); |
| |
| 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 if (certFmt == kCertFormat_Chip_Raw || certFmt == kCertFormat_Chip_Base64) |
| { |
| uint8_t * certToWrite = nullptr; |
| size_t certToWriteLen = 0; |
| uint32_t chipCertBase64Len = BASE64_ENCODED_LEN(kMaxCHIPCertLength); |
| std::unique_ptr<uint8_t[]> chipCertBase64(new uint8_t[chipCertBase64Len]); |
| uint8_t chipCertBuf[kMaxCHIPCertLength]; |
| MutableByteSpan chipCert(chipCertBuf); |
| |
| res = X509ToChipCert(cert, chipCert); |
| VerifyTrueOrExit(res); |
| |
| if (certFmt == kCertFormat_Chip_Base64) |
| { |
| res = Base64Encode(chipCert.data(), static_cast<uint32_t>(chipCert.size()), chipCertBase64.get(), chipCertBase64Len, |
| chipCertBase64Len); |
| VerifyTrueOrExit(res); |
| |
| certToWrite = chipCertBase64.get(); |
| certToWriteLen = chipCertBase64Len; |
| } |
| else |
| { |
| certToWrite = chipCert.data(); |
| certToWriteLen = chipCert.size(); |
| } |
| |
| if (fwrite(certToWrite, 1, certToWriteLen, file) != certToWriteLen) |
| { |
| fprintf(stderr, "Unable to write to %s: %s\n", fileName, strerror(ferror(file) ? errno : ENOSPC)); |
| ExitNow(res = false); |
| } |
| } |
| |
| exit: |
| CloseFile(file); |
| return res; |
| } |
| |
| bool MakeCert(uint8_t certType, const ToolChipDN * subjectDN, X509 * caCert, EVP_PKEY * caKey, const struct tm & validFrom, |
| uint32_t validDays, int pathLen, const FutureExtension * futureExts, uint8_t futureExtsCount, X509 * newCert, |
| EVP_PKEY * newKey) |
| { |
| bool res = true; |
| |
| 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, 2)) |
| { |
| 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); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate's public key. |
| if (!X509_set_pubkey(newCert, newKey)) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false); |
| } |
| |
| // Set certificate subject DN. |
| res = subjectDN->SetCertSubjectDN(newCert); |
| 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 (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert))) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false); |
| } |
| |
| // Add basic constraints certificate extensions. |
| { |
| std::string basicConstraintsExt; |
| |
| if (certType == kCertType_Node || certType == kCertType_FirmwareSigning) |
| { |
| basicConstraintsExt = "critical,CA:FALSE"; |
| } |
| else |
| { |
| basicConstraintsExt = "critical,CA:TRUE"; |
| } |
| |
| if (pathLen != kPathLength_NotSpecified) |
| { |
| basicConstraintsExt.append(",pathlen:" + std::to_string(pathLen)); |
| } |
| |
| res = AddExtension(newCert, NID_basic_constraints, basicConstraintsExt.c_str()); |
| VerifyTrueOrExit(res); |
| } |
| |
| // Add the appropriate certificate extensions. |
| if (certType == kCertType_Node) |
| { |
| res = AddExtension(newCert, NID_key_usage, "critical,digitalSignature") && |
| AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"); |
| } |
| else if (certType == kCertType_FirmwareSigning) |
| { |
| res = AddExtension(newCert, NID_key_usage, "critical,digitalSignature") && |
| AddExtension(newCert, NID_ext_key_usage, "critical,codeSigning"); |
| } |
| else if (certType == kCertType_ICA || certType == kCertType_Root) |
| { |
| res = AddExtension(newCert, NID_key_usage, "critical,keyCertSign,cRLSign"); |
| } |
| VerifyTrueOrExit(res); |
| |
| // Add a subject key id extension for the certificate. |
| res = AddSubjectKeyId(newCert); |
| 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. |
| res = AddAuthorityKeyId(newCert, caCert); |
| 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, EVP_sha256())) |
| { |
| ReportOpenSSLErrorAndExit("X509_sign", res = false); |
| } |
| |
| exit: |
| return res; |
| } |
| |
| 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); |
| 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, X509 * caCert, |
| EVP_PKEY * caKey, const struct tm & validFrom, uint32_t validDays, X509 * newCert, EVP_PKEY * newKey) |
| { |
| bool res = true; |
| |
| VerifyOrReturnError(subjectCN != nullptr, false); |
| VerifyOrReturnError(caCert != nullptr, false); |
| VerifyOrReturnError(caKey != nullptr, false); |
| VerifyOrReturnError(newCert != nullptr, false); |
| VerifyOrReturnError(newKey != nullptr, false); |
| |
| // Set the certificate version (must be 2, a.k.a. v3). |
| if (!X509_set_version(newCert, 2)) |
| { |
| 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); |
| VerifyTrueOrExit(res); |
| |
| // Set the certificate's public key. |
| if (!X509_set_pubkey(newCert, newKey)) |
| { |
| ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false); |
| } |
| |
| // 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<unsigned char *>(const_cast<char *>(subjectCN)), |
| static_cast<int>(strlen(subjectCN)), -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| |
| // Add VID attribute to the certificate subject DN. |
| if (subjectVID != 0) |
| { |
| char chipAttrStr[5]; |
| |
| snprintf(chipAttrStr, sizeof(chipAttrStr), "%04" PRIX16 "", subjectVID); |
| |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(newCert), gNIDChipAttAttrVID, MBSTRING_UTF8, |
| reinterpret_cast<unsigned char *>(chipAttrStr), 4, -1, 0)) |
| { |
| ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false); |
| } |
| } |
| |
| // Add PID attribute to the certificate subject DN. |
| if (subjectPID != 0) |
| { |
| char chipAttrStr[5]; |
| |
| snprintf(chipAttrStr, sizeof(chipAttrStr), "%04" PRIX16 "", subjectPID); |
| |
| if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(newCert), gNIDChipAttAttrPID, MBSTRING_UTF8, |
| reinterpret_cast<unsigned char *>(chipAttrStr), 4, -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 the appropriate certificate extensions. |
| if (attCertType == kAttCertType_DAC) |
| { |
| res = AddExtension(newCert, NID_basic_constraints, "critical,CA:FALSE") && |
| AddExtension(newCert, NID_key_usage, "critical,digitalSignature"); |
| } |
| else if (attCertType == kAttCertType_PAI) |
| { |
| res = AddExtension(newCert, NID_basic_constraints, "critical,CA:TRUE,pathlen:0") && |
| AddExtension(newCert, NID_key_usage, "critical,keyCertSign,cRLSign"); |
| } |
| // otherwise, it is PAA |
| else |
| { |
| res = AddExtension(newCert, NID_basic_constraints, "critical,CA:TRUE,pathlen:1") && |
| AddExtension(newCert, NID_key_usage, "critical,keyCertSign,cRLSign"); |
| } |
| VerifyTrueOrExit(res); |
| |
| // Add a subject key id extension for the certificate. |
| res = AddSubjectKeyId(newCert); |
| VerifyTrueOrExit(res); |
| |
| // Add the authority key id extension from the signing certificate. |
| res = AddAuthorityKeyId(newCert, caCert); |
| VerifyTrueOrExit(res); |
| |
| // Sign the new certificate. |
| if (!X509_sign(newCert, caKey, EVP_sha256())) |
| { |
| ReportOpenSSLErrorAndExit("X509_sign", res = false); |
| } |
| |
| exit: |
| return res; |
| } |