diff options
author | Ed Tanous <ed@tanous.net> | 2024-03-28 08:03:05 +0300 |
---|---|---|
committer | Ed Tanous <ed@tanous.net> | 2024-06-10 18:18:59 +0300 |
commit | 099225cc9300c8e06b742a48318df75b0366561f (patch) | |
tree | 59725b175cf3b47f2cc94d0abaf8db02d303e952 | |
parent | 2ecde74fa187366dc4ed628e61a88015cdbeb769 (diff) | |
download | bmcweb-099225cc9300c8e06b742a48318df75b0366561f.tar.xz |
Make cert generate for readonly directories
When run from a development PC, we shouldn't REQUIRE that the cert
directory exists or is writable.
This commit reworks the SSL cert generation to generate a string with
the certification info, instead of writing it to disk and reading it
back. This allows bmcweb to start up in read-only environments, or
environments where there isn't access to the key information.
Tested: Launching the application on a dev desktop without an ssl
directory present no longer crashes.
Change-Id: I0d44eb1ce8d298986c5560803ca2d72958d3707c
Signed-off-by: Ed Tanous <ed@tanous.net>
-rw-r--r-- | http/http_server.hpp | 15 | ||||
-rw-r--r-- | include/hostname_monitor.hpp | 11 | ||||
-rw-r--r-- | include/ssl_key_handler.hpp | 237 | ||||
-rw-r--r-- | meson.build | 1 | ||||
-rw-r--r-- | src/ssl_key_handler_test.cpp | 1 | ||||
-rw-r--r-- | test/include/ssl_key_handler_test.cpp | 26 |
6 files changed, 198 insertions, 93 deletions
diff --git a/http/http_server.hpp b/http/http_server.hpp index 206a0d175e..6088ca1a2e 100644 --- a/http/http_server.hpp +++ b/http/http_server.hpp @@ -17,6 +17,7 @@ #include <filesystem> #include <future> #include <memory> +#include <string> #include <utility> #include <vector> @@ -96,9 +97,19 @@ class Server fs::path certFile = certPath / "server.pem"; BMCWEB_LOG_INFO("Building SSL Context file={}", certFile.string()); std::string sslPemFile(certFile); - ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile); + std::string cert = + ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile); + if (cert.empty()) + { + throw std::runtime_error("Failed to load string"); + } std::shared_ptr<boost::asio::ssl::context> sslContext = - ensuressl::getSslContext(sslPemFile); + ensuressl::getSslContext(cert); + if (sslContext == nullptr) + { + throw std::runtime_error("Failed to load certificate"); + } + BMCWEB_LOG_DEBUG("Replaced certificate"); adaptorCtx = sslContext; handler->ssl(std::move(sslContext)); } diff --git a/include/hostname_monitor.hpp b/include/hostname_monitor.hpp index bb6998798a..a3a176ded6 100644 --- a/include/hostname_monitor.hpp +++ b/include/hostname_monitor.hpp @@ -121,8 +121,15 @@ inline int onPropertyUpdate(sd_bus_message* m, void* /* userdata */, "Ready to generate new HTTPs certificate with subject cn: {}", *hostname); - ensuressl::generateSslCertificate("/tmp/hostname_cert.tmp", - *hostname); + std::string certData = ensuressl::generateSslCertificate(*hostname); + if (certData.empty()) + { + BMCWEB_LOG_ERROR("Failed to generate cert"); + return 0; + } + ensuressl::writeCertificateToFile("/tmp/hostname_cert.tmp", + certData); + installCertificate("/tmp/hostname_cert.tmp"); } ASN1_STRING_free(asn1); diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp index 36477da02c..551d474d69 100644 --- a/include/ssl_key_handler.hpp +++ b/include/ssl_key_handler.hpp @@ -5,6 +5,8 @@ #include "logging.hpp" #include "ossl_random.hpp" +#include <boost/beast/core/file_posix.hpp> + extern "C" { #include <nghttp2/nghttp2.h> @@ -20,6 +22,7 @@ extern "C" } #include <boost/asio/ssl/context.hpp> +#include <boost/system/error_code.hpp> #include <optional> #include <random> @@ -101,25 +104,57 @@ inline bool validateCertificate(X509* const cert) return false; } -inline bool verifyOpensslKeyCert(const std::string& filepath) +inline std::string verifyOpensslKeyCert(const std::string& filepath) { bool privateKeyValid = false; - bool certValid = false; BMCWEB_LOG_INFO("Checking certs in file {}", filepath); + boost::beast::file_posix file; + boost::system::error_code ec; + file.open(filepath.c_str(), boost::beast::file_mode::read, ec); + if (ec) + { + return ""; + } + bool certValid = false; + std::string fileContents; + fileContents.resize(static_cast<size_t>(file.size(ec)), '\0'); + file.read(fileContents.data(), fileContents.size(), ec); + if (ec) + { + BMCWEB_LOG_ERROR("Failed to read file"); + return ""; + } - FILE* file = fopen(filepath.c_str(), "r"); - if (file != nullptr) + BIO* bufio = BIO_new_mem_buf(static_cast<void*>(fileContents.data()), + static_cast<int>(fileContents.size())); + EVP_PKEY* pkey = PEM_read_bio_PrivateKey(bufio, nullptr, nullptr, nullptr); + BIO_free(bufio); + if (pkey != nullptr) { - EVP_PKEY* pkey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr); - if (pkey != nullptr) - { #if (OPENSSL_VERSION_NUMBER < 0x30000000L) - RSA* rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa != nullptr) + RSA* rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa != nullptr) + { + BMCWEB_LOG_INFO("Found an RSA key"); + if (RSA_check_key(rsa) == 1) + { + privateKeyValid = true; + } + else { - BMCWEB_LOG_INFO("Found an RSA key"); - if (RSA_check_key(rsa) == 1) + BMCWEB_LOG_ERROR("Key not valid error number {}", + ERR_get_error()); + } + RSA_free(rsa); + } + else + { + EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey); + if (ec != nullptr) + { + BMCWEB_LOG_INFO("Found an EC key"); + if (EC_KEY_check_key(ec) == 1) { privateKeyValid = true; } @@ -128,75 +163,55 @@ inline bool verifyOpensslKeyCert(const std::string& filepath) BMCWEB_LOG_ERROR("Key not valid error number {}", ERR_get_error()); } - RSA_free(rsa); - } - else - { - EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey); - if (ec != nullptr) - { - BMCWEB_LOG_INFO("Found an EC key"); - if (EC_KEY_check_key(ec) == 1) - { - privateKeyValid = true; - } - else - { - BMCWEB_LOG_ERROR("Key not valid error number {}", - ERR_get_error()); - } - EC_KEY_free(ec); - } + EC_KEY_free(ec); } + } #else - EVP_PKEY_CTX* pkeyCtx = EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, - nullptr); + EVP_PKEY_CTX* pkeyCtx = EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, + nullptr); - if (pkeyCtx == nullptr) - { - BMCWEB_LOG_ERROR("Unable to allocate pkeyCtx {}", - ERR_get_error()); - } - else if (EVP_PKEY_check(pkeyCtx) == 1) + if (pkeyCtx == nullptr) + { + BMCWEB_LOG_ERROR("Unable to allocate pkeyCtx {}", ERR_get_error()); + } + else if (EVP_PKEY_check(pkeyCtx) == 1) + { + privateKeyValid = true; + } + else + { + BMCWEB_LOG_ERROR("Key not valid error number {}", ERR_get_error()); + } +#endif + + if (privateKeyValid) + { + BIO* bufio2 = + BIO_new_mem_buf(static_cast<void*>(fileContents.data()), + static_cast<int>(fileContents.size())); + X509* x509 = PEM_read_bio_X509(bufio2, nullptr, nullptr, nullptr); + BIO_free(bufio2); + if (x509 == nullptr) { - privateKeyValid = true; + BMCWEB_LOG_ERROR("error getting x509 cert {}", ERR_get_error()); } else { - BMCWEB_LOG_ERROR("Key not valid error number {}", - ERR_get_error()); - } -#endif - - if (privateKeyValid) - { - // If the order is certificate followed by key in input file - // then, certificate read will fail. So, setting the file - // pointer to point beginning of file to avoid certificate and - // key order issue. - fseek(file, 0, SEEK_SET); - - X509* x509 = PEM_read_X509(file, nullptr, nullptr, nullptr); - if (x509 == nullptr) - { - BMCWEB_LOG_ERROR("error getting x509 cert {}", - ERR_get_error()); - } - else - { - certValid = validateCertificate(x509); - X509_free(x509); - } + certValid = validateCertificate(x509); + X509_free(x509); } + } #if (OPENSSL_VERSION_NUMBER > 0x30000000L) - EVP_PKEY_CTX_free(pkeyCtx); + EVP_PKEY_CTX_free(pkeyCtx); #endif - EVP_PKEY_free(pkey); - } - fclose(file); + EVP_PKEY_free(pkey); + } + if (!certValid) + { + return ""; } - return certValid; + return fileContents; } inline X509* loadCert(const std::string& filePath) @@ -249,13 +264,26 @@ inline int addExt(X509* cert, int nid, const char* value) return 0; } -inline void generateSslCertificate(const std::string& filepath, - const std::string& cn) +// Writes a certificate to a path, ignoring errors +inline void writeCertificateToFile(const std::string& filepath, + const std::string& certificate) +{ + boost::system::error_code ec; + boost::beast::file_posix file; + file.open(filepath.c_str(), boost::beast::file_mode::write, ec); + if (!ec) + { + file.write(certificate.data(), certificate.size(), ec); + // ignore result + } +} + +inline std::string generateSslCertificate(const std::string& cn) { - FILE* pFile = nullptr; BMCWEB_LOG_INFO("Generating new keys"); initOpenssl(); + std::string buffer; BMCWEB_LOG_INFO("Generating EC key"); EVP_PKEY* pPrivKey = createEcKey(); if (pPrivKey != nullptr) @@ -316,18 +344,33 @@ inline void generateSslCertificate(const std::string& filepath, // Sign the certificate with our private key X509_sign(x509, pPrivKey, EVP_sha256()); - pFile = fopen(filepath.c_str(), "wt"); + BIO* bufio = BIO_new(BIO_s_mem()); - if (pFile != nullptr) + int pkeyRet = PEM_write_bio_PrivateKey( + bufio, pPrivKey, nullptr, nullptr, 0, nullptr, nullptr); + if (pkeyRet <= 0) { - PEM_write_PrivateKey(pFile, pPrivKey, nullptr, nullptr, 0, - nullptr, nullptr); + BMCWEB_LOG_ERROR( + "Failed to write pkey with code {}. Ignoring.", pkeyRet); + } - PEM_write_X509(pFile, x509); - fclose(pFile); - pFile = nullptr; + char* data = nullptr; + long int dataLen = BIO_get_mem_data(bufio, &data); + buffer += std::string_view(data, static_cast<size_t>(dataLen)); + BIO_free(bufio); + + bufio = BIO_new(BIO_s_mem()); + pkeyRet = PEM_write_bio_X509(bufio, x509); + if (pkeyRet <= 0) + { + BMCWEB_LOG_ERROR( + "Failed to write X509 with code {}. Ignoring.", pkeyRet); } + dataLen = BIO_get_mem_data(bufio, &data); + buffer += std::string_view(data, static_cast<size_t>(dataLen)); + BIO_free(bufio); + BMCWEB_LOG_INFO("Cert size is {}", buffer.size()); X509_free(x509); } @@ -336,6 +379,7 @@ inline void generateSslCertificate(const std::string& filepath, } // cleanup_openssl(); + return buffer; } EVP_PKEY* createEcKey() @@ -415,17 +459,24 @@ void initOpenssl() #endif } -inline void ensureOpensslKeyPresentAndValid(const std::string& filepath) +inline std::string ensureOpensslKeyPresentAndValid(const std::string& filepath) { - bool pemFileValid = false; + std::string cert = verifyOpensslKeyCert(filepath); - pemFileValid = verifyOpensslKeyCert(filepath); - - if (!pemFileValid) + if (cert.empty()) { BMCWEB_LOG_WARNING("Error in verifying signature, regenerating"); - generateSslCertificate(filepath, "testhost"); + cert = generateSslCertificate("testhost"); + if (cert.empty()) + { + BMCWEB_LOG_ERROR("Failed to generate cert"); + } + else + { + writeCertificateToFile(filepath, cert); + } } + return cert; } inline int nextProtoCallback(SSL* /*unused*/, const unsigned char** data, @@ -480,10 +531,20 @@ inline std::shared_ptr<boost::asio::ssl::context> BMCWEB_LOG_DEBUG("Using default TrustStore location: {}", trustStorePath); mSslContext->add_verify_path(trustStorePath); - mSslContext->use_certificate_file(sslPemFile, - boost::asio::ssl::context::pem); - mSslContext->use_private_key_file(sslPemFile, - boost::asio::ssl::context::pem); + boost::system::error_code ec; + boost::asio::const_buffer buf(sslPemFile.data(), sslPemFile.size()); + mSslContext->use_certificate(buf, boost::asio::ssl::context::pem, ec); + if (ec) + { + BMCWEB_LOG_CRITICAL("Failed to open ssl certificate"); + return nullptr; + } + mSslContext->use_private_key(buf, boost::asio::ssl::context::pem); + if (ec) + { + BMCWEB_LOG_CRITICAL("Failed to open ssl pkey"); + return nullptr; + } if constexpr (BMCWEB_EXPERIMENTAL_HTTP2) { diff --git a/meson.build b/meson.build index 79c38b476b..42f24707d6 100644 --- a/meson.build +++ b/meson.build @@ -385,6 +385,7 @@ srcfiles_unittest = files( 'test/include/multipart_test.cpp', 'test/include/openbmc_dbus_rest_test.cpp', 'test/include/ossl_random.cpp', + 'test/include/ssl_key_handler_test.cpp', 'test/include/str_utility_test.cpp', 'test/redfish-core/include/privileges_test.cpp', 'test/redfish-core/include/redfish_aggregator_test.cpp', diff --git a/src/ssl_key_handler_test.cpp b/src/ssl_key_handler_test.cpp deleted file mode 100644 index a2b0f55ad3..0000000000 --- a/src/ssl_key_handler_test.cpp +++ /dev/null @@ -1 +0,0 @@ -// TODO(ed) WRITE TESTS FOR THIS diff --git a/test/include/ssl_key_handler_test.cpp b/test/include/ssl_key_handler_test.cpp new file mode 100644 index 0000000000..f60252ff6e --- /dev/null +++ b/test/include/ssl_key_handler_test.cpp @@ -0,0 +1,26 @@ +#include "file_test_utilities.hpp" +#include "ssl_key_handler.hpp" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +namespace ensuressl +{ + +TEST(SSLKeyHandler, GenerateVerifyRoundTrip) +{ + /* Verifies that we can generate a certificate, then read back in the + * certificate that was read */ + TemporaryFileHandle myFile(""); + std::string cert = generateSslCertificate("TestCommonName"); + + EXPECT_FALSE(cert.empty()); + + writeCertificateToFile(myFile.stringPath, cert); + + std::string cert2 = verifyOpensslKeyCert(myFile.stringPath); + EXPECT_FALSE(cert2.empty()); + EXPECT_EQ(cert, cert2); +} + +} // namespace ensuressl |