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/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