blob: adbe70e54d9ade6f63ed6527a14b0e2bd6275c65 [file] [log] [blame]
/*
*
* 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;
}