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);
diff --git a/certificate.hpp b/certificate.hpp
index c96017f..fa3191a 100644
--- a/certificate.hpp
+++ b/certificate.hpp
@@ -146,31 +146,14 @@
private:
/**
- * @brief Return error if ceritificate NotBefore date is lt 1970
+ * @brief Populate certificate properties by parsing given certificate
+ * object
*
- * Parse the certificate and return error if certificate NotBefore date
- * is lt 1970.
- *
- * @param[in] cert Reference to certificate object uploaded
+ * @param[in] cert The given certificate object
*
* @return void
*/
- void validateCertificateStartDate(X509& cert);
-
- /**
- * @brief Populate certificate properties by parsing given certificate file
- *
- * @param[in] certPath Path to certificate that should be parsed
- *
- * @return void
- */
- void populateProperties(const std::string& certPath);
-
- /** @brief Load Certificate file into the X509 structure.
- * @param[in] filePath - Certificate and key full file path.
- * @return pointer to the X509 structure.
- */
- internal::X509Ptr loadCert(const std::string& filePath);
+ void populateProperties(X509& cert);
/** @brief Check and append private key to the certificate file
* If private key is not present in the certificate file append the
@@ -190,15 +173,6 @@
bool compareKeys(const std::string& filePath);
/**
- * @brief Generate certificate ID based on provided certificate file.
- *
- * @param[in] certPath - Certificate file path.
- *
- * @return Certificate ID as formatted string.
- */
- std::string generateCertId(const std::string& certPath);
-
- /**
* @brief Generate file name which is unique in the provided directory.
*
* @param[in] directoryPath - Directory path.
diff --git a/meson.build b/meson.build
index e0893a6..68c56c6 100644
--- a/meson.build
+++ b/meson.build
@@ -66,6 +66,7 @@
'certs_manager.cpp',
'csr.cpp',
'watch.cpp',
+ 'x509_utils.cpp',
],
dependencies: phosphor_certificate_deps,
)
diff --git a/x509_utils.cpp b/x509_utils.cpp
new file mode 100644
index 0000000..9a589dd
--- /dev/null
+++ b/x509_utils.cpp
@@ -0,0 +1,228 @@
+#include "x509_utils.hpp"
+
+#include <openssl/asn1.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/ssl3.h>
+#include <openssl/x509_vfy.h>
+
+#include <cstdio>
+#include <ctime>
+#include <exception>
+#include <memory>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Certs/error.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+namespace phosphor::certs
+{
+
+namespace
+{
+
+using ::phosphor::logging::elog;
+using ::phosphor::logging::entry;
+using ::phosphor::logging::level;
+using ::phosphor::logging::log;
+using ::sdbusplus::xyz::openbmc_project::Certs::Error::InvalidCertificate;
+using ::sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using Reason = ::phosphor::logging::xyz::openbmc_project::Certs::
+ InvalidCertificate::REASON;
+
+// RAII support for openSSL functions.
+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 X509Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
+using BIOMemPtr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
+using ASN1TimePtr = std::unique_ptr<ASN1_TIME, decltype(&ASN1_STRING_free)>;
+using SSLCtxPtr = std::unique_ptr<SSL_CTX, decltype(&::SSL_CTX_free)>;
+
+// Trust chain related errors.`
+constexpr bool isTrustChainError(int error)
+{
+ return error == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT ||
+ error == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN ||
+ error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY ||
+ error == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT ||
+ error == X509_V_ERR_CERT_UNTRUSTED ||
+ error == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE;
+}
+} // namespace
+
+X509StorePtr getX509Store(const std::string& certSrcPath)
+{
+ // 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 the Certificate file into X509 Store.
+ if (int errCode = X509_LOOKUP_load_file(lookup, certSrcPath.c_str(),
+ X509_FILETYPE_PEM);
+ errCode != 1)
+ {
+ log<level::ERR>("Error occurred during X509_LOOKUP_load_file call",
+ entry("FILE=%s", certSrcPath.c_str()));
+ elog<InvalidCertificate>(Reason("Invalid certificate file format"));
+ }
+ return x509Store;
+}
+
+X509Ptr loadCert(const std::string& filePath)
+{
+ // Read Certificate file
+ 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;
+}
+
+// Checks that notBefore is not earlier than the unix epoch given that
+// the corresponding DBus interface is uint64_t.
+void 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<InvalidCertificate>(
+ Reason("NotBefore should after 19700101000000Z"));
+ }
+}
+
+void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert)
+{
+ int errCode = X509_V_OK;
+ X509StoreCtxPtr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
+ if (!storeCtx)
+ {
+ log<level::ERR>("Error occurred during X509_STORE_CTX_new call");
+ elog<InternalFailure>();
+ }
+
+ errCode = X509_STORE_CTX_init(storeCtx.get(), &x509Store, &cert, nullptr);
+ if (errCode != 1)
+ {
+ log<level::ERR>("Error occurred during X509_STORE_CTX_init call");
+ 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("ERRCODE=%d", errCode),
+ entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
+ }
+ else
+ {
+ log<level::ERR>("Error occurred during X509_verify_cert call");
+ elog<InternalFailure>();
+ }
+
+ // 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) ||
+ isTrustChainError(errCode)))
+ {
+ if (errCode == X509_V_ERR_CERT_HAS_EXPIRED)
+ {
+ log<level::ERR>("Expired certificate ");
+ elog<InvalidCertificate>(Reason("Expired Certificate"));
+ }
+ // Loging general error here.
+ log<level::ERR>(
+ "Certificate validation failed", entry("ERRCODE=%d", errCode),
+ entry("ERROR_STR=%s", X509_verify_cert_error_string(errCode)));
+ elog<InvalidCertificate>(Reason("Certificate validation failed"));
+ }
+}
+
+void validateCertificateInSSLContext(X509& cert)
+{
+ const SSL_METHOD* method = TLS_method();
+ SSLCtxPtr ctx(SSL_CTX_new(method), SSL_CTX_free);
+ if (SSL_CTX_use_certificate(ctx.get(), &cert) != 1)
+ {
+ log<level::ERR>("Certificate is not usable",
+ entry("ERRCODE=%x", ERR_get_error()));
+ elog<InvalidCertificate>(Reason("Certificate is not usable"));
+ }
+}
+
+std::string generateCertId(X509& cert)
+{
+ unsigned long subjectNameHash = X509_subject_name_hash(&cert);
+ unsigned long issuerSerialHash = X509_issuer_and_serial_hash(&cert);
+ static constexpr auto CERT_ID_LENGTH = 17;
+ char idBuff[CERT_ID_LENGTH];
+
+ snprintf(idBuff, CERT_ID_LENGTH, "%08lx%08lx", subjectNameHash,
+ issuerSerialHash);
+
+ return {idBuff};
+}
+} // namespace phosphor::certs
diff --git a/x509_utils.hpp b/x509_utils.hpp
new file mode 100644
index 0000000..e439aad
--- /dev/null
+++ b/x509_utils.hpp
@@ -0,0 +1,61 @@
+#include <openssl/ossl_typ.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <memory>
+#include <string>
+
+namespace phosphor::certs
+{
+
+/** @brief Creates an X509 Store from the given certSrcPath
+ * Creates an X509 Store, adds a lookup file to the store from the given source
+ * certificate, and returns it
+ * @param[in] certSrcPath - the file path to a list of trusted certificates
+ *
+ */
+std::unique_ptr<X509_STORE, decltype(&::X509_STORE_free)>
+ getX509Store(const std::string& certSrcPath);
+
+/** @brief Loads Certificate file into the X509 structure.
+ * @param[in] filePath - Certificate and key full file path.
+ * @return pointer to the X509 structure.
+ */
+std::unique_ptr<X509, decltype(&::X509_free)>
+ loadCert(const std::string& filePath);
+
+/**
+ * @brief Parses the certificate and throws error if certificate NotBefore date
+ * is lt 1970
+ * @param[in] cert Reference to certificate object uploaded
+ * @return void
+ */
+void validateCertificateStartDate(X509& cert);
+
+/**
+ * @brief Validates the certificate against the trusted certificates store and
+ * throws error if certificate is not valid
+ * @param[in] x509Store Reference to trusted certificates store
+ * @param[in] cert Reference to certificate to be validated
+ * @return void
+ */
+void validateCertificateAgainstStore(X509_STORE& x509Store, X509& cert);
+
+/**
+ * @brief Validates the certificate can be used in an SSL context, otherwise,
+ * throws errors
+ * @param[in] cert Reference to certificate to be validated
+ * @return void
+ */
+void validateCertificateInSSLContext(X509& cert);
+
+/**
+ * @brief Generates certificate ID based on provided certificate file.
+ *
+ * @param[in] cert - Certificate object.
+ *
+ * @return Certificate ID as formatted string.
+ */
+std::string generateCertId(X509& cert);
+
+} // namespace phosphor::certs