summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEd Tanous <ed@tanous.net>2024-03-28 08:03:05 +0300
committerEd Tanous <ed@tanous.net>2024-06-10 18:18:59 +0300
commit099225cc9300c8e06b742a48318df75b0366561f (patch)
tree59725b175cf3b47f2cc94d0abaf8db02d303e952
parent2ecde74fa187366dc4ed628e61a88015cdbeb769 (diff)
downloadbmcweb-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.hpp15
-rw-r--r--include/hostname_monitor.hpp11
-rw-r--r--include/ssl_key_handler.hpp237
-rw-r--r--meson.build1
-rw-r--r--src/ssl_key_handler_test.cpp1
-rw-r--r--test/include/ssl_key_handler_test.cpp26
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