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>
diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp
index 36477da..551d474 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 @@
}
#include <boost/asio/ssl/context.hpp>
+#include <boost/system/error_code.hpp>
#include <optional>
#include <random>
@@ -101,63 +104,40 @@
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);
-
- FILE* file = fopen(filepath.c_str(), "r");
- if (file != nullptr)
+ boost::beast::file_posix file;
+ boost::system::error_code ec;
+ file.open(filepath.c_str(), boost::beast::file_mode::read, ec);
+ if (ec)
{
- 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)
- {
- BMCWEB_LOG_INFO("Found an RSA key");
- if (RSA_check_key(rsa) == 1)
- {
- privateKeyValid = true;
- }
- else
- {
- 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);
- }
- }
-#else
- EVP_PKEY_CTX* pkeyCtx = EVP_PKEY_CTX_new_from_pkey(nullptr, pkey,
- nullptr);
+ 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 "";
+ }
- if (pkeyCtx == nullptr)
- {
- BMCWEB_LOG_ERROR("Unable to allocate pkeyCtx {}",
- ERR_get_error());
- }
- else if (EVP_PKEY_check(pkeyCtx) == 1)
+ 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)
+ {
+#if (OPENSSL_VERSION_NUMBER < 0x30000000L)
+ 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;
}
@@ -166,37 +146,72 @@
BMCWEB_LOG_ERROR("Key not valid error number {}",
ERR_get_error());
}
-#endif
-
- if (privateKeyValid)
+ RSA_free(rsa);
+ }
+ else
+ {
+ EC_KEY* ec = EVP_PKEY_get1_EC_KEY(pkey);
+ if (ec != nullptr)
{
- // 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_INFO("Found an EC key");
+ if (EC_KEY_check_key(ec) == 1)
{
- BMCWEB_LOG_ERROR("error getting x509 cert {}",
- ERR_get_error());
+ privateKeyValid = true;
}
else
{
- certValid = validateCertificate(x509);
- X509_free(x509);
+ BMCWEB_LOG_ERROR("Key not valid error number {}",
+ ERR_get_error());
}
+ EC_KEY_free(ec);
}
+ }
+#else
+ 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)
+ {
+ 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)
+ {
+ BMCWEB_LOG_ERROR("error getting x509 cert {}", ERR_get_error());
+ }
+ else
+ {
+ 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);
}
- return certValid;
+ if (!certValid)
+ {
+ return "";
+ }
+ return fileContents;
}
inline X509* loadCert(const std::string& filePath)
@@ -249,13 +264,26 @@
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)
{
- FILE* pFile = nullptr;
+ 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)
+{
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 @@
// 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);
-
- PEM_write_X509(pFile, x509);
- fclose(pFile);
- pFile = nullptr;
+ BMCWEB_LOG_ERROR(
+ "Failed to write pkey with code {}. Ignoring.", pkeyRet);
}
+ 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 @@
}
// cleanup_openssl();
+ return buffer;
}
EVP_PKEY* createEcKey()
@@ -415,17 +459,24 @@
#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 @@
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)
{