summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarri Devender Rao <devenrao@in.ibm.com>2019-01-21 19:27:12 +0300
committerEd Tanous <ed.tanous@intel.com>2019-06-13 01:53:05 +0300
commit5968caee7ccf978755a9a63d225965101a0c0378 (patch)
tree81f7298459cc19487bcbc4774d5e84764c1d5283
parentd22c8396f0bcec4488d0c98eae3092384b3a5929 (diff)
downloadbmcweb-5968caee7ccf978755a9a63d225965101a0c0378.tar.xz
Redfish: Add certificate service to manage HTTPS certificates
Implements CertificateService schema to list the actions available. Implements CertificateLocations schema to list the certificates present in the system. Implements CertificateCollection schema to upload/list existing HTTPS certificates Implements Certificate schema to view existing HTTPS certificate Cater for reloading the SSL context after a certificate is uploaded. Fix Certificate signature validation failure At present bmcweb uses the certificate from "/home/root/server.pem" the same is modified to "/etc/ssl/certs/https/server.pem" as phosphor-certificate-manager uses the specified path to install/replace certificates. Bmcweb creates a self-signed certificate when certificate is not present. Catered for creating "/etc/ssl/certs/https/" direcotry structure so that self signed certificate is created in the path. Implements ReplaceCertificate action of Certificate Service for replacing existing HTTPS certificates Cleanup of older self-signed certificate at /home/root/server.pem 1. Tested schema with validator and no issues 2. Privilege map for certificate service is not yet pubished 2. GET on /redfish/v1/CertificateService/ "CertificateService": { "@odata.id": "/redfish/v1/CertificateService" }, 3. GET on /redfish/v1/CertificateService/CertificateLocations/ "@odata.context": "/redfish/v1/$metadata#CertificateLocations.CertificateLocations", "@odata.id": "/redfish/v1/CertificateService/CertificateLocations", "@odata.type": "#CertificateLocations.v1_0_0.CertificateLocations", "Description": "Defines a resource that an administrator can use in order to locate all certificates installed on a given service", "Id": "CertificateLocations", "Name": "Certificate Locations" 4.POST on /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates { Returns contents of certificate "@odata.context": "/redfish/v1/$metadata#Certificate.Certificate", "@odata.id": "/redfish/v1/AccountService/LDAP/Certificates/1", "@odata.type": "#Certificate.v1A_0_0.Certificate", "Id": "1", "Issuer": { ... ... } 5.GET on /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/ { "@odata.context": "/redfish/v1/$metadata#CertificateCollection.CertificateCollection", "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates", "@odata.type": "#CertificateCollection.CertificatesCollection", "Description": "A Collection of HTTPS certificate instances", "Members": [ { "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1" } ], "Members@odata.count": 1, "Name": "HTTPS Certificate Collection" } 6.GET on /redfish/v1/CertificateService/CertificateLocations/ { "@odata.context": "/redfish/v1/$metadata#CertificateLocations.CertificateLocations", "@odata.id": "/redfish/v1/CertificateService/CertificateLocations", "@odata.type": "#CertificateLocations.v1_0_0.CertificateLocations", "Description": "Defines a resource that an administrator can use in order to locate all certificates installed on a given service", "Id": "CertificateLocations", "Links": { "Certificates": [ { "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1" } ], "Certificates@odata.count": 1 }, "Name": "Certificate Locations" } 7.GET on /redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1 { "@odata.context": "/redfish/v1/$metadata#Certificate.Certificate", "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1", "@odata.type": "#Certificate.v1_0_0.Certificate", "CertificateString": "-----BEGINCERTIFICATE-----\n....\n-----ENDCERTIFICATE-----\n", "CertificateType": "PEM", "Description": "HTTPS Certificate", "Id": "1", "Issuer": { } 8. Verified SSL context is reloaded after a certificate is installed. 9.curl -c cjar -b cjar -k -H "X-Auth-Token: $bmc_token" -X POST https://${bmc}/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/ -d @data_https.json { "@odata.context": "/redfish/v1/$metadata#Certificate.Certificate", "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1", "@odata.type": "#Certificate.v1_0_0.Certificate", "CertificateString": "-----BEGIN CERTIFICATE----END CERTIFICATE-----\n", "Description": "HTTPS certificate", "Id": "1", "Issuer": { } 4. data_https.json file contents { "CertificateString": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDClW1COSab2O0W\nW0SgTzLxQ1Igl4EpbEmTK8CAQ+wI7loTDZ7sZwYdf6yc9TAs/yNKjlJljgedGszv\nbC7sPNpH4FA63kaM6TbBBKTRshwZ3myXiBOOkOBs6w6V7+c7uEPcMFge6/4W1VXD\nReMi016cnPWZsmQyGzpmPM49YNEDZBfdKZ/pLuCYc9L9t706U7FrUSGfM7swB+mC\n8NH9qMixMuWAV9SBvzUWI6p4OCmN8a/F+4lOdbPMVEUqQ0hCBCjGM4qmiy/5Ng6y\n6rKeJlUdmOSTk8ojrNGcOXKh0nRafNEQFkIuoPHt8k5B/Yw2CX6s2BoGwvF+hS03\n+z3qVSw3AgMBAAECggEBAKpe92kybRGr3/rhMrdCYRJJpZEP1nGUdN89QbGMxxAS\n0h84n9vRYNNXRKWxMNtVEWtoLdDpiNUP8Dv59yO1LFIen2DL2e3rDJv4Gu/YCS7F\nR0NuS+FaDIaRURYLFeV+MzyJv75jVvhbFlqByJxngcGS1KAcSApvOLTnrJSlPpy9\n8ec5gnDhdOUND9PaQt8xCqMs1RPpjqvrgRzMEodZoqT5v+b0K1GmsAdbSHNP2mLM\nrqtpFDefiM1YfsTHUtxQykxG2Ipd2jzJ0a8O0qmVqdXcP9J9aqLcmD/2/r96GEV6\n/5qvIBj3SRFobxCiCwfys2XOXfjz2J+BUZzGoZvKeRECgYEA518hT6mn46LhwrTI\nW+Qpi7iTJgOfeLC+Ng855VHVQFED1P3T2lfyfGDyqKI/wV1DJIJmO8iOXerSPnhi\nb7reQkyHj6ERUtuE+6BQ9oTw2QD3EEvzOK2PEH5UipbhVTDnC3fT62Vz2yb3tR8D\n2h0XVJkj/dng9p1Td5aDGMriRRMCgYEA10vTyYqBPjDIEYw/Sc9aQk2kT6x3hrRQ\ngR4xyuI31RTCRD/KpLh/7z4s11Wkr+F9CyASeLbqu6zymlLOlS5p7IUkJ/x2X027\nJWVY1SR+oF3iF3SHiP4XkOVvWOKwIVUhgTjK1+Di6i3AlwIeAOS7VCCP6W0gbnwJ\nyyAAHZ30NM0CgYAqTur4dj2NEqvVvtkkdIRkWEwQF3mByE//8qjTljM4n5fjysaC\nlrJwrAmzbHfcFAHDG1U2eWYPJnFrmvflFnauCPCBAyL308xtdtNXQNgJ1nNXN4wy\nQQp4KaGr9gseWOLm5fKKiPK2kFmbdSBvMgKiJZ6/PKg2cG5i39L5JaBaoQKBgApw\nqOJ7Du1fHDSNonwHzA6vCSq76Efl8olwV2XJNn/ks87vcPov4DRPxYjjpErLGm8x\nrPOhmxxitJj7Lv1Y9NX9VtWBjpPshwi3M2mSjXllVBNjGTdxat8h4RZkV7omEKvd\nfyicxSQp987a0W2lqdfYhGIDYrE43pi1AoxtHmx5AoGBAJSoRy62oZbW6vjfdkuf\nvVnjNfFZwuiPV/X2NT+BhNPe5ZKFtC6gGedHLaIBBD3ItRhGuHZxgWXccPjGHofi\n6DlPdp2NePJgDT2maSjGSiAcHxyXdmW+Ev27NblvAxktoTUcVqSENrKFb+Fh4FXN\nlXiJzOEwAXiP2ZFbMRyNF/MI\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIDNzCCAh+gAwIBAgIJAI1Wr/fK5F0GMA0GCSqGSIb3DQEBCwUAMDIxHDAaBgNV\nBAoME29wZW5ibWMtcHJvamVjdC54eXoxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x\nOTAyMDExMzIyMDhaFw0yOTAxMjkxMzIyMDhaMDIxHDAaBgNVBAoME29wZW5ibWMt\ncHJvamVjdC54eXoxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAMKVbUI5JpvY7RZbRKBPMvFDUiCXgSlsSZMrwIBD7Aju\nWhMNnuxnBh1/rJz1MCz/I0qOUmWOB50azO9sLuw82kfgUDreRozpNsEEpNGyHBne\nbJeIE46Q4GzrDpXv5zu4Q9wwWB7r/hbVVcNF4yLTXpyc9ZmyZDIbOmY8zj1g0QNk\nF90pn+ku4Jhz0v23vTpTsWtRIZ8zuzAH6YLw0f2oyLEy5YBX1IG/NRYjqng4KY3x\nr8X7iU51s8xURSpDSEIEKMYziqaLL/k2DrLqsp4mVR2Y5JOTyiOs0Zw5cqHSdFp8\n0RAWQi6g8e3yTkH9jDYJfqzYGgbC8X6FLTf7PepVLDcCAwEAAaNQME4wHQYDVR0O\nBBYEFDDohRZ1+QlC3WdIkOAdBHXVyW/SMB8GA1UdIwQYMBaAFDDohRZ1+QlC3WdI\nkOAdBHXVyW/SMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFN0DWy6\nYPXHzidWMKKyQiJ5diqUv6LbujKOHUk+/LGSoCqcUp8NvmFDKWYP9MxjOAi9TVbs\nRGlIHBl38oSwKUayXBTY/vVeSLls90giUAOjswoRbBBQZvKyfEuFpc1zUsrhGLDC\n/6DuRt9l0DWcMcmP6Yh3jePIIwTr3bpxBGrwNLly8fPf16q4bWRIAcI3ZgLOhsrN\nLfD2kf56oYViM44d54Wa0qjuCfeTnJ46x/lo6w2kB9IzF7lwpipMU7+AG8ijDdaQ\nn8t0nADpv6tNNargLcOTTfJ0/P2PaKxwA1B88NhjlymBnNbz4epIn4T3KyysgS62\nzwqs66LPWoDerzc=\n-----END CERTIFICATE-----", "CertificateType": "PEM", "CertificateUri": { "@odata.id": "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/1" } } Change-Id: I2acbf8afa06bbf7d029d4971f7ab3b3988f5f060 Signed-off-by: Marri Devender Rao <devenrao@in.ibm.com> Signed-off-by: Ed Tanous <ed.tanous@intel.com>
-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