Add Certificate verification support
Call X509_verify_cert to perform the following validations:
o Check trust settings on the root CA
o Validity of the certificate chain by
enabling (X509_V_ERR_CERT_HAS_EXPIRED).
For details of the verification, refer:
https://www.openssl.org/docs/manmaster/man1/verify.html
Change-Id: I5fcde5d34658e7b483de2715831107509f31b531
Signed-off-by: Jayanth Othayoth <ojayanth@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 7ff740a..d107521 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,7 +16,9 @@
phosphor_certificate_manager_LDFLAGS = \
$(SDBUSPLUS_LIBS) \
$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
- -lstdc++fs
+ -lstdc++fs \
+ -lssl \
+ -lcrypto
phosphor_certificate_manager_CXXFLAGS = \
$(SYSTEMD_CFLAGS) \
diff --git a/certs_manager.cpp b/certs_manager.cpp
index d1ff25d..7ecefa2 100644
--- a/certs_manager.cpp
+++ b/certs_manager.cpp
@@ -1,25 +1,52 @@
#include "certs_manager.hpp"
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/x509v3.h>
+
#include <experimental/filesystem>
#include <phosphor-logging/elog-errors.hpp>
#include <phosphor-logging/elog.hpp>
#include <phosphor-logging/log.hpp>
#include <sdbusplus/bus.hpp>
+#include <xyz/openbmc_project/Certs/Install/error.hpp>
#include <xyz/openbmc_project/Common/error.hpp>
namespace phosphor
{
namespace certs
{
+// RAII support for openSSL functions.
+using BIO_MEM_Ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;
+using X509_STORE_CTX_Ptr =
+ std::unique_ptr<X509_STORE_CTX, decltype(&::X509_STORE_CTX_free)>;
+using X509_LOOKUP_Ptr =
+ std::unique_ptr<X509_LOOKUP, decltype(&::X509_LOOKUP_free)>;
+namespace fs = std::experimental::filesystem;
using namespace phosphor::logging;
using InternalFailure =
sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidCertificate =
+ sdbusplus::xyz::openbmc_project::Certs::Install::Error::InvalidCertificate;
+using Reason = xyz::openbmc_project::Certs::Install::InvalidCertificate::REASON;
void Manager::install(const std::string path)
{
- // TODO Validate the certificate file
-
+ // Verify the certificate file
+ auto rc = verifyCert(path);
+ if (rc != X509_V_OK)
+ {
+ if (rc == X509_V_ERR_CERT_HAS_EXPIRED)
+ {
+ elog<InvalidCertificate>(Reason("Expired Certificate"));
+ }
+ // Loging general error here.
+ elog<InvalidCertificate>(Reason("Certificate validation failed"));
+ }
// Copy the certificate file
copy(path, certPath);
@@ -89,5 +116,141 @@
}
}
+X509_Ptr Manager::loadCert(const std::string& filePath)
+{
+ // Read Certificate file
+ X509_Ptr cert(X509_new(), ::X509_free);
+ if (!cert)
+ {
+ log<level::ERR>("Error occured during X509_new call",
+ entry("FILE=%s", filePath.c_str()),
+ entry("ERRCODE=%lu", ERR_get_error()));
+ elog<InternalFailure>();
+ }
+
+ BIO_MEM_Ptr bioCert(BIO_new_file(filePath.c_str(), "rb"), ::BIO_free);
+ if (!bioCert)
+ {
+ log<level::ERR>("Error occured 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 occured during PEM_read_bio_X509 call",
+ entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+ return cert;
+}
+
+int32_t Manager::verifyCert(const std::string& filePath)
+{
+ auto errCode = X509_V_OK;
+
+ fs::path file(filePath);
+ if (!fs::exists(file))
+ {
+ log<level::ERR>("File is Missing", entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+
+ try
+ {
+ if (fs::file_size(filePath) == 0)
+ {
+ // file is empty
+ log<level::ERR>("File is empty",
+ entry("FILE=%s", filePath.c_str()));
+ elog<InvalidCertificate>(Reason("File is empty"));
+ }
+ }
+ catch (const fs::filesystem_error& e)
+ {
+ // Log Error message
+ log<level::ERR>(e.what(), entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+
+ // Defining store object as RAW to avoid double free.
+ // X509_LOOKUP_free free up store object.
+ // Create an empty X509_STORE structure for certificate validation.
+ auto x509Store = X509_STORE_new();
+ if (!x509Store)
+ {
+ log<level::ERR>("Error occured during X509_STORE_new call");
+ elog<InternalFailure>();
+ }
+
+ OpenSSL_add_all_algorithms();
+
+ // ADD Certificate Lookup method.
+ X509_LOOKUP_Ptr lookup(X509_STORE_add_lookup(x509Store, X509_LOOKUP_file()),
+ ::X509_LOOKUP_free);
+ if (!lookup)
+ {
+ // Normally lookup cleanup function interanlly does X509Store cleanup
+ // Free up the X509Store.
+ X509_STORE_free(x509Store);
+ log<level::ERR>("Error occured during X509_STORE_add_lookup call");
+ elog<InternalFailure>();
+ }
+ // Load Certificate file.
+ int32_t rc = X509_LOOKUP_load_file(lookup.get(), filePath.c_str(),
+ X509_FILETYPE_PEM);
+ if (rc != 1)
+ {
+ log<level::ERR>("Error occured during X509_LOOKUP_load_file call",
+ entry("FILE=%s", filePath.c_str()));
+ elog<InvalidCertificate>(Reason("Invalid certificate file format"));
+ }
+
+ // Load Certificate file into the X509 structre.
+ X509_Ptr cert = std::move(loadCert(filePath));
+ X509_STORE_CTX_Ptr storeCtx(X509_STORE_CTX_new(), ::X509_STORE_CTX_free);
+ if (!storeCtx)
+ {
+ log<level::ERR>("Error occured during X509_STORE_CTX_new call",
+ entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+
+ rc = X509_STORE_CTX_init(storeCtx.get(), x509Store, cert.get(), NULL);
+ if (rc != 1)
+ {
+ log<level::ERR>("Error occured during X509_STORE_CTX_init call",
+ entry("FILE=%s", filePath.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);
+
+ rc = X509_verify_cert(storeCtx.get());
+ if (rc == 1)
+ {
+ errCode = X509_V_OK;
+ }
+ else if (rc == 0)
+ {
+ errCode = X509_STORE_CTX_get_error(storeCtx.get());
+ log<level::ERR>("Certificate verification failed",
+ entry("FILE=%s", filePath.c_str()),
+ entry("ERRCODE=%d", errCode));
+ }
+ else
+ {
+ log<level::ERR>("Error occured during X509_verify_cert call",
+ entry("FILE=%s", filePath.c_str()));
+ elog<InternalFailure>();
+ }
+ return errCode;
+}
+
} // namespace certs
} // namespace phosphor
diff --git a/certs_manager.hpp b/certs_manager.hpp
index f001df7..76f64b4 100644
--- a/certs_manager.hpp
+++ b/certs_manager.hpp
@@ -1,4 +1,6 @@
#pragma once
+#include <openssl/x509.h>
+
#include <cstring>
#include <sdbusplus/bus.hpp>
#include <sdbusplus/server/object.hpp>
@@ -9,6 +11,8 @@
{
namespace certs
{
+// RAII support for openSSL functions.
+using X509_Ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
// Supported Types.
static constexpr auto SERVER = "server";
@@ -84,6 +88,20 @@
*/
void copy(const std::string& src, const std::string& dst);
+ /** @brief Certificate verification function
+ * Certificate file specific validation using openssl
+ * verify function also includes expiry date check
+ * @param[in] fileName - Certificate and key full file path.
+ * @return error code from open ssl verify function.
+ */
+ int32_t verifyCert(const std::string& filePath);
+
+ /** @brief Load Certificate file into the X509 structre.
+ * @param[in] fileName - Certificate and key full file path.
+ * @return pointer to the X509 structure.
+ */
+ X509_Ptr loadCert(const std::string& filePath);
+
/** @brief sdbusplus handler */
sdbusplus::bus::bus& bus;
diff --git a/test/Makefile.am b/test/Makefile.am
index 814966e..279148c 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -18,6 +18,7 @@
-lstdc++fs \
$(SDBUSPLUS_LIBS) \
$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ -lssl -lcrypto \
-lgtest -lgtest_main -lgmock $(PTHREAD_CFLAGS) $(OESDK_TESTCASE_FLAGS)
check_PROGRAMS =
@@ -27,7 +28,7 @@
# Build/add certs_manager_test to test suite
check_PROGRAMS += certs_manager_test
-certs_manager_test_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_CPPFLAGS)
+certs_manager_test_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_CPPFLAGS)
certs_manager_test_LDFLAGS = $(AM_LDFLAGS) $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS)
certs_manager_test_SOURCES = certs_manager_test.cpp
certs_manager_test_LDADD = $(top_builddir)/certs_manager.o