blob: 2bd8097b6c3229c4d8f9521fd2cc1f30708ff1bf [file] [log] [blame]
/*
*
* 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 Weave certificates.
*
*/
#define __STDC_FORMAT_MACROS
#include <ctype.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "weave-tool.h"
using namespace nl::Weave::Profiles::Security;
using namespace nl::Weave::ASN1;
enum {
kMaxWeaveCertInflationFactor = 5 // Maximum ratio of the size of buffer needed to hold an X.509 certificate
// relative to the size of buffer needed to hold its Weave counterpart.
// This value (5) is conservatively big given that certificates contain large
// amounts of incompressible data. In practice, the factor is going to be
// much closer to 1.5.
};
bool ReadCert(const char *fileName, X509 *& cert)
{
CertFormat origCertFmt;
return ReadCert(fileName, cert, origCertFmt);
}
bool ReadCert(const char *fileName, X509 *& cert, CertFormat& origCertFmt)
{
bool res = true;
WEAVE_ERROR err;
uint8_t *certBuf = NULL;
const uint8_t *p;
uint32_t certLen;
CertFormat curCertFmt;
cert = NULL;
if (!ReadFileIntoMem(fileName, certBuf, certLen))
ExitNow(res = false);
VerifyOrExit(certLen <= MAX_CERT_SIZE, res = false);
curCertFmt = origCertFmt = DetectCertFormat(certBuf, certLen);
if (curCertFmt == kCertFormat_X509_PEM)
{
if (!X509PEMToDER(certBuf, certLen))
ExitNow(res = false);
curCertFmt = kCertFormat_X509_DER;
}
else if (curCertFmt == kCertFormat_Weave_Base64)
{
if (!Base64Decode(certBuf, certLen, certBuf, certLen, certLen))
ExitNow(res = false);
curCertFmt = kCertFormat_Weave_Raw;
}
if (curCertFmt == kCertFormat_Weave_Raw)
{
uint32_t convertedCertLen = certLen * kMaxWeaveCertInflationFactor;
uint8_t *convertedCertBuf = (uint8_t *)malloc((size_t)convertedCertLen);
err = ConvertWeaveCertToX509Cert(certBuf, certLen, convertedCertBuf, convertedCertLen, convertedCertLen);
if (err != WEAVE_NO_ERROR)
{
free(convertedCertBuf);
fprintf(stderr, "weave: Error converting certificate: %s\n", nl::ErrorStr(err));
ExitNow(res = false);
}
free(certBuf);
certBuf = convertedCertBuf;
certLen = convertedCertLen;
curCertFmt= kCertFormat_X509_DER;
}
p = certBuf;
cert = d2i_X509(NULL, &p, certLen);
if (cert == NULL)
ReportOpenSSLErrorAndExit("d2i_X509", res = false);
exit:
if (cert != NULL && !res) {
X509_free(cert);
cert = NULL;
}
if (certBuf != NULL) {
free(certBuf);
}
return res;
}
bool ReadCertPEM(const char *fileName, X509 *& cert)
{
bool res = true;
FILE *file = NULL;
file = fopen(fileName, "r");
if (file == NULL)
{
fprintf(stderr, "Unable to open %s: %s\n", fileName, strerror(errno));
return false;
}
cert = NULL;
if (PEM_read_X509(file, &cert, NULL, NULL) == NULL)
{
fprintf(stderr, "Unable to read %s\n", fileName);
ReportOpenSSLErrorAndExit("PEM_read_X509", res = false);
}
exit:
if (file != NULL)
fclose(file);
return res;
}
bool ReadWeaveCert(const char *fileName, uint8_t *& certBuf, uint32_t& certLen)
{
bool res = true;
CertFormat certFmt;
certBuf = NULL;
certLen = 0;
if (!ReadFileIntoMem(fileName, certBuf, certLen))
ExitNow(res = false);
VerifyOrExit(certLen <= MAX_CERT_SIZE, res = false);
certFmt = DetectCertFormat(certBuf, certLen);
if (certFmt != kCertFormat_Weave_Raw && certFmt != kCertFormat_Weave_Base64)
{
fprintf(stderr, "weave: Error reading %s\nUnrecognized certificate format\n", fileName);
ExitNow(res = false);
}
if (certFmt == kCertFormat_Weave_Base64)
{
uint8_t *rawCertBuf = Base64Decode(certBuf, certLen, NULL, 0, certLen);
if (rawCertBuf == NULL)
{
ExitNow(res = false);
}
free(certBuf);
certBuf = rawCertBuf;
}
exit:
if (!res && certBuf != NULL)
{
free(certBuf);
certBuf = NULL;
certLen = 0;
}
return res;
}
bool LoadWeaveCert(const char *fileName, bool isTrused, WeaveCertificateSet& certSet, uint8_t *& certBuf)
{
bool res = true;
WEAVE_ERROR err;
WeaveCertificateData *cert;
uint32_t certLen;
if (certSet.CertCount == certSet.MaxCerts)
{
fprintf(stderr, "weave: Too many input certificates.\n");
return false;
}
res = ReadWeaveCert(fileName, certBuf, certLen);
if (!res)
ExitNow();
err = certSet.LoadCert(certBuf, certLen, (isTrused) ? 0 : kDecodeFlag_GenerateTBSHash, cert);
if (err != WEAVE_NO_ERROR)
{
fprintf(stderr, "weave: Error reading %s\n%s\n", fileName, nl::ErrorStr(err));
ExitNow(res = false);
}
if (isTrused)
cert->CertFlags |= kCertFlag_IsTrusted;
exit:
if (!res && certBuf != NULL)
{
free(certBuf);
certBuf = NULL;
}
return res;
}
bool WriteCert(X509 *cert, FILE *file, const char *fileName, CertFormat certFmt)
{
bool res = true;
uint8_t *weaveCert = NULL;
uint32_t weaveCertLen;
char *weaveCertBase64 = NULL;
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_Weave_Raw || certFmt == kCertFormat_Weave_Base64)
{
if (!WeaveEncodeCert(cert, weaveCert, weaveCertLen))
ExitNow(res = false);
if (certFmt == kCertFormat_Weave_Raw)
{
if (fwrite(weaveCert, 1, weaveCertLen, file) != weaveCertLen)
{
fprintf(stderr, "weave: ERROR: Unable to write to %s\n%s\n", fileName, strerror(ferror(file) ? errno : ENOSPC));
ExitNow(res = false);
}
}
else
{
weaveCertBase64 = Base64Encode(weaveCert, weaveCertLen);
if (weaveCertBase64 == NULL)
ExitNow(res = false);
if (fputs(weaveCertBase64, file) == EOF)
{
fprintf(stderr, "weave: ERROR: Unable to write output certificate file (%s)\n%s\n", fileName, strerror(ferror(file) ? errno : ENOSPC));
ExitNow(res = false);
}
}
}
exit:
if (weaveCert != NULL)
free(weaveCert);
if (weaveCertBase64 != NULL)
free(weaveCertBase64);
return res;
}
bool WeaveEncodeCert(X509 *cert, uint8_t *& encodedCert, uint32_t& encodedCertLen)
{
bool res = true;
WEAVE_ERROR err;
uint8_t *derEncodedCert = NULL;
int32_t derEncodedCertLen;
encodedCert = NULL;
derEncodedCertLen = i2d_X509(cert, &derEncodedCert);
if (derEncodedCertLen < 0)
ReportOpenSSLErrorAndExit("i2d_X509", res = false);
encodedCert = (uint8_t *)malloc(derEncodedCertLen);
if (encodedCert == NULL)
{
fprintf(stderr, "Failed to encode certificate: Memory allocation failure\n");
ExitNow(res = false);
}
err = ConvertX509CertToWeaveCert(derEncodedCert, derEncodedCertLen, encodedCert, derEncodedCertLen, encodedCertLen);
if (err != WEAVE_NO_ERROR)
{
fprintf(stderr, "Failed to create device certificate: ConvertX509CertToWeaveCert() failed\n%s\n", nl::ErrorStr(err));
ExitNow(res = false);
}
exit:
if (encodedCert != NULL && !res)
{
free(encodedCert);
encodedCert = NULL;
}
return res;
}
bool DEREncodeCert(X509 *cert, uint8_t *& encodedCert, uint32_t& encodedCertLen)
{
int res;
encodedCert = NULL;
res = i2d_X509(cert, &encodedCert);
if (res < 0)
ReportOpenSSLErrorAndExit("i2d_X509", );
encodedCertLen = (uint32_t)res;
exit:
return res >= 0;
}
bool MakeDeviceCert(uint64_t devId, X509 *caCert, EVP_PKEY *caKey, const char *curveName, const struct tm& validFrom, uint32_t validDays,
const EVP_MD *sigHashAlgo, X509 *& devCert, EVP_PKEY *& devKey)
{
bool res = true;
bool keySupplied = (devKey != NULL);
devCert = NULL;
if (!keySupplied && !GenerateKeyPair(curveName, devKey))
ExitNow(res = false);
devCert = X509_new();
if (devCert == NULL)
ReportOpenSSLErrorAndExit("X509_new", res = false);
if (!X509_set_version(devCert, 2))
ReportOpenSSLErrorAndExit("X509_set_version", res = false);
if (!SetCertSerialNumber(devCert))
ExitNow(res = false);
// Set the certificate validity time.
if (!SetValidityTime(devCert, validFrom, validDays))
ExitNow(res = false);
if (!X509_set_pubkey(devCert, devKey))
ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false);
if (!SetWeaveCertSubjectName(devCert, gNIDWeaveDeviceId, devId))
ExitNow(res = false);
if (!X509_set_issuer_name(devCert, X509_get_subject_name(caCert)))
ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false);
if (!AddExtension(devCert, NID_basic_constraints, "critical,CA:FALSE") ||
!AddExtension(devCert, NID_key_usage, "critical,digitalSignature,keyEncipherment") ||
!AddExtension(devCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"))
ExitNow(res = false);
if (!AddSubjectKeyId(devCert))
ExitNow(res = false);
if (!AddAuthorityKeyId(devCert, caCert))
ExitNow(res = false);
if (!X509_sign(devCert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
if (devCert != NULL && !res)
{
X509_free(devCert);
devCert = NULL;
}
if (!keySupplied && devKey != NULL && !res)
{
EVP_PKEY_free(devKey);
devKey = NULL;
}
return res;
}
bool MakeCACert(uint64_t newCertId, EVP_PKEY *newCertKey, X509 *caCert, EVP_PKEY *caKey, const struct tm& validFrom, uint32_t validDays,
const EVP_MD *sigHashAlgo, X509 *& newCert)
{
bool res = true;
// Create new certificate object.
newCert = X509_new();
if (newCert == NULL)
ReportOpenSSLErrorAndExit("X509_new", res = false);
// If a signing CA certificate was not provided, then arrange to generate a self-signed
// certificate.
if (caCert == NULL)
{
caCert = newCert;
caKey = newCertKey;
}
// 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.
if (!SetCertSerialNumber(newCert))
ExitNow(res = false);
// Set the certificate validity time.
if (!SetValidityTime(newCert, validFrom, validDays))
ExitNow(res = false);
// Set the certificate's public key.
if (!X509_set_pubkey(newCert, newCertKey))
ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false);
// Create a subject name consisting of a single RDN containing the WeaveCAId attribute.
if (!SetWeaveCertSubjectName(newCert, gNIDWeaveCAId, newCertId))
ExitNow(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 extensions for a CA certificate.
if (!AddExtension(newCert, NID_basic_constraints, "critical,CA:TRUE") ||
!AddExtension(newCert, NID_key_usage, "critical,keyCertSign,cRLSign"))
ExitNow(res = false);
// Generate a subject key id for the certificate.
if (!AddSubjectKeyId(newCert))
ExitNow(res = false);
// Add the authority key id from the signing certificate. For self-signed cert's this will
// be the same as new cert's subject key id.
if (!AddAuthorityKeyId(newCert, caCert))
ExitNow(res = false);
// Sign the new certificate.
if (!X509_sign(newCert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
if (newCert != NULL && !res)
{
X509_free(newCert);
newCert = NULL;
}
return res;
}
bool MakeCodeSigningCert(uint64_t newCertId, EVP_PKEY *newCertKey, X509 *caCert, EVP_PKEY *caKey, const struct tm& validFrom, uint32_t validDays,
const EVP_MD *sigHashAlgo, X509 *& newCert)
{
bool res = true;
// Create new certificate object.
newCert = X509_new();
if (newCert == NULL)
ReportOpenSSLErrorAndExit("X509_new", 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.
if (!SetCertSerialNumber(newCert))
ExitNow(res = false);
// Set the certificate validity time.
if (!SetValidityTime(newCert, validFrom, validDays))
ExitNow(res = false);
// Set the certificate's public key.
if (!X509_set_pubkey(newCert, newCertKey))
ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false);
// Create a subject name consisting of a single RDN containing the WeaveSoftwarePublisherId attribute.
if (!SetWeaveCertSubjectName(newCert, gNIDWeaveSoftwarePublisherId, newCertId))
ExitNow(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 extensions for a code signing certificate.
if (!AddExtension(newCert, NID_basic_constraints, "critical,CA:FALSE") ||
!AddExtension(newCert, NID_key_usage, "critical,digitalSignature") ||
!AddExtension(newCert, NID_ext_key_usage, "critical,codeSigning"))
ExitNow(res = false);
// Generate a subject key id for the certificate.
if (!AddSubjectKeyId(newCert))
ExitNow(res = false);
// Add the authority key id from the signing certificate. For self-signed cert's this will
// be the same as new cert's subject key id.
if (!AddAuthorityKeyId(newCert, caCert))
ExitNow(res = false);
// Sign the new certificate.
if (!X509_sign(newCert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
if (newCert != NULL && !res)
{
X509_free(newCert);
newCert = NULL;
}
return res;
}
bool MakeServiceEndpointCert(uint64_t newCertId, EVP_PKEY *newCertKey, X509 *caCert, EVP_PKEY *caKey, const struct tm& validFrom, uint32_t validDays,
const EVP_MD *sigHashAlgo, X509 *& newCert)
{
bool res = true;
// Create new certificate object.
newCert = X509_new();
if (newCert == NULL)
ReportOpenSSLErrorAndExit("X509_new", 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.
if (!SetCertSerialNumber(newCert))
ExitNow(res = false);
// Set the certificate validity time.
if (!SetValidityTime(newCert, validFrom, validDays))
ExitNow(res = false);
// Set the certificate's public key.
if (!X509_set_pubkey(newCert, newCertKey))
ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false);
// Create a subject name consisting of a single RDN containing the WeaveServiceEndpointId attribute.
if (!SetWeaveCertSubjectName(newCert, gNIDWeaveServiceEndpointId, newCertId))
ExitNow(res = false);
// Set the issuer name for the certificate.
if (!X509_set_issuer_name(newCert, X509_get_subject_name(caCert)))
ReportOpenSSLErrorAndExit("X509_set_issuer_name", res = false);
// Add the appropriate extensions for a service endpoint certificate.
if (!AddExtension(newCert, NID_basic_constraints, "critical,CA:FALSE") ||
!AddExtension(newCert, NID_key_usage, "critical,digitalSignature,keyEncipherment") ||
!AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"))
ExitNow(res = false);
// Generate a subject key id for the certificate.
if (!AddSubjectKeyId(newCert))
ExitNow(res = false);
// Add the authority key id from the signing certificate. For self-signed cert's this will
// be the same as new cert's subject key id.
if (!AddAuthorityKeyId(newCert, caCert))
ExitNow(res = false);
// Sign the new certificate.
if (!X509_sign(newCert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
if (newCert != NULL && !res)
{
X509_free(newCert);
newCert = NULL;
}
return res;
}
bool MakeGeneralCert(const char *subject, EVP_PKEY *newCertKey, X509 *caCert, EVP_PKEY *caKey, const struct tm& validFrom, uint32_t validDays,
const EVP_MD *sigHashAlgo, X509 *& newCert)
{
bool res = true;
// Create new certificate object.
newCert = X509_new();
if (newCert == NULL)
ReportOpenSSLErrorAndExit("X509_new", res = false);
// If a signing CA certificate was not provided, then arrange to generate a self-signed
// certificate.
if (caCert == NULL)
{
caCert = newCert;
caKey = newCertKey;
}
// 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.
if (!SetCertSerialNumber(newCert))
ExitNow(res = false);
// Set the certificate validity time.
if (!SetValidityTime(newCert, validFrom, validDays))
ExitNow(res = false);
// Set the certificate's public key.
if (!X509_set_pubkey(newCert, newCertKey))
ReportOpenSSLErrorAndExit("X509_set_pubkey", res = false);
// Create a subject name consisting of a single RDN containing the specific subject name.
if (!SetCertSubjectName(newCert, NID_commonName, subject))
ExitNow(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 extensions for a non-CA certificate.
if (!AddExtension(newCert, NID_basic_constraints, "critical,CA:FALSE") ||
!AddExtension(newCert, NID_key_usage, "critical,digitalSignature,keyEncipherment") ||
!AddExtension(newCert, NID_ext_key_usage, "critical,clientAuth,serverAuth"))
ExitNow(res = false);
// Generate a subject key id for the certificate.
if (!AddSubjectKeyId(newCert))
ExitNow(res = false);
// Add the authority key id from the signing certificate. For self-signed cert's this will
// be the same as new cert's subject key id.
if (!AddAuthorityKeyId(newCert, caCert))
ExitNow(res = false);
// Sign the new certificate.
if (!X509_sign(newCert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
if (newCert != NULL && !res)
{
X509_free(newCert);
newCert = NULL;
}
return res;
}
bool ResignCert(X509 *cert, X509 *caCert, EVP_PKEY *caKey, const EVP_MD *sigHashAlgo)
{
bool res = true;
int authKeyIdExtLoc = -1;
if (!SetCertSerialNumber(cert))
ExitNow(res = false);
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) == NULL)
ReportOpenSSLErrorAndExit("X509_delete_ext", res = false);
}
if (!AddAuthorityKeyId(cert, caCert))
ExitNow(res = false);
if (!X509_sign(cert, caKey, sigHashAlgo))
ReportOpenSSLErrorAndExit("X509_sign", res = false);
exit:
return res;
}
// ASN1_INTEGER_set_uint64() is a new method, which was introduced in OpenSSL version 1.1.0.
// This method is used in the SetCertSerialNumber() function to store the serial number
// as an ASN1 integer value within the certificate.
// For older versions of OpenSSL c2i_ASN1_INTEGER() method was available and it is used in
// the SetCertSerialNumber() function to achieve similar functionality.
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
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;
}
#else // (OPENSSL_VERSION_NUMBER < 0x10100000L)
bool SetCertSerialNumber(X509 *cert)
{
bool res = true;
uint8_t rnd[8];
const uint8_t *rndPtr = rnd;
long rndLen = sizeof(rnd);
ASN1_INTEGER *snInt = X509_get_serialNumber(cert);
// Generate a random value to be used as the serial number.
if (!RAND_bytes(rnd, sizeof(rnd)))
ReportOpenSSLErrorAndExit("RAND_bytes", res = false);
// Avoid negative numbers.
rnd[0] = rnd[0] & 0x7F;
// Trim extraneous leading zeros. This ensures that older versions of OpenSSL encode the
// integer in the fewest number of bytes, as required by ASN.1 encoding rules (X.209/ISO-8825).
while (rndLen > 1 && rndPtr[0] == 0 && (rndPtr[1] & 0x80) == 0)
{
rndPtr++;
rndLen--;
}
// Store the serial number as an ASN1 integer value within the certificate.
if (c2i_ASN1_INTEGER(&snInt, &rndPtr, rndLen) == NULL)
ReportOpenSSLErrorAndExit("c2i_ASN1_INTEGER", res = false);
exit:
return res;
}
#endif // (OPENSSL_VERSION_NUMBER >= 0x10100000L)
bool SetCertSubjectName(X509 *cert, int attrNID, const char *subjectName)
{
bool res = true;
int len = strlen(subjectName);
if (!X509_NAME_add_entry_by_NID(X509_get_subject_name(cert), attrNID, MBSTRING_UTF8, (unsigned char *)subjectName, len, -1, 0))
ReportOpenSSLErrorAndExit("X509_NAME_add_entry_by_NID", res = false);
exit:
return res;
}
bool SetWeaveCertSubjectName(X509 *cert, int attrNID, uint64_t id)
{
char idStr[17];
snprintf(idStr, sizeof(idStr), "%016" PRIX64 "", id);
return SetCertSubjectName(cert, attrNID, idStr);
}
bool SetCertTimeField(ASN1_TIME *s, 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 + 1900, value.tm_mon + 1, value.tm_mday,
value.tm_hour, value.tm_min, value.tm_sec);
// 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(s, 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)
{
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.
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 == (time_t)-1)
{
fprintf(stderr, "mktime() failed\n");
return false;
}
localtime_r(&validToTime, &validTo);
// Set the certificate's notBefore date.
if (!SetCertTimeField(X509_get_notBefore(cert), validFrom))
return false;
// Set the certificate's notAfter date.
if (!SetCertTimeField(X509_get_notAfter(cert), validTo))
return false;
return true;
}
bool AddExtension(X509 *devCert, int extNID, const char *extStr)
{
bool res = true;
X509_EXTENSION *ex;
ex = X509V3_EXT_nconf_nid(NULL, NULL, extNID, (char *)extStr);
if (ex == NULL)
ReportOpenSSLErrorAndExit("X509V3_EXT_conf_nid", res = false);
if (!X509_add_ext(devCert, ex, -1))
ReportOpenSSLErrorAndExit("X509_add_ext", res = false);
exit:
if (ex != NULL)
X509_EXTENSION_free(ex);
return res;
}
bool AddSubjectKeyId(X509 *cert)
{
bool res = true;
ASN1_OCTET_STRING *pkHashOS = NULL;
ASN1_BIT_STRING *pk = X509_get0_pubkey_bitstr(cert);
unsigned char pkHash[EVP_MAX_MD_SIZE];
unsigned int pkHashLen;
if (!EVP_Digest(pk->data, pk->length, pkHash, &pkHashLen, EVP_sha1(), NULL))
ReportOpenSSLErrorAndExit("EVP_Digest", res = false);
if (pkHashLen != 20)
{
fprintf(stderr, "Unexpected hash length returned from EVP_Digest()\n");
ExitNow(res = false);
}
pkHashOS = ASN1_STRING_type_new(V_ASN1_OCTET_STRING);
if (pkHashOS == NULL)
ReportOpenSSLErrorAndExit("ASN1_STRING_type_new", res = false);
/* Use "truncated" SHA-1 hash. Per RFC5280:
*
* "(2) The keyIdentifier is composed of a four-bit type field with the value 0100 followed
* by the least significant 60 bits of the SHA-1 hash of the value of the BIT STRING
* subjectPublicKey (excluding the tag, length, and number of unused bits)."
*/
pkHash[12] = 0x40 | (pkHash[12] & 0xF);
if (!ASN1_STRING_set(reinterpret_cast<ASN1_STRING *>(pkHashOS), &pkHash[12], 8))
ReportOpenSSLErrorAndExit("ASN1_STRING_set", res = false);
if (!X509_add1_ext_i2d(cert, NID_subject_key_identifier, pkHashOS, 0, X509V3_ADD_APPEND))
ReportOpenSSLErrorAndExit("X509_add1_ext_i2d", res = false);
exit:
if (pkHashOS != NULL)
ASN1_STRING_free(reinterpret_cast<ASN1_STRING *>(pkHashOS));
return res;
}
bool AddAuthorityKeyId(X509 *cert, X509 *caCert)
{
bool res = true;
AUTHORITY_KEYID *akid;
int isCritical, index = 0;
akid = AUTHORITY_KEYID_new();
if (akid == NULL)
{
fprintf(stderr, "Memory allocation failure\n");
ExitNow(res = false);
}
akid->keyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(caCert, NID_subject_key_identifier, &isCritical, &index);
if (akid->keyid == NULL)
ReportOpenSSLErrorAndExit("X509_get_ext_d2i", res = false);
if (!X509_add1_ext_i2d(cert, NID_authority_key_identifier, akid, 0, X509V3_ADD_APPEND))
ReportOpenSSLErrorAndExit("X509_add1_ext_i2d", res = false);
exit:
return res;
}
bool X509PEMToDER(uint8_t *cert, uint32_t& certLen)
{
bool res = true;
BIO *certBIO;
char *name = NULL;
char *header = NULL;
uint8_t *data = NULL;
long dataLen;
certBIO = BIO_new_mem_buf((void*)cert, certLen);
if (certBIO == NULL)
{
fprintf(stderr, "Memory allocation error\n");
ExitNow(res = false);
}
if (!PEM_read_bio(certBIO, &name, &header, &data, &dataLen))
ReportOpenSSLErrorAndExit("PEM_read_bio", res = false);
memcpy(cert, data, dataLen);
certLen = dataLen;
exit:
if (certBIO != NULL)
BIO_free(certBIO);
if (name != NULL)
OPENSSL_free(name);
if (header != NULL)
OPENSSL_free(header);
if (data != NULL)
OPENSSL_free(data);
return res;
}
bool X509DERToPEM(uint8_t *cert, uint32_t& certLen, uint32_t bufLen)
{
bool res = true;
BIO *certBIO;
BUF_MEM *pemBuf;
certBIO = BIO_new(BIO_s_mem());
if (certBIO == NULL)
{
fprintf(stderr, "Memory allocation error\n");
ExitNow(res = false);
}
if (!PEM_write_bio(certBIO, "CERTIFICATE", (char *)"", cert, certLen))
ReportOpenSSLErrorAndExit("PEM_write_bio", res = false);
(void)BIO_flush(certBIO);
BIO_get_mem_ptr(certBIO, &pemBuf);
if (pemBuf->length > bufLen)
{
fprintf(stderr, "Certificate too big\n");
ExitNow(res = false);
}
memcpy(cert, pemBuf->data, pemBuf->length);
certLen = pemBuf->length;
exit:
if (certBIO != NULL)
BIO_free(certBIO);
return res;
}
CertFormat DetectCertFormat(uint8_t *cert, uint32_t certLen)
{
static const uint8_t weaveRawPrefix[] = { 0xD5, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00 };
static const char *weaveB64Prefix = "1QAABAAB";
static const uint32_t weaveB64PrefixLen = sizeof(weaveB64Prefix) - 1;
static const char *pemMarker = "-----BEGIN CERTIFICATE-----";
if (certLen > sizeof(weaveRawPrefix) && memcmp(cert, weaveRawPrefix, sizeof(weaveRawPrefix)) == 0)
return kCertFormat_Weave_Raw;
if (certLen > weaveB64PrefixLen && memcmp(cert, weaveB64Prefix, weaveB64PrefixLen) == 0)
return kCertFormat_Weave_Base64;
if (ContainsPEMMarker(pemMarker, cert, certLen))
return kCertFormat_X509_PEM;
return kCertFormat_X509_DER;
}