summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bmcweb.service.in1
-rw-r--r--crow/include/crow/app.h48
-rw-r--r--crow/include/crow/http_server.h77
-rw-r--r--include/ssl_key_handler.hpp32
-rw-r--r--redfish-core/include/redfish.hpp8
-rw-r--r--redfish-core/lib/certificate_service.hpp804
-rw-r--r--redfish-core/lib/network_protocol.hpp4
-rw-r--r--redfish-core/lib/service_root.hpp5
-rw-r--r--src/webserver_main.cpp9
9 files changed, 927 insertions, 61 deletions
diff --git a/bmcweb.service.in b/bmcweb.service.in
index 644eccae5f..fdb023b809 100644
--- a/bmcweb.service.in
+++ b/bmcweb.service.in
@@ -5,6 +5,7 @@ Wants=network.target
After=network.target
[Service]
+ExecReload=kill -s HUP $MAINPID
ExecStart=@CMAKE_INSTALL_PREFIX@/bin/bmcweb
Type=simple
WorkingDirectory=/home/root
diff --git a/crow/include/crow/app.h b/crow/include/crow/app.h
index 95bbaed0bc..4bdf9ff852 100644
--- a/crow/include/crow/app.h
+++ b/crow/include/crow/app.h
@@ -99,12 +99,12 @@ template <typename... Middlewares> class Crow
if (-1 == socketFd)
{
sslServer = std::move(std::make_unique<ssl_server_t>(
- this, bindaddrStr, portUint, &middlewares, &sslContext, io));
+ this, bindaddrStr, portUint, sslContext, &middlewares, io));
}
else
{
sslServer = std::move(std::make_unique<ssl_server_t>(
- this, socketFd, &middlewares, &sslContext, io));
+ this, socketFd, sslContext, &middlewares, io));
}
sslServer->setTickFunction(tickInterval, tickFunction);
sslServer->run();
@@ -114,12 +114,12 @@ template <typename... Middlewares> class Crow
if (-1 == socketFd)
{
server = std::move(std::make_unique<server_t>(
- this, bindaddrStr, portUint, &middlewares, nullptr, io));
+ this, bindaddrStr, portUint, nullptr, &middlewares, io));
}
else
{
server = std::move(std::make_unique<server_t>(
- this, socketFd, &middlewares, nullptr, io));
+ this, socketFd, nullptr, &middlewares, io));
}
server->setTickFunction(tickInterval, tickFunction);
server->run();
@@ -153,36 +153,42 @@ template <typename... Middlewares> class Crow
self_t& sslFile(const std::string& crt_filename,
const std::string& key_filename)
{
- sslContext.set_verify_mode(boost::asio::ssl::verify_peer);
- sslContext.use_certificate_file(crt_filename, ssl_context_t::pem);
- sslContext.use_private_key_file(key_filename, ssl_context_t::pem);
- sslContext.set_options(boost::asio::ssl::context::default_workarounds |
- boost::asio::ssl::context::no_sslv2 |
- boost::asio::ssl::context::no_sslv3 |
- boost::asio::ssl::context::no_tlsv1 |
- boost::asio::ssl::context::no_tlsv1_1);
+ sslContext = std::make_shared<ssl_context_t>(
+ boost::asio::ssl::context::tls_server);
+ sslContext->set_verify_mode(boost::asio::ssl::verify_peer);
+ sslContext->use_certificate_file(crt_filename, ssl_context_t::pem);
+ sslContext->use_private_key_file(key_filename, ssl_context_t::pem);
+ sslContext->set_options(boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::no_sslv3 |
+ boost::asio::ssl::context::no_tlsv1 |
+ boost::asio::ssl::context::no_tlsv1_1);
return *this;
}
self_t& sslFile(const std::string& pem_filename)
{
- sslContext.set_verify_mode(boost::asio::ssl::verify_peer);
- sslContext.load_verify_file(pem_filename);
- sslContext.set_options(boost::asio::ssl::context::default_workarounds |
- boost::asio::ssl::context::no_sslv2 |
- boost::asio::ssl::context::no_sslv3 |
- boost::asio::ssl::context::no_tlsv1 |
- boost::asio::ssl::context::no_tlsv1_1);
+ sslContext = std::make_shared<ssl_context_t>(
+ boost::asio::ssl::context::tls_server);
+ sslContext->set_verify_mode(boost::asio::ssl::verify_peer);
+ sslContext->load_verify_file(pem_filename);
+ sslContext->set_options(boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::no_sslv3 |
+ boost::asio::ssl::context::no_tlsv1 |
+ boost::asio::ssl::context::no_tlsv1_1);
return *this;
}
- self_t& ssl(boost::asio::ssl::context&& ctx)
+ self_t& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
{
sslContext = std::move(ctx);
+ BMCWEB_LOG_INFO << "app::ssl context use_count="
+ << sslContext.use_count();
return *this;
}
- ssl_context_t sslContext{boost::asio::ssl::context::tls_server};
+ std::shared_ptr<ssl_context_t> sslContext = nullptr;
#else
template <typename T, typename... Remain> self_t& ssl_file(T&&, Remain&&...)
diff --git a/crow/include/crow/http_server.h b/crow/include/crow/http_server.h
index 36bab51438..d8a258f0f8 100644
--- a/crow/include/crow/http_server.h
+++ b/crow/include/crow/http_server.h
@@ -15,8 +15,10 @@
#include <boost/date_time/posix_time/posix_time.hpp>
#include <chrono>
#include <cstdint>
+#include <filesystem>
#include <future>
#include <memory>
+#include <ssl_key_handler.hpp>
#include <utility>
#include <vector>
@@ -35,39 +37,39 @@ class Server
{
public:
Server(Handler* handler, std::unique_ptr<tcp::acceptor>&& acceptor,
+ std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx,
std::tuple<Middlewares...>* middlewares = nullptr,
- boost::asio::ssl::context* adaptor_ctx = nullptr,
std::shared_ptr<boost::asio::io_context> io =
std::make_shared<boost::asio::io_context>()) :
ioService(std::move(io)),
- acceptor(std::move(acceptor)), signals(*ioService, SIGINT, SIGTERM),
- tickTimer(*ioService), handler(handler), middlewares(middlewares),
- adaptorCtx(adaptor_ctx)
+ acceptor(std::move(acceptor)),
+ signals(*ioService, SIGINT, SIGTERM, SIGHUP), tickTimer(*ioService),
+ handler(handler), adaptorCtx(adaptor_ctx), middlewares(middlewares)
{
}
Server(Handler* handler, const std::string& bindaddr, uint16_t port,
+ std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx,
std::tuple<Middlewares...>* middlewares = nullptr,
- boost::asio::ssl::context* adaptor_ctx = nullptr,
std::shared_ptr<boost::asio::io_context> io =
std::make_shared<boost::asio::io_context>()) :
Server(handler,
std::make_unique<tcp::acceptor>(
*io, tcp::endpoint(boost::asio::ip::make_address(bindaddr),
port)),
- middlewares, adaptor_ctx, io)
+ adaptor_ctx, middlewares, io)
{
}
Server(Handler* handler, int existing_socket,
+ std::shared_ptr<boost::asio::ssl::context>& adaptor_ctx,
std::tuple<Middlewares...>* middlewares = nullptr,
- boost::asio::ssl::context* adaptor_ctx = nullptr,
std::shared_ptr<boost::asio::io_context> io =
std::make_shared<boost::asio::io_context>()) :
Server(handler,
std::make_unique<tcp::acceptor>(*io, boost::asio::ip::tcp::v6(),
existing_socket),
- middlewares, adaptor_ctx, io)
+ adaptor_ctx, middlewares, io)
{
}
@@ -109,6 +111,7 @@ class Server
void run()
{
+ loadCertificate();
updateDateStr();
getCachedDateStr = [this]() -> std::string {
@@ -153,11 +156,61 @@ class Server
BMCWEB_LOG_INFO << serverName << " server is running, local endpoint "
<< acceptor->local_endpoint();
+ startAsyncWaitForSignal();
+ doAccept();
+ }
- signals.async_wait([&](const boost::system::error_code& /*error*/,
- int /*signal_number*/) { stop(); });
+ void loadCertificate()
+ {
+#ifdef BMCWEB_ENABLE_SSL
+ namespace fs = std::filesystem;
+ // Cleanup older certificate file existing in the system
+ fs::path oldCert = "/home/root/server.pem";
+ if (fs::exists(oldCert))
+ {
+ fs::remove("/home/root/server.pem");
+ }
+ fs::path certPath = "/etc/ssl/certs/https/";
+ // if path does not exist create the path so that
+ // self signed certificate can be created in the
+ // path
+ if (!fs::exists(certPath))
+ {
+ fs::create_directories(certPath);
+ }
+ fs::path certFile = certPath / "server.pem";
+ BMCWEB_LOG_INFO << "Building SSL Context file=" << certFile;
+ std::string sslPemFile(certFile);
+ ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
+ std::shared_ptr<boost::asio::ssl::context> sslContext =
+ ensuressl::getSslContext(sslPemFile);
+ adaptorCtx = sslContext;
+ handler->ssl(std::move(sslContext));
+#endif
+ }
- doAccept();
+ void startAsyncWaitForSignal()
+ {
+ signals.async_wait([this](const boost::system::error_code& ec,
+ int signalNo) {
+ if (ec)
+ {
+ BMCWEB_LOG_INFO << "Error in signal handler" << ec.message();
+ }
+ else
+ {
+ if (signalNo == SIGHUP)
+ {
+ BMCWEB_LOG_INFO << "Receivied reload signal";
+ loadCertificate();
+ this->startAsyncWaitForSignal();
+ }
+ else
+ {
+ stop();
+ }
+ }
+ });
}
void stop()
@@ -240,6 +293,6 @@ class Server
#ifdef BMCWEB_ENABLE_SSL
bool useSsl{false};
#endif
- boost::asio::ssl::context* adaptorCtx;
+ std::shared_ptr<boost::asio::ssl::context> adaptorCtx;
}; // namespace crow
} // namespace crow
diff --git a/include/ssl_key_handler.hpp b/include/ssl_key_handler.hpp
index e309d70a30..2b67661d62 100644
--- a/include/ssl_key_handler.hpp
+++ b/include/ssl_key_handler.hpp
@@ -279,28 +279,30 @@ inline void ensureOpensslKeyPresentAndValid(const std::string &filepath)
}
}
-inline boost::asio::ssl::context getSslContext(const std::string &ssl_pem_file)
+inline std::shared_ptr<boost::asio::ssl::context>
+ getSslContext(const std::string &ssl_pem_file)
{
- boost::asio::ssl::context mSslContext{
- boost::asio::ssl::context::tls_server};
- mSslContext.set_options(boost::asio::ssl::context::default_workarounds |
- boost::asio::ssl::context::no_sslv2 |
- boost::asio::ssl::context::no_sslv3 |
- boost::asio::ssl::context::single_dh_use |
- boost::asio::ssl::context::no_tlsv1 |
- boost::asio::ssl::context::no_tlsv1_1);
+ std::shared_ptr<boost::asio::ssl::context> mSslContext =
+ std::make_shared<boost::asio::ssl::context>(
+ boost::asio::ssl::context::tls_server);
+ mSslContext->set_options(boost::asio::ssl::context::default_workarounds |
+ boost::asio::ssl::context::no_sslv2 |
+ boost::asio::ssl::context::no_sslv3 |
+ boost::asio::ssl::context::single_dh_use |
+ boost::asio::ssl::context::no_tlsv1 |
+ boost::asio::ssl::context::no_tlsv1_1);
// m_ssl_context.set_verify_mode(boost::asio::ssl::verify_peer);
- mSslContext.use_certificate_file(ssl_pem_file,
- boost::asio::ssl::context::pem);
- mSslContext.use_private_key_file(ssl_pem_file,
- boost::asio::ssl::context::pem);
+ mSslContext->use_certificate_file(ssl_pem_file,
+ boost::asio::ssl::context::pem);
+ mSslContext->use_private_key_file(ssl_pem_file,
+ boost::asio::ssl::context::pem);
// Set up EC curves to auto (boost asio doesn't have a method for this)
// There is a pull request to add this. Once this is included in an asio
// drop, use the right way
// http://stackoverflow.com/questions/18929049/boost-asio-with-ecdsa-certificate-issue
- if (SSL_CTX_set_ecdh_auto(mSslContext.native_handle(), 1) != 1)
+ if (SSL_CTX_set_ecdh_auto(mSslContext->native_handle(), 1) != 1)
{
BMCWEB_LOG_ERROR << "Error setting tmp ecdh list\n";
}
@@ -316,7 +318,7 @@ inline boost::asio::ssl::context getSslContext(const std::string &ssl_pem_file)
"ECDHE-ECDSA-AES128-SHA256:"
"ECDHE-RSA-AES128-SHA256";
- if (SSL_CTX_set_cipher_list(mSslContext.native_handle(),
+ if (SSL_CTX_set_cipher_list(mSslContext->native_handle(),
mozillaModern.c_str()) != 1)
{
BMCWEB_LOG_ERROR << "Error setting cipher list\n";
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 91578f13c3..9edffa9692 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -16,6 +16,7 @@
#pragma once
#include "../lib/account_service.hpp"
+#include "../lib/certificate_service.hpp"
#include "../lib/chassis.hpp"
#include "../lib/cpudimm.hpp"
#include "../lib/ethernet.hpp"
@@ -115,7 +116,12 @@ class RedfishService
nodes.emplace_back(std::make_unique<BaseMessageRegistry>(app));
nodes.emplace_back(std::make_unique<OpenBMCMessageRegistryFile>(app));
nodes.emplace_back(std::make_unique<OpenBMCMessageRegistry>(app));
-
+ nodes.emplace_back(std::make_unique<CertificateService>(app));
+ nodes.emplace_back(
+ std::make_unique<CertificateActionsReplaceCertificate>(app));
+ nodes.emplace_back(std::make_unique<CertificateLocations>(app));
+ nodes.emplace_back(std::make_unique<HTTPSCertificateCollection>(app));
+ nodes.emplace_back(std::make_unique<HTTPSCertificate>(app));
for (const auto& node : nodes)
{
node->initPrivileges();
diff --git a/redfish-core/lib/certificate_service.hpp b/redfish-core/lib/certificate_service.hpp
new file mode 100644
index 0000000000..8045e4ea91
--- /dev/null
+++ b/redfish-core/lib/certificate_service.hpp
@@ -0,0 +1,804 @@
+/*
+// Copyright (c) 2018 IBM Corporation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+*/
+#pragma once
+
+#include "node.hpp"
+
+#include <variant>
+namespace redfish
+{
+namespace certs
+{
+constexpr char const *httpsObjectPath =
+ "/xyz/openbmc_project/certs/server/https";
+constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install";
+constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
+constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate";
+constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties";
+constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
+constexpr char const *mapperBusName = "xyz.openbmc_project.ObjectMapper";
+constexpr char const *mapperObjectPath = "/xyz/openbmc_project/object_mapper";
+constexpr char const *mapperIntf = "xyz.openbmc_project.ObjectMapper";
+} // namespace certs
+
+/**
+ * The Certificate schema defines a Certificate Service which represents the
+ * actions available to manage certificates and links to where certificates
+ * are installed.
+ */
+class CertificateService : public Node
+{
+ public:
+ CertificateService(CrowApp &app) :
+ Node(app, "/redfish/v1/CertificateService/")
+ {
+ // TODO: Issue#61 No entries are available for Certificate
+ // sevice at https://www.dmtf.org/standards/redfish
+ // "redfish standard registries". Need to modify after DMTF
+ // publish Privilege details for certificate service
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ res.jsonValue = {
+ {"@odata.type", "#CertificateService.v1_0_0.CertificateService"},
+ {"@odata.id", "/redfish/v1/CertificateService"},
+ {"@odata.context",
+ "/redfish/v1/$metadata#CertificateService.CertificateService"},
+ {"Id", "CertificateService"},
+ {"Name", "Certificate Service"},
+ {"Description", "Actions available to manage certificates"}};
+ res.jsonValue["CertificateLocations"] = {
+ {"@odata.id",
+ "/redfish/v1/CertificateService/CertificateLocations"}};
+ res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = {
+ {"target", "/redfish/v1/CertificateService/Actions/"
+ "CertificateService.ReplaceCertificate"},
+ {"CertificateType@Redfish.AllowableValues", {"PEM"}}};
+ res.end();
+ }
+}; // CertificateService
+/**
+ * @brief Find the ID specified in the URL
+ * Finds the numbers specified after the last "/" in the URL and returns.
+ * @param[in] path URL
+ * @return -1 on failure and number on success
+ */
+long getIDFromURL(const std::string_view url)
+{
+ std::size_t found = url.rfind("/");
+ if (found == std::string::npos)
+ {
+ return -1;
+ }
+ if ((found + 1) < url.length())
+ {
+ char *endPtr;
+ std::string_view str = url.substr(found + 1);
+ long value = std::strtol(str.data(), &endPtr, 10);
+ if (endPtr != &str.back())
+ {
+ return -1;
+ }
+ return value;
+ }
+ return -1;
+}
+
+/**
+ * Class to create a temporary certificate file for uploading to system
+ */
+class CertificateFile
+{
+ public:
+ CertificateFile() = delete;
+ CertificateFile(const CertificateFile &) = delete;
+ CertificateFile &operator=(const CertificateFile &) = delete;
+ CertificateFile(CertificateFile &&) = delete;
+ CertificateFile &operator=(CertificateFile &&) = delete;
+ CertificateFile(const std::string &certString)
+ {
+ char dirTemplate[] = "/tmp/Certs.XXXXXX";
+ char *tempDirectory = mkdtemp(dirTemplate);
+ if (tempDirectory)
+ {
+ certDirectory = tempDirectory;
+ certificateFile = certDirectory / "cert.pem";
+ std::ofstream out(certificateFile, std::ofstream::out |
+ std::ofstream::binary |
+ std::ofstream::trunc);
+ out << certString;
+ out.close();
+ BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile;
+ }
+ }
+ ~CertificateFile()
+ {
+ if (std::filesystem::exists(certDirectory))
+ {
+ BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile;
+ try
+ {
+ std::filesystem::remove_all(certDirectory);
+ }
+ catch (const std::filesystem::filesystem_error &e)
+ {
+ BMCWEB_LOG_ERROR << "Failed to remove temp directory"
+ << certDirectory;
+ }
+ }
+ }
+ std::string getCertFilePath()
+ {
+ return certificateFile;
+ }
+
+ private:
+ std::filesystem::path certificateFile;
+ std::filesystem::path certDirectory;
+};
+
+/**
+ * @brief Parse and update Certficate Issue/Subject property
+ *
+ * @param[in] asyncResp Shared pointer to the response message
+ * @param[in] str Issuer/Subject value in key=value pairs
+ * @param[in] type Issuer/Subject
+ * @return None
+ */
+static void updateCertIssuerOrSubject(nlohmann::json &out,
+ const std::string_view value)
+{
+ // example: O=openbmc-project.xyz,CN=localhost
+ std::string_view::iterator i = value.begin();
+ while (i != value.end())
+ {
+ std::string_view::iterator tokenBegin = i;
+ while (i != value.end() && *i != '=')
+ {
+ i++;
+ }
+ if (i == value.end())
+ {
+ break;
+ }
+ const std::string_view key(tokenBegin, i - tokenBegin);
+ i++;
+ tokenBegin = i;
+ while (i != value.end() && *i != ',')
+ {
+ i++;
+ }
+ const std::string_view val(tokenBegin, i - tokenBegin);
+ if (key == "L")
+ {
+ out["City"] = val;
+ }
+ else if (key == "CN")
+ {
+ out["CommonName"] = val;
+ }
+ else if (key == "C")
+ {
+ out["Country"] = val;
+ }
+ else if (key == "O")
+ {
+ out["Organization"] = val;
+ }
+ else if (key == "OU")
+ {
+ out["OrganizationalUnit"] = val;
+ }
+ else if (key == "ST")
+ {
+ out["State"] = val;
+ }
+ // skip comma character
+ if (i != value.end())
+ {
+ i++;
+ }
+ }
+}
+
+/**
+ * @brief Retrieve the certificates properties and append to the response
+ * message
+ *
+ * @param[in] asyncResp Shared pointer to the response message
+ * @param[in] objectPath Path of the D-Bus service object
+ * @param[in] certId Id of the certificate
+ * @param[in] certURL URL of the certificate object
+ * @param[in] name name of the certificate
+ * @return None
+ */
+static void getCertificateProperties(
+ const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
+ long certId, const std::string &certURL, const std::string &name)
+{
+ using PropertyType =
+ std::variant<std::string, uint64_t, std::vector<std::string>>;
+ using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
+ BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
+ << " certId=" << certId << " certURl=" << certURL;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, objectPath, certURL, certId,
+ name](const boost::system::error_code ec, const GetObjectType &resp) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ if (resp.size() > 1 || resp.empty())
+ {
+ BMCWEB_LOG_ERROR << "Invalid number of objects found "
+ << resp.size();
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string &service = resp.begin()->first;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, certURL, certId,
+ name](const boost::system::error_code ec,
+ const PropertiesMap &properties) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ asyncResp->res.jsonValue = {
+ {"@odata.id", certURL},
+ {"@odata.type", "#Certificate.v1_0_0.Certificate"},
+ {"@odata.context",
+ "/redfish/v1/$metadata#Certificate.Certificate"},
+ {"Id", std::to_string(certId)},
+ {"Name", name},
+ {"Description", name}};
+ for (const auto &property : properties)
+ {
+ if (property.first == "CertificateString")
+ {
+ asyncResp->res.jsonValue["CertificateString"] = "";
+ const std::string *value =
+ std::get_if<std::string>(&property.second);
+ if (value)
+ {
+ asyncResp->res.jsonValue["CertificateString"] =
+ *value;
+ }
+ }
+ else if (property.first == "KeyUsage")
+ {
+ nlohmann::json &keyUsage =
+ asyncResp->res.jsonValue["KeyUsage"];
+ keyUsage = nlohmann::json::array();
+ const std::vector<std::string> *value =
+ std::get_if<std::vector<std::string>>(
+ &property.second);
+ if (value)
+ {
+ for (const std::string &usage : *value)
+ {
+ keyUsage.push_back(usage);
+ }
+ }
+ }
+ else if (property.first == "Issuer")
+ {
+ const std::string *value =
+ std::get_if<std::string>(&property.second);
+ if (value)
+ {
+ updateCertIssuerOrSubject(
+ asyncResp->res.jsonValue["Issuer"], *value);
+ }
+ }
+ else if (property.first == "Subject")
+ {
+ const std::string *value =
+ std::get_if<std::string>(&property.second);
+ if (value)
+ {
+ updateCertIssuerOrSubject(
+ asyncResp->res.jsonValue["Subject"],
+ *value);
+ }
+ }
+ else if (property.first == "ValidNotAfter")
+ {
+ const uint64_t *value =
+ std::get_if<uint64_t>(&property.second);
+ if (value)
+ {
+ std::time_t time =
+ static_cast<std::time_t>(*value);
+ asyncResp->res.jsonValue["ValidNotAfter"] =
+ crow::utility::getDateTime(time);
+ }
+ }
+ else if (property.first == "ValidNotBefore")
+ {
+ const uint64_t *value =
+ std::get_if<uint64_t>(&property.second);
+ if (value)
+ {
+ std::time_t time =
+ static_cast<std::time_t>(*value);
+ asyncResp->res.jsonValue["ValidNotBefore"] =
+ crow::utility::getDateTime(time);
+ }
+ }
+ }
+ asyncResp->res.addHeader("Location", certURL);
+ },
+ service, objectPath, certs::dbusPropIntf, "GetAll",
+ certs::certPropIntf);
+ },
+ certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
+ "GetObject", objectPath,
+ std::array<const char *, 1>{certs::certPropIntf});
+}
+
+using GetObjectType =
+ std::vector<std::pair<std::string, std::vector<std::string>>>;
+
+/**
+ * Action to replace an existing certificate
+ */
+class CertificateActionsReplaceCertificate : public Node
+{
+ public:
+ CertificateActionsReplaceCertificate(CrowApp &app) :
+ Node(app, "/redfish/v1/CertificateService/Actions/"
+ "CertificateService.ReplaceCertificate/")
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ }
+
+ private:
+ void doPost(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ std::string certificate;
+ nlohmann::json certificateUri;
+ std::optional<std::string> certificateType = "PEM";
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ if (!json_util::readJson(req, asyncResp->res, "CertificateString",
+ certificate, "CertificateUri", certificateUri,
+ "CertificateType", certificateType))
+ {
+ BMCWEB_LOG_ERROR << "Required parameters are missing";
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ if (!certificateType)
+ {
+ // should never happen, but it never hurts to be paranoid.
+ return;
+ }
+ if (certificateType != "PEM")
+ {
+ messages::actionParameterNotSupported(
+ asyncResp->res, "CertificateType", "ReplaceCertificate");
+ return;
+ }
+
+ std::string certURI;
+ if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
+ "@odata.id", certURI))
+ {
+ messages::actionParameterMissing(
+ asyncResp->res, "ReplaceCertificate", "CertificateUri");
+ return;
+ }
+
+ if (!boost::starts_with(
+ certURI,
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
+ {
+ BMCWEB_LOG_ERROR << "Unsupported certificate URI" << certURI;
+ messages::actionParameterValueFormatError(asyncResp->res, certURI,
+ "CertificateUri",
+ "ReplaceCertificate");
+ return;
+ }
+
+ BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
+ long id = getIDFromURL(certURI);
+ if (id < 0)
+ {
+ messages::actionParameterValueFormatError(asyncResp->res, certURI,
+ "CertificateUri",
+ "ReplaceCertificate");
+ return;
+ }
+ std::string objectPath;
+ std::string name;
+ if (boost::starts_with(
+ certURI,
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
+ {
+ objectPath =
+ std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
+ name = "HTTPS certificate";
+ }
+ else
+ {
+ messages::actionParameterNotSupported(
+ asyncResp->res, "CertificateUri", "ReplaceCertificate");
+ return;
+ }
+
+ std::shared_ptr<CertificateFile> certFile =
+ std::make_shared<CertificateFile>(certificate);
+
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, objectPath, certFile, id, certURI, name](
+ const boost::system::error_code ec, const GetObjectType &resp) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ if (resp.size() > 1 || resp.empty())
+ {
+ BMCWEB_LOG_ERROR << "Invalid number of objects found "
+ << resp.size();
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string &service = resp.begin()->first;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, certFile, objectPath, certURI, id,
+ name](const boost::system::error_code ec) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ getCertificateProperties(asyncResp, objectPath, id,
+ certURI, name);
+ BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
+ << certFile->getCertFilePath();
+ },
+ service, objectPath, certs::certReplaceIntf, "Replace",
+ certFile->getCertFilePath());
+ },
+ certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
+ "GetObject", objectPath,
+ std::array<std::string, 1>({certs::certReplaceIntf}));
+ }
+}; // CertificateActionsReplaceCertificate
+
+/**
+ * Certificate resource describes a certificate used to prove the identity
+ * of a component, account or service.
+ */
+class HTTPSCertificate : public Node
+{
+ public:
+ template <typename CrowApp>
+ HTTPSCertificate(CrowApp &app) :
+ Node(app,
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
+ "<str>/",
+ std::string())
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ }
+
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ if (params.size() != 1)
+ {
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ long id = getIDFromURL(req.url);
+
+ BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
+ std::string certURL =
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
+ std::to_string(id);
+ std::string objectPath = certs::httpsObjectPath;
+ objectPath += "/";
+ objectPath += std::to_string(id);
+ getCertificateProperties(asyncResp, objectPath, id, certURL,
+ "HTTPS Certificate");
+ }
+
+}; // namespace redfish
+
+/**
+ * Collection of HTTPS certificates
+ */
+class HTTPSCertificateCollection : public Node
+{
+ public:
+ template <typename CrowApp>
+ HTTPSCertificateCollection(CrowApp &app) :
+ Node(app,
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ }
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ res.jsonValue = {
+ {"@odata.id",
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
+ {"@odata.type", "#CertificateCollection.CertificateCollection"},
+ {"@odata.context",
+ "/redfish/v1/"
+ "$metadata#CertificateCollection.CertificateCollection"},
+ {"Name", "HTTPS Certificates Collection"},
+ {"Description", "A Collection of HTTPS certificate instances"}};
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ crow::connections::systemBus->async_method_call(
+ [asyncResp](const boost::system::error_code ec,
+ const GetObjectType &resp) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ if (resp.size() > 1 || resp.empty())
+ {
+ BMCWEB_LOG_ERROR << "Invalid number of objects found "
+ << resp.size();
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string &service = resp.begin()->first;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp](const boost::system::error_code ec,
+ const ManagedObjectType &certs) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ nlohmann::json &members =
+ asyncResp->res.jsonValue["Members"];
+ members = nlohmann::json::array();
+ for (const auto &cert : certs)
+ {
+ long id = getIDFromURL(cert.first.str);
+ if (id != -1)
+ {
+ members.push_back(
+ {{"@odata.id",
+ "/redfish/v1/Managers/bmc/"
+ "NetworkProtocol/HTTPS/Certificates/" +
+ std::to_string(id)}});
+ }
+ }
+ asyncResp->res.jsonValue["Members@odata.count"] =
+ members.size();
+ },
+ service, certs::httpsObjectPath, certs::dbusObjManagerIntf,
+ "GetManagedObjects");
+ },
+ certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
+ "GetObject", certs::httpsObjectPath,
+ std::array<const char *, 1>{certs::certInstallIntf});
+ }
+
+ void doPost(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
+ {"Description", "HTTPS Certificate"}};
+
+ std::shared_ptr<CertificateFile> certFile =
+ std::make_shared<CertificateFile>(req.body);
+
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, certFile](const boost::system::error_code ec,
+ const GetObjectType &resp) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ if (resp.size() > 1 || resp.empty())
+ {
+ BMCWEB_LOG_ERROR << "Invalid number of objects found "
+ << resp.size();
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string &service = resp.begin()->first;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, certFile](const boost::system::error_code ec) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ // TODO: Issue#84 supporting only 1 certificate
+ long certId = 1;
+ std::string certURL =
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
+ "Certificates/" +
+ std::to_string(certId);
+ std::string objectPath =
+ std::string(certs::httpsObjectPath) + "/" +
+ std::to_string(certId);
+ getCertificateProperties(asyncResp, objectPath, certId,
+ certURL, "HTTPS Certificate");
+ BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
+ << certFile->getCertFilePath();
+ },
+ service, certs::httpsObjectPath, certs::certInstallIntf,
+ "Install", certFile->getCertFilePath());
+ },
+ certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
+ "GetObject", certs::httpsObjectPath,
+ std::array<const char *, 1>{certs::certInstallIntf});
+ }
+}; // HTTPSCertificateCollection
+
+/**
+ * @brief Retrieve the certificates installed list and append to the response
+ *
+ * @param[in] asyncResp Shared pointer to the response message
+ * @param[in] certURL Path of the certificate object
+ * @param[in] path Path of the D-Bus service object
+ * @return None
+ */
+static void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
+ const std::string &certURL,
+ const std::string &path)
+{
+ BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
+ << " Path=" << path;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, path, certURL](const boost::system::error_code ec,
+ const GetObjectType &resp) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ if (resp.size() > 1 || resp.empty())
+ {
+ BMCWEB_LOG_ERROR << "Invalid number of objects found "
+ << resp.size();
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string &service = resp.begin()->first;
+ crow::connections::systemBus->async_method_call(
+ [asyncResp, certURL](const boost::system::error_code ec,
+ const ManagedObjectType &certs) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+ nlohmann::json &links =
+ asyncResp->res.jsonValue["Links"]["Certificates"];
+ for (auto &cert : certs)
+ {
+ long id = getIDFromURL(cert.first.str);
+ if (id != -1)
+ {
+ links.push_back(
+ {{"@odata.id", certURL + std::to_string(id)}});
+ }
+ }
+ asyncResp->res
+ .jsonValue["Links"]["Certificates@odata.count"] =
+ links.size();
+ },
+ service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
+ },
+ certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
+ "GetObject", path, std::array<std::string, 0>());
+}
+
+/**
+ * The certificate location schema defines a resource that an administrator
+ * can use in order to locate all certificates installed on a given service.
+ */
+class CertificateLocations : public Node
+{
+ public:
+ template <typename CrowApp>
+ CertificateLocations(CrowApp &app) :
+ Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
+ {
+ entityPrivileges = {
+ {boost::beast::http::verb::get, {{"Login"}}},
+ {boost::beast::http::verb::head, {{"Login"}}},
+ {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+ {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+ }
+
+ private:
+ void doGet(crow::Response &res, const crow::Request &req,
+ const std::vector<std::string> &params) override
+ {
+ res.jsonValue = {
+ {"@odata.id",
+ "/redfish/v1/CertificateService/CertificateLocations"},
+ {"@odata.type",
+ "#CertificateLocations.v1_0_0.CertificateLocations"},
+ {"@odata.context",
+ "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
+ {"Name", "Certificate Locations"},
+ {"Id", "CertificateLocations"},
+ {"Description",
+ "Defines a resource that an administrator can use in order to "
+ "locate all certificates installed on a given service"}};
+ auto asyncResp = std::make_shared<AsyncResp>(res);
+ nlohmann::json &links =
+ asyncResp->res.jsonValue["Links"]["Certificates"];
+ links = nlohmann::json::array();
+ getCertificateLocations(
+ asyncResp,
+ "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
+ certs::httpsObjectPath);
+ }
+}; // CertificateLocations
+} // namespace redfish
diff --git a/redfish-core/lib/network_protocol.hpp b/redfish-core/lib/network_protocol.hpp
index d24f246109..23965faced 100644
--- a/redfish-core/lib/network_protocol.hpp
+++ b/redfish-core/lib/network_protocol.hpp
@@ -234,6 +234,9 @@ class NetworkProtocol : public Node
messages::internalError(asyncResp->res);
return;
}
+ asyncResp->res.jsonValue["HTTPS"]["Certificates"] = {
+ {"@odata.id", "/redfish/v1/Managers/bmc/NetworkProtocol/"
+ "HTTPS/Certificates/"}};
for (auto& unit : resp)
{
@@ -252,7 +255,6 @@ class NetworkProtocol : public Node
"running") ||
(std::get<NET_PROTO_UNIT_SUB_STATE>(unit) ==
"listening");
-
crow::connections::systemBus->async_method_call(
[asyncResp, service{std::string(service)}](
const boost::system::error_code ec,
diff --git a/redfish-core/lib/service_root.hpp b/redfish-core/lib/service_root.hpp
index ad2b2f2989..3b9adbf37c 100644
--- a/redfish-core/lib/service_root.hpp
+++ b/redfish-core/lib/service_root.hpp
@@ -42,7 +42,7 @@ class ServiceRoot : public Node
void doGet(crow::Response& res, const crow::Request& req,
const std::vector<std::string>& params) override
{
- res.jsonValue["@odata.type"] = "#ServiceRoot.v1_1_1.ServiceRoot";
+ res.jsonValue["@odata.type"] = "#ServiceRoot.v1_5_0.ServiceRoot";
res.jsonValue["@odata.id"] = "/redfish/v1";
res.jsonValue["@odata.context"] =
"/redfish/v1/$metadata#ServiceRoot.ServiceRoot";
@@ -65,8 +65,9 @@ class ServiceRoot : public Node
res.jsonValue["UpdateService"] = {
{"@odata.id", "/redfish/v1/UpdateService"}};
-
res.jsonValue["UUID"] = uuid;
+ res.jsonValue["CertificateService"] = {
+ {"@odata.id", "/redfish/v1/CertificateService"}};
res.end();
}
diff --git a/src/webserver_main.cpp b/src/webserver_main.cpp
index a4cf582aa7..1c9c1f18d3 100644
--- a/src/webserver_main.cpp
+++ b/src/webserver_main.cpp
@@ -61,15 +61,6 @@ int main(int argc, char** argv)
auto io = std::make_shared<boost::asio::io_context>();
CrowApp app(io);
-#ifdef BMCWEB_ENABLE_SSL
- std::string sslPemFile("server.pem");
- std::cout << "Building SSL Context\n";
-
- ensuressl::ensureOpensslKeyPresentAndValid(sslPemFile);
- std::cout << "SSL Enabled\n";
- auto sslContext = ensuressl::getSslContext(sslPemFile);
- app.ssl(std::move(sslContext));
-#endif
// Static assets need to be initialized before Authorization, because auth
// needs to build the whitelist from the static routes