Add x509 utils
This change moves some existing static functions in the Certificate
class and x509 related routines into a separate library. These functions
will be used in future Authorities List related functions.
This change also reduces the number of times Certificate class reads PEM
files by passing cert via X509 pointers rather than Certificate paths.
Signed-off-by: Nan Zhou <nanzhoumails@gmail.com>
Change-Id: Ieb268ee051c3597f2add732902eb0461375a4c3f
diff --git a/certificate.cpp b/certificate.cpp
index 09a9222..66ea698 100644
--- a/certificate.cpp
+++ b/certificate.cpp
@@ -3,6 +3,7 @@
#include "certificate.hpp"
#include "certs_manager.hpp"
+#include "x509_utils.hpp"
#include <openssl/asn1.h>
#include <openssl/bio.h>
@@ -13,13 +14,11 @@
#include <openssl/objects.h>
#include <openssl/opensslv.h>
#include <openssl/pem.h>
-#include <openssl/ssl3.h>
#include <openssl/x509v3.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
-#include <ctime>
#include <exception>
#include <filesystem>
#include <fstream>
@@ -52,21 +51,10 @@
// RAII support for openSSL functions.
using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
using X509StorePtr = std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>;
-using X509StoreCtxPtr =
- std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
using EVPPkeyPtr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BufMemPtr = std::unique_ptr<BUF_MEM, decltype(&::BUF_MEM_free)>;
-// Trust chain related errors.`
-#define TRUST_CHAIN_ERR(errnum) \
- ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || \
- (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) || \
- (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) || \
- (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) || \
- (errnum == X509_V_ERR_CERT_UNTRUSTED) || \
- (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))
-
// Refer to schema 2018.3
// http://redfish.dmtf.org/schemas/v1/Certificate.json#/definitions/KeyUsage for
// supported KeyUsage types in redfish
@@ -94,21 +82,50 @@
{NID_OCSP_sign, "OCSPSigning"},
{NID_ad_timeStamping, "Timestamping"},
{NID_code_sign, "CodeSigning"}};
-} // namespace
-std::string Certificate::generateCertId(const std::string& certPath)
+/**
+ * @brief Copies the certificate from sourceFilePath to installFilePath
+ *
+ * @param[in] sourceFilePath - Path to the source file.
+ * @param[in] sourceFilePath - Path to the destination file.
+ *
+ * @return void
+ */
+void copyCertificate(const std::string& certSrcFilePath,
+ const std::string& certFilePath)
{
- const internal::X509Ptr cert = loadCert(certPath);
- unsigned long subjectNameHash = X509_subject_name_hash(cert.get());
- unsigned long issuerSerialHash = X509_issuer_and_serial_hash(cert.get());
- static constexpr auto CERT_ID_LENGTH = 17;
- char idBuff[CERT_ID_LENGTH];
-
- snprintf(idBuff, CERT_ID_LENGTH, "%08lx%08lx", subjectNameHash,
- issuerSerialHash);
-
- return std::string(idBuff);
+ // Copy the certificate to the installation path
+ // During bootup will be parsing existing file so no need to
+ // copy it.
+ if (certSrcFilePath != certFilePath)
+ {
+ std::ifstream inputCertFileStream;
+ std::ofstream outputCertFileStream;
+ inputCertFileStream.exceptions(std::ifstream::failbit |
+ std::ifstream::badbit |
+ std::ifstream::eofbit);
+ outputCertFileStream.exceptions(std::ofstream::failbit |
+ std::ofstream::badbit |
+ std::ofstream::eofbit);
+ try
+ {
+ inputCertFileStream.open(certSrcFilePath);
+ outputCertFileStream.open(certFilePath, std::ios::out);
+ outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
+ inputCertFileStream.close();
+ outputCertFileStream.close();
+ }
+ catch (const std::exception& e)
+ {
+ log<level::ERR>("Failed to copy certificate",
+ entry("ERR=%s", e.what()),
+ entry("SRC=%s", certSrcFilePath.c_str()),
+ entry("DST=%s", certFilePath.c_str()));
+ elog<InternalFailure>();
+ }
+ }
}
+} // namespace
std::string
Certificate::generateUniqueFilePath(const std::string& directoryPath)
@@ -242,7 +259,6 @@
{
log<level::INFO>("Certificate install ",
entry("FILEPATH=%s", certSrcFilePath.c_str()));
- auto errCode = X509_V_OK;
// stop watch for user initiated certificate install
if (certWatch != nullptr)
@@ -277,176 +293,48 @@
elog<InternalFailure>();
}
- // Create an empty X509_STORE structure for certificate validation.
- X509StorePtr x509Store(X509_STORE_new(), &X509_STORE_free);
- if (!x509Store)
- {
- log<level::ERR>("Error occurred during X509_STORE_new call");
- elog<InternalFailure>();
- }
-
- OpenSSL_add_all_algorithms();
-
- // ADD Certificate Lookup method.
- // lookup will be cleaned up automatically when the holding Store goes away.
- auto lookup = X509_STORE_add_lookup(x509Store.get(), X509_LOOKUP_file());
-
- if (!lookup)
- {
- log<level::ERR>("Error occurred during X509_STORE_add_lookup call");
- elog<InternalFailure>();
- }
- // Load Certificate file.
- errCode = X509_LOOKUP_load_file(lookup, certSrcFilePath.c_str(),
- X509_FILETYPE_PEM);
- if (errCode != 1)
- {
- log<level::ERR>("Error occurred during X509_LOOKUP_load_file call",
- entry("FILE=%s", certSrcFilePath.c_str()));
- elog<InvalidCertificateError>(
- InvalidCertificate::REASON("Invalid certificate file format"));
- }
+ X509StorePtr x509Store = getX509Store(certSrcFilePath);
// Load Certificate file into the X509 structure.
internal::X509Ptr cert = loadCert(certSrcFilePath);
- X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
- if (!storeCtx)
+
+ // Perform validation
+ validateCertificateAgainstStore(*x509Store, *cert);
+ validateCertificateStartDate(*cert);
+ validateCertificateInSSLContext(*cert);
+
+ // Invoke type specific append private key function.
+ if (auto it = appendKeyMap.find(certType); it == appendKeyMap.end())
{
- log<level::ERR>("Error occurred during X509_STORE_CTX_new call",
- entry("FILE=%s", certSrcFilePath.c_str()));
+ log<level::ERR>("Unsupported Type",
+ entry("TYPE=%s", certificateTypeToString(certType)));
elog<InternalFailure>();
}
-
- errCode = X509_STORE_CTX_init(storeCtx.get(), x509Store.get(), cert.get(),
- nullptr);
- if (errCode != 1)
- {
- log<level::ERR>("Error occurred during X509_STORE_CTX_init call",
- entry("FILE=%s", certSrcFilePath.c_str()));
- elog<InternalFailure>();
- }
-
- // Set time to current time.
- auto locTime = time(nullptr);
-
- X509_STORE_CTX_set_time(storeCtx.get(), X509_V_FLAG_USE_CHECK_TIME,
- locTime);
-
- errCode = X509_verify_cert(storeCtx.get());
- if (errCode == 1)
- {
- errCode = X509_V_OK;
- }
- else if (errCode == 0)
- {
- errCode = X509_STORE_CTX_get_error(storeCtx.get());
- log<level::INFO>(
- "Error occurred during X509_verify_cert call, checking for known "
- "error",
- entry("FILE=%s", certSrcFilePath.c_str()),
- entry("ERRCODE=%d", errCode),
- entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
- }
else
{
- log<level::ERR>("Error occurred during X509_verify_cert call",
- entry("FILE=%s", certSrcFilePath.c_str()));
- elog<InternalFailure>();
+ it->second(certSrcFilePath);
}
- // Allow certificate upload, for "certificate is not yet valid" and
- // trust chain related errors.
- if (!((errCode == X509_V_OK) ||
- (errCode == X509_V_ERR_CERT_NOT_YET_VALID) ||
- TRUST_CHAIN_ERR(errCode)))
- {
- if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
- {
- log<level::ERR>("Expired certificate ");
- elog<InvalidCertificateError>(
- InvalidCertificate::REASON("Expired Certificate"));
- }
- // Logging general error here.
- log<level::ERR>(
- "Certificate validation failed", entry("ERRCODE=%d", errCode),
- entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
- elog<InvalidCertificateError>(
- InvalidCertificate::REASON("Certificate validation failed"));
- }
-
- validateCertificateStartDate(*cert);
-
- // Verify that the certificate can be used in a TLS context
- const SSL_METHOD* method = TLS_method();
- std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)> ctx(SSL_CTX_new(method),
- SSL_CTX_free);
- if (SSL_CTX_use_certificate(ctx.get(), cert.get()) != 1)
- {
- log<level::ERR>("Certificate is not usable",
- entry("ERRCODE=%x", ERR_get_error()));
- elog<InvalidCertificateError>(
- InvalidCertificate::REASON("Certificate is not usable"));
- }
-
- // Invoke type specific append private key function.
- auto appendIter = appendKeyMap.find(certType);
- if (appendIter == appendKeyMap.end())
- {
- log<level::ERR>("Unsupported Type",
- entry("TYPE=%s", certificateTypeToString(certType)));
- elog<InternalFailure>();
- }
- appendIter->second(certSrcFilePath);
-
// Invoke type specific compare keys function.
- auto compIter = typeFuncMap.find(certType);
- if (compIter == typeFuncMap.end())
+ if (auto it = typeFuncMap.find(certType); it == typeFuncMap.end())
{
log<level::ERR>("Unsupported Type",
entry("TYPE=%s", certificateTypeToString(certType)));
elog<InternalFailure>();
}
- compIter->second(certSrcFilePath);
-
- // Copy the certificate to the installation path
- // During bootup will be parsing existing file so no need to
- // copy it.
- if (certSrcFilePath != certFilePath)
+ else
{
- std::ifstream inputCertFileStream;
- std::ofstream outputCertFileStream;
- inputCertFileStream.exceptions(std::ifstream::failbit |
- std::ifstream::badbit |
- std::ifstream::eofbit);
- outputCertFileStream.exceptions(std::ofstream::failbit |
- std::ofstream::badbit |
- std::ofstream::eofbit);
-
- try
- {
- inputCertFileStream.open(certSrcFilePath);
- outputCertFileStream.open(certFilePath, std::ios::out);
- outputCertFileStream << inputCertFileStream.rdbuf() << std::flush;
- inputCertFileStream.close();
- outputCertFileStream.close();
- }
- catch (const std::exception& e)
- {
- log<level::ERR>("Failed to copy certificate",
- entry("ERR=%s", e.what()),
- entry("SRC=%s", certSrcFilePath.c_str()),
- entry("DST=%s", certFilePath.c_str()));
- elog<InternalFailure>();
- }
+ it->second(certSrcFilePath);
}
+ copyCertificate(certSrcFilePath, certFilePath);
storageUpdate();
// Keep certificate ID
- certId = generateCertId(certFilePath);
+ certId = generateCertId(*cert);
// Parse the certificate file and populate properties
- populateProperties(certFilePath);
+ populateProperties(*cert);
// restart watch
if (certWatch != nullptr)
@@ -455,31 +343,10 @@
}
}
-// Checks that notBefore is not earlier than the unix epoch given that
-// the corresponding DBus interface is uint64_t.
-void Certificate::validateCertificateStartDate(X509& cert)
-{
- int days = 0;
- int secs = 0;
-
- ASN1TimePtr epoch(ASN1_TIME_new(), ASN1_STRING_free);
- // Set time to 00:00am GMT, Jan 1 1970; format: YYYYMMDDHHMMSSZ
- ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
-
- ASN1_TIME* notBefore = X509_get_notBefore(&cert);
- ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
-
- if (days < 0 || secs < 0)
- {
- log<level::ERR>("Certificate valid date starts before the Unix Epoch");
- elog<InvalidCertificateError>(InvalidCertificate::REASON(
- "NotBefore should after 19700101000000Z"));
- }
-}
-
void Certificate::populateProperties()
{
- populateProperties(certInstallPath);
+ internal::X509Ptr cert = loadCert(certInstallPath);
+ populateProperties(*cert);
}
std::string Certificate::getCertId() const
@@ -489,7 +356,8 @@
bool Certificate::isSame(const std::string& certPath)
{
- return getCertId() == generateCertId(certPath);
+ internal::X509Ptr cert = loadCert(certPath);
+ return getCertId() == generateCertId(*cert);
}
void Certificate::storageUpdate()
@@ -520,12 +388,11 @@
}
}
-void Certificate::populateProperties(const std::string& certPath)
+void Certificate::populateProperties(X509& cert)
{
- internal::X509Ptr cert = loadCert(certPath);
// Update properties if no error thrown
BIOMemPtr certBio(BIO_new(BIO_s_mem()), BIO_free);
- PEM_write_bio_X509(certBio.get(), cert.get());
+ PEM_write_bio_X509(certBio.get(), &cert);
BufMemPtr certBuf(BUF_MEM_new(), BUF_MEM_free);
BUF_MEM* buf = certBuf.get();
BIO_get_mem_ptr(certBio.get(), &buf);
@@ -535,16 +402,16 @@
static const int maxKeySize = 4096;
char subBuffer[maxKeySize] = {0};
BIOMemPtr subBio(BIO_new(BIO_s_mem()), BIO_free);
- // This pointer cannot be freed independently.
- X509_NAME* sub = X509_get_subject_name(cert.get());
+ // This pointer cannot be freed independantly.
+ X509_NAME* sub = X509_get_subject_name(&cert);
X509_NAME_print_ex(subBio.get(), sub, 0, XN_FLAG_SEP_COMMA_PLUS);
BIO_read(subBio.get(), subBuffer, maxKeySize);
subject(subBuffer);
char issuerBuffer[maxKeySize] = {0};
BIOMemPtr issuerBio(BIO_new(BIO_s_mem()), BIO_free);
- // This pointer cannot be freed independently.
- X509_NAME* issuer_name = X509_get_issuer_name(cert.get());
+ // This pointer cannot be freed independantly.
+ X509_NAME* issuer_name = X509_get_issuer_name(&cert);
X509_NAME_print_ex(issuerBio.get(), issuer_name, 0, XN_FLAG_SEP_COMMA_PLUS);
BIO_read(issuerBio.get(), issuerBuffer, maxKeySize);
issuer(issuerBuffer);
@@ -555,7 +422,7 @@
// Go through each usage in the bit string and convert to
// corresponding string value
if ((usage = static_cast<ASN1_BIT_STRING*>(
- X509_get_ext_d2i(cert.get(), NID_key_usage, nullptr, nullptr))))
+ X509_get_ext_d2i(&cert, NID_key_usage, nullptr, nullptr))))
{
for (auto i = 0; i < usage->length; ++i)
{
@@ -571,8 +438,8 @@
}
EXTENDED_KEY_USAGE* extUsage;
- if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(X509_get_ext_d2i(
- cert.get(), NID_ext_key_usage, nullptr, nullptr))))
+ if ((extUsage = static_cast<EXTENDED_KEY_USAGE*>(
+ X509_get_ext_d2i(&cert, NID_ext_key_usage, nullptr, nullptr))))
{
for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
{
@@ -590,45 +457,15 @@
ASN1_TIME_set_string(epoch.get(), "19700101000000Z");
static const uint64_t dayToSeconds = 24 * 60 * 60;
- ASN1_TIME* notAfter = X509_get_notAfter(cert.get());
+ ASN1_TIME* notAfter = X509_get_notAfter(&cert);
ASN1_TIME_diff(&days, &secs, epoch.get(), notAfter);
validNotAfter((days * dayToSeconds) + secs);
- ASN1_TIME* notBefore = X509_get_notBefore(cert.get());
+ ASN1_TIME* notBefore = X509_get_notBefore(&cert);
ASN1_TIME_diff(&days, &secs, epoch.get(), notBefore);
validNotBefore((days * dayToSeconds) + secs);
}
-internal::X509Ptr Certificate::loadCert(const std::string& filePath)
-{
- // Read Certificate file
- internal::X509Ptr cert(X509_new(), ::X509_free);
- if (!cert)
- {
- log<level::ERR>("Error occurred during X509_new call",
- entry("FILE=%s", filePath.c_str()),
- entry("ERRCODE=%lu", ERR_get_error()));
- elog<InternalFailure>();
- }
-
- BIOMemPtr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
- if (!bioCert)
- {
- log<level::ERR>("Error occurred during BIO_new_file call",
- entry("FILE=%s", filePath.c_str()));
- elog<InternalFailure>();
- }
-
- X509* x509 = cert.get();
- if (!PEM_read_bio_X509(bioCert.get(), &x509, nullptr, nullptr))
- {
- log<level::ERR>("Error occurred during PEM_read_bio_X509 call",
- entry("FILE=%s", filePath.c_str()));
- elog<InternalFailure>();
- }
- return cert;
-}
-
void Certificate::checkAndAppendPrivateKey(const std::string& filePath)
{
BIOMemPtr keyBio(BIO_new(BIO_s_file()), ::BIO_free);