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