xref: /openbmc/bmcweb/redfish-core/lib/certificate_service.hpp (revision d98a2f9388b9cab1100d30de401da43c32c98ef4)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "bmcweb_config.h"
6 
7 #include "app.hpp"
8 #include "async_resp.hpp"
9 #include "dbus_singleton.hpp"
10 #include "dbus_utility.hpp"
11 #include "error_messages.hpp"
12 #include "http/parsing.hpp"
13 #include "http_request.hpp"
14 #include "http_response.hpp"
15 #include "io_context_singleton.hpp"
16 #include "logging.hpp"
17 #include "privileges.hpp"
18 #include "query.hpp"
19 #include "registries/privilege_registry.hpp"
20 #include "utility.hpp"
21 #include "utils/dbus_utils.hpp"
22 #include "utils/json_utils.hpp"
23 #include "utils/time_utils.hpp"
24 
25 #include <systemd/sd-bus.h>
26 
27 #include <boost/asio/error.hpp>
28 #include <boost/asio/steady_timer.hpp>
29 #include <boost/beast/http/field.hpp>
30 #include <boost/beast/http/status.hpp>
31 #include <boost/beast/http/verb.hpp>
32 #include <boost/system/result.hpp>
33 #include <boost/url/format.hpp>
34 #include <boost/url/parse.hpp>
35 #include <boost/url/url.hpp>
36 #include <nlohmann/json.hpp>
37 #include <sdbusplus/bus/match.hpp>
38 #include <sdbusplus/message.hpp>
39 #include <sdbusplus/message/native_types.hpp>
40 #include <sdbusplus/unpack_properties.hpp>
41 
42 #include <array>
43 #include <chrono>
44 #include <cstddef>
45 #include <cstdint>
46 #include <cstdlib>
47 #include <filesystem>
48 #include <format>
49 #include <fstream>
50 #include <functional>
51 #include <iterator>
52 #include <memory>
53 #include <optional>
54 #include <string>
55 #include <string_view>
56 #include <system_error>
57 #include <utility>
58 #include <vector>
59 
60 namespace redfish
61 {
62 namespace certs
63 {
64 constexpr const char* certInstallIntf = "xyz.openbmc_project.Certs.Install";
65 constexpr const char* certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
66 constexpr const char* objDeleteIntf = "xyz.openbmc_project.Object.Delete";
67 constexpr const char* certPropIntf = "xyz.openbmc_project.Certs.Certificate";
68 constexpr const char* dbusPropIntf = "org.freedesktop.DBus.Properties";
69 constexpr const char* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
70 constexpr const char* httpsServiceName =
71     "xyz.openbmc_project.Certs.Manager.Server.Https";
72 constexpr const char* ldapServiceName =
73     "xyz.openbmc_project.Certs.Manager.Client.Ldap";
74 constexpr const char* authorityServiceName =
75     "xyz.openbmc_project.Certs.Manager.Authority.Truststore";
76 constexpr const char* baseObjectPath = "/xyz/openbmc_project/certs";
77 constexpr const char* httpsObjectPath =
78     "/xyz/openbmc_project/certs/server/https";
79 constexpr const char* ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
80 constexpr const char* authorityObjectPath =
81     "/xyz/openbmc_project/certs/authority/truststore";
82 } // namespace certs
83 
84 /**
85  * The Certificate schema defines a Certificate Service which represents the
86  * actions available to manage certificates and links to where certificates
87  * are installed.
88  */
89 
getCertificateFromReqBody(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)90 inline std::string getCertificateFromReqBody(
91     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
92     const crow::Request& req)
93 {
94     nlohmann::json reqJson;
95     JsonParseResult ret = parseRequestAsJson(req, reqJson);
96     if (ret != JsonParseResult::Success)
97     {
98         // We did not receive JSON request, proceed as it is RAW data
99         return req.body();
100     }
101 
102     std::string certificate;
103     std::optional<std::string> certificateType = "PEM";
104 
105     if (!json_util::readJsonPatch(             //
106             req, asyncResp->res,               //
107             "CertificateString", certificate,  //
108             "CertificateType", certificateType //
109             ))
110     {
111         BMCWEB_LOG_ERROR("Required parameters are missing");
112         messages::internalError(asyncResp->res);
113         return {};
114     }
115 
116     if (*certificateType != "PEM")
117     {
118         messages::propertyValueNotInList(asyncResp->res, *certificateType,
119                                          "CertificateType");
120         return {};
121     }
122 
123     return certificate;
124 }
125 
126 /**
127  * Class to create a temporary certificate file for uploading to system
128  */
129 class CertificateFile
130 {
131   public:
132     CertificateFile() = delete;
133     CertificateFile(const CertificateFile&) = delete;
134     CertificateFile& operator=(const CertificateFile&) = delete;
135     CertificateFile(CertificateFile&&) = delete;
136     CertificateFile& operator=(CertificateFile&&) = delete;
CertificateFile(const std::string & certString)137     explicit CertificateFile(const std::string& certString)
138     {
139         std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C',
140                                             'e', 'r', 't', 's', '.', 'X',
141                                             'X', 'X', 'X', 'X', 'X', '\0'};
142         // NOLINTNEXTLINE(misc-include-cleaner)
143         char* tempDirectory = mkdtemp(dirTemplate.data());
144         if (tempDirectory != nullptr)
145         {
146             certDirectory = tempDirectory;
147             certificateFile = certDirectory / "cert.pem";
148             std::ofstream out(certificateFile,
149                               std::ofstream::out | std::ofstream::binary |
150                                   std::ofstream::trunc);
151             out << certString;
152             out.close();
153             BMCWEB_LOG_DEBUG("Creating certificate file{}",
154                              certificateFile.string());
155         }
156     }
~CertificateFile()157     ~CertificateFile()
158     {
159         if (std::filesystem::exists(certDirectory))
160         {
161             BMCWEB_LOG_DEBUG("Removing certificate file{}",
162                              certificateFile.string());
163             std::error_code ec;
164             std::filesystem::remove_all(certDirectory, ec);
165             if (ec)
166             {
167                 BMCWEB_LOG_ERROR("Failed to remove temp directory{}",
168                                  certDirectory.string());
169             }
170         }
171     }
getCertFilePath()172     std::string getCertFilePath()
173     {
174         return certificateFile;
175     }
176 
177   private:
178     std::filesystem::path certificateFile;
179     std::filesystem::path certDirectory;
180 };
181 
182 /**
183  * @brief Parse and update Certificate Issue/Subject property
184  *
185  * @param[in] asyncResp Shared pointer to the response message
186  * @param[in] str  Issuer/Subject value in key=value pairs
187  * @param[in] type Issuer/Subject
188  * @return None
189  */
updateCertIssuerOrSubject(nlohmann::json & out,std::string_view value)190 inline void updateCertIssuerOrSubject(nlohmann::json& out,
191                                       std::string_view value)
192 {
193     // example: O=openbmc-project.xyz,CN=localhost
194     std::string_view::iterator i = value.begin();
195     while (i != value.end())
196     {
197         std::string_view::iterator tokenBegin = i;
198         while (i != value.end() && *i != '=')
199         {
200             std::advance(i, 1);
201         }
202         if (i == value.end())
203         {
204             break;
205         }
206         std::string_view key(tokenBegin, static_cast<size_t>(i - tokenBegin));
207         std::advance(i, 1);
208         tokenBegin = i;
209         while (i != value.end() && *i != ',')
210         {
211             std::advance(i, 1);
212         }
213         std::string_view val(tokenBegin, static_cast<size_t>(i - tokenBegin));
214         if (key == "L")
215         {
216             out["City"] = val;
217         }
218         else if (key == "CN")
219         {
220             out["CommonName"] = val;
221         }
222         else if (key == "C")
223         {
224             out["Country"] = val;
225         }
226         else if (key == "O")
227         {
228             out["Organization"] = val;
229         }
230         else if (key == "OU")
231         {
232             out["OrganizationalUnit"] = val;
233         }
234         else if (key == "ST")
235         {
236             out["State"] = val;
237         }
238         // skip comma character
239         if (i != value.end())
240         {
241             std::advance(i, 1);
242         }
243     }
244 }
245 
246 /**
247  * @brief Retrieve the installed certificate list
248  *
249  * @param[in] asyncResp Shared pointer to the response message
250  * @param[in] basePath DBus object path to search
251  * @param[in] listPtr Json pointer to the list in asyncResp
252  * @param[in] countPtr Json pointer to the count in asyncResp
253  * @return None
254  */
getCertificateList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & basePath,const nlohmann::json::json_pointer & listPtr,const nlohmann::json::json_pointer & countPtr)255 inline void getCertificateList(
256     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
257     const std::string& basePath, const nlohmann::json::json_pointer& listPtr,
258     const nlohmann::json::json_pointer& countPtr)
259 {
260     constexpr std::array<std::string_view, 1> interfaces = {
261         certs::certPropIntf};
262     dbus::utility::getSubTreePaths(
263         basePath, 0, interfaces,
264         [asyncResp, listPtr, countPtr](
265             const boost::system::error_code& ec,
266             const dbus::utility::MapperGetSubTreePathsResponse& certPaths) {
267             if (ec)
268             {
269                 BMCWEB_LOG_ERROR("Certificate collection query failed: {}", ec);
270                 messages::internalError(asyncResp->res);
271                 return;
272             }
273 
274             nlohmann::json& links = asyncResp->res.jsonValue[listPtr];
275             links = nlohmann::json::array();
276             for (const auto& certPath : certPaths)
277             {
278                 sdbusplus::message::object_path objPath(certPath);
279                 std::string certId = objPath.filename();
280                 if (certId.empty())
281                 {
282                     BMCWEB_LOG_ERROR("Invalid certificate objPath {}",
283                                      certPath);
284                     continue;
285                 }
286 
287                 boost::urls::url certURL;
288                 if (objPath.parent_path() == certs::httpsObjectPath)
289                 {
290                     certURL = boost::urls::format(
291                         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
292                         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
293                 }
294                 else if (objPath.parent_path() == certs::ldapObjectPath)
295                 {
296                     certURL = boost::urls::format(
297                         "/redfish/v1/AccountService/LDAP/Certificates/{}",
298                         certId);
299                 }
300                 else if (objPath.parent_path() == certs::authorityObjectPath)
301                 {
302                     certURL = boost::urls::format(
303                         "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
304                         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
305                 }
306                 else
307                 {
308                     continue;
309                 }
310 
311                 nlohmann::json::object_t link;
312                 link["@odata.id"] = certURL;
313                 links.emplace_back(std::move(link));
314             }
315 
316             asyncResp->res.jsonValue[countPtr] = links.size();
317         });
318 }
319 
320 /**
321  * @brief Retrieve the certificates properties and append to the response
322  * message
323  *
324  * @param[in] asyncResp Shared pointer to the response message
325  * @param[in] objectPath  Path of the D-Bus service object
326  * @param[in] certId  Id of the certificate
327  * @param[in] certURL  URL of the certificate object
328  * @param[in] name  name of the certificate
329  * @return None
330  */
getCertificateProperties(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & objectPath,const std::string & service,const std::string & certId,const boost::urls::url & certURL,const std::string & name)331 inline void getCertificateProperties(
332     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
333     const std::string& objectPath, const std::string& service,
334     const std::string& certId, const boost::urls::url& certURL,
335     const std::string& name)
336 {
337     BMCWEB_LOG_DEBUG("getCertificateProperties Path={} certId={} certURl={}",
338                      objectPath, certId, certURL);
339     dbus::utility::getAllProperties(
340         service, objectPath, certs::certPropIntf,
341         [asyncResp, certURL, certId,
342          name](const boost::system::error_code& ec,
343                const dbus::utility::DBusPropertiesMap& properties) {
344             if (ec)
345             {
346                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
347                 messages::resourceNotFound(asyncResp->res, "Certificate",
348                                            certId);
349                 return;
350             }
351 
352             const std::string* certificateString = nullptr;
353             const std::vector<std::string>* keyUsage = nullptr;
354             const std::string* issuer = nullptr;
355             const std::string* subject = nullptr;
356             const uint64_t* validNotAfter = nullptr;
357             const uint64_t* validNotBefore = nullptr;
358 
359             const bool success = sdbusplus::unpackPropertiesNoThrow(
360                 dbus_utils::UnpackErrorPrinter(), properties,
361                 "CertificateString", certificateString, "KeyUsage", keyUsage,
362                 "Issuer", issuer, "Subject", subject, "ValidNotAfter",
363                 validNotAfter, "ValidNotBefore", validNotBefore);
364 
365             if (!success)
366             {
367                 messages::internalError(asyncResp->res);
368                 return;
369             }
370 
371             asyncResp->res.jsonValue["@odata.id"] = certURL;
372             asyncResp->res.jsonValue["@odata.type"] =
373                 "#Certificate.v1_0_0.Certificate";
374             asyncResp->res.jsonValue["Id"] = certId;
375             asyncResp->res.jsonValue["Name"] = name;
376             asyncResp->res.jsonValue["Description"] = name;
377             asyncResp->res.jsonValue["CertificateString"] = "";
378             asyncResp->res.jsonValue["KeyUsage"] = nlohmann::json::array();
379 
380             if (certificateString != nullptr)
381             {
382                 asyncResp->res.jsonValue["CertificateString"] =
383                     *certificateString;
384             }
385 
386             if (keyUsage != nullptr)
387             {
388                 asyncResp->res.jsonValue["KeyUsage"] = *keyUsage;
389             }
390 
391             if (issuer != nullptr)
392             {
393                 updateCertIssuerOrSubject(asyncResp->res.jsonValue["Issuer"],
394                                           *issuer);
395             }
396 
397             if (subject != nullptr)
398             {
399                 updateCertIssuerOrSubject(asyncResp->res.jsonValue["Subject"],
400                                           *subject);
401             }
402 
403             if (validNotAfter != nullptr)
404             {
405                 asyncResp->res.jsonValue["ValidNotAfter"] =
406                     redfish::time_utils::getDateTimeUint(*validNotAfter);
407             }
408 
409             if (validNotBefore != nullptr)
410             {
411                 asyncResp->res.jsonValue["ValidNotBefore"] =
412                     redfish::time_utils::getDateTimeUint(*validNotBefore);
413             }
414 
415             asyncResp->res.addHeader(
416                 boost::beast::http::field::location,
417                 std::string_view(certURL.data(), certURL.size()));
418         });
419 }
420 
deleteCertificate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const sdbusplus::message::object_path & objectPath)421 inline void deleteCertificate(
422     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
423     const std::string& service,
424     const sdbusplus::message::object_path& objectPath)
425 {
426     crow::connections::systemBus->async_method_call(
427         [asyncResp,
428          id{objectPath.filename()}](const boost::system::error_code& ec) {
429             if (ec)
430             {
431                 messages::resourceNotFound(asyncResp->res, "Certificate", id);
432                 return;
433             }
434             BMCWEB_LOG_INFO("Certificate deleted");
435             asyncResp->res.result(boost::beast::http::status::no_content);
436         },
437         service, objectPath, certs::objDeleteIntf, "Delete");
438 }
439 
handleCertificateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)440 inline void handleCertificateServiceGet(
441     App& app, const crow::Request& req,
442     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
443 {
444     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
445     {
446         return;
447     }
448 
449     if (req.session == nullptr)
450     {
451         messages::internalError(asyncResp->res);
452         return;
453     }
454 
455     asyncResp->res.jsonValue["@odata.type"] =
456         "#CertificateService.v1_0_0.CertificateService";
457     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/CertificateService";
458     asyncResp->res.jsonValue["Id"] = "CertificateService";
459     asyncResp->res.jsonValue["Name"] = "Certificate Service";
460     asyncResp->res.jsonValue["Description"] =
461         "Actions available to manage certificates";
462     // /redfish/v1/CertificateService/CertificateLocations is something
463     // only ConfigureManager can access then only display when the user
464     // has permissions ConfigureManager
465     Privileges effectiveUserPrivileges =
466         redfish::getUserPrivileges(*req.session);
467     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
468                                          effectiveUserPrivileges))
469     {
470         asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] =
471             "/redfish/v1/CertificateService/CertificateLocations";
472     }
473     nlohmann::json& actions = asyncResp->res.jsonValue["Actions"];
474     nlohmann::json& replace = actions["#CertificateService.ReplaceCertificate"];
475     replace["target"] =
476         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate";
477     nlohmann::json::array_t allowed;
478     allowed.emplace_back("PEM");
479     replace["CertificateType@Redfish.AllowableValues"] = std::move(allowed);
480     actions["#CertificateService.GenerateCSR"]["target"] =
481         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR";
482 }
483 
handleCertificateLocationsGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)484 inline void handleCertificateLocationsGet(
485     App& app, const crow::Request& req,
486     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
487 {
488     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
489     {
490         return;
491     }
492     asyncResp->res.jsonValue["@odata.id"] =
493         "/redfish/v1/CertificateService/CertificateLocations";
494     asyncResp->res.jsonValue["@odata.type"] =
495         "#CertificateLocations.v1_0_0.CertificateLocations";
496     asyncResp->res.jsonValue["Name"] = "Certificate Locations";
497     asyncResp->res.jsonValue["Id"] = "CertificateLocations";
498     asyncResp->res.jsonValue["Description"] =
499         "Defines a resource that an administrator can use in order to "
500         "locate all certificates installed on a given service";
501 
502     getCertificateList(asyncResp, certs::baseObjectPath,
503                        "/Links/Certificates"_json_pointer,
504                        "/Links/Certificates@odata.count"_json_pointer);
505 }
506 
handleError(const std::string_view dbusErrorName,const std::string & id,const std::string & certificate,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)507 inline void handleError(const std::string_view dbusErrorName,
508                         const std::string& id, const std::string& certificate,
509                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
510 {
511     if (dbusErrorName == "org.freedesktop.DBus.Error.UnknownObject")
512     {
513         messages::resourceNotFound(asyncResp->res, "Certificate", id);
514     }
515     else if (dbusErrorName ==
516              "xyz.openbmc_project.Certs.Error.InvalidCertificate")
517     {
518         messages::propertyValueIncorrect(asyncResp->res, "Certificate",
519                                          certificate);
520     }
521     else
522     {
523         messages::internalError(asyncResp->res);
524     }
525 }
526 
handleReplaceCertificateAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)527 inline void handleReplaceCertificateAction(
528     App& app, const crow::Request& req,
529     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
530 {
531     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
532     {
533         return;
534     }
535     std::string certificate;
536     std::string certURI;
537     std::optional<std::string> certificateType = "PEM";
538 
539     if (!json_util::readJsonAction(             //
540             req, asyncResp->res,                //
541             "CertificateString", certificate,   //
542             "CertificateType", certificateType, //
543             "CertificateUri/@odata.id", certURI //
544             ))
545     {
546         BMCWEB_LOG_ERROR("Required parameters are missing");
547         return;
548     }
549 
550     if (!certificateType)
551     {
552         // should never happen, but it never hurts to be paranoid.
553         return;
554     }
555     if (certificateType != "PEM")
556     {
557         messages::actionParameterNotSupported(asyncResp->res, "CertificateType",
558                                               "ReplaceCertificate");
559         return;
560     }
561 
562     BMCWEB_LOG_INFO("Certificate URI to replace: {}", certURI);
563 
564     boost::system::result<boost::urls::url> parsedUrl =
565         boost::urls::parse_relative_ref(certURI);
566     if (!parsedUrl)
567     {
568         messages::actionParameterValueFormatError(
569             asyncResp->res, certURI, "CertificateUri", "ReplaceCertificate");
570         return;
571     }
572 
573     std::string id;
574     sdbusplus::message::object_path objectPath;
575     std::string name;
576     std::string service;
577     if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", "Managers",
578                                        "bmc", "NetworkProtocol", "HTTPS",
579                                        "Certificates", std::ref(id)))
580     {
581         objectPath = sdbusplus::message::object_path(certs::httpsObjectPath) /
582                      id;
583         name = "HTTPS certificate";
584         service = certs::httpsServiceName;
585     }
586     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
587                                             "AccountService", "LDAP",
588                                             "Certificates", std::ref(id)))
589     {
590         objectPath = sdbusplus::message::object_path(certs::ldapObjectPath) /
591                      id;
592         name = "LDAP certificate";
593         service = certs::ldapServiceName;
594     }
595     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
596                                             "Managers", "bmc", "Truststore",
597                                             "Certificates", std::ref(id)))
598     {
599         objectPath =
600             sdbusplus::message::object_path(certs::authorityObjectPath) / id;
601         name = "TrustStore certificate";
602         service = certs::authorityServiceName;
603     }
604     else
605     {
606         messages::actionParameterNotSupported(asyncResp->res, "CertificateUri",
607                                               "ReplaceCertificate");
608         return;
609     }
610 
611     std::shared_ptr<CertificateFile> certFile =
612         std::make_shared<CertificateFile>(certificate);
613     crow::connections::systemBus->async_method_call(
614         [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, name,
615          certificate](const boost::system::error_code& ec,
616                       sdbusplus::message_t& m) {
617             if (ec)
618             {
619                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
620                 const sd_bus_error* dbusError = m.get_error();
621                 if ((dbusError != nullptr) && (dbusError->name != nullptr))
622                 {
623                     handleError(dbusError->name, id, certificate, asyncResp);
624                 }
625                 else
626                 {
627                     messages::internalError(asyncResp->res);
628                 }
629                 return;
630             }
631             getCertificateProperties(asyncResp, objectPath, service, id, url,
632                                      name);
633             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
634                              certFile->getCertFilePath());
635         },
636         service, objectPath, certs::certReplaceIntf, "Replace",
637         certFile->getCertFilePath());
638 }
639 
640 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
641 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher;
642 /**
643  * @brief Read data from CSR D-bus object and set to response
644  *
645  * @param[in] asyncResp Shared pointer to the response message
646  * @param[in] certURI Link to certificate collection URI
647  * @param[in] service D-Bus service name
648  * @param[in] certObjPath certificate D-Bus object path
649  * @param[in] csrObjPath CSR D-Bus object path
650  * @return None
651  */
getCSR(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & certURI,const std::string & service,const std::string & certObjPath,const std::string & csrObjPath)652 inline void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
653                    const std::string& certURI, const std::string& service,
654                    const std::string& certObjPath,
655                    const std::string& csrObjPath)
656 {
657     BMCWEB_LOG_DEBUG("getCSR CertObjectPath{} CSRObjectPath={} service={}",
658                      certObjPath, csrObjPath, service);
659     crow::connections::systemBus->async_method_call(
660         [asyncResp,
661          certURI](const boost::system::error_code& ec, const std::string& csr) {
662             if (ec)
663             {
664                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
665                 messages::internalError(asyncResp->res);
666                 return;
667             }
668             if (csr.empty())
669             {
670                 BMCWEB_LOG_ERROR("CSR read is empty");
671                 messages::internalError(asyncResp->res);
672                 return;
673             }
674             asyncResp->res.jsonValue["CSRString"] = csr;
675             asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
676                 certURI;
677         },
678         service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
679 }
680 
handleGenerateCSRAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)681 inline void handleGenerateCSRAction(
682     App& app, const crow::Request& req,
683     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
684 {
685     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
686     {
687         return;
688     }
689     static const int rsaKeyBitLength = 2048;
690 
691     // Required parameters
692     std::string city;
693     std::string commonName;
694     std::string country;
695     std::string organization;
696     std::string organizationalUnit;
697     std::string state;
698     std::string certURI;
699 
700     // Optional parameters
701     std::optional<std::vector<std::string>> optAlternativeNames =
702         std::vector<std::string>();
703     std::optional<std::string> optContactPerson = "";
704     std::optional<std::string> optChallengePassword = "";
705     std::optional<std::string> optEmail = "";
706     std::optional<std::string> optGivenName = "";
707     std::optional<std::string> optInitials = "";
708     std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
709     std::optional<std::string> optKeyCurveId = "secp384r1";
710     std::optional<std::string> optKeyPairAlgorithm = "EC";
711     std::optional<std::vector<std::string>> optKeyUsage =
712         std::vector<std::string>();
713     std::optional<std::string> optSurname = "";
714     std::optional<std::string> optUnstructuredName = "";
715     if (!json_util::readJsonAction(                     //
716             req, asyncResp->res,                        //
717             "AlternativeNames", optAlternativeNames,    //
718             "CertificateCollection/@odata.id", certURI, //
719             "ChallengePassword", optChallengePassword,  //
720             "City", city,                               //
721             "CommonName", commonName,                   //
722             "ContactPerson", optContactPerson,          //
723             "Country", country,                         //
724             "Email", optEmail,                          //
725             "GivenName", optGivenName,                  //
726             "Initials", optInitials,                    //
727             "KeyBitLength", optKeyBitLength,            //
728             "KeyCurveId", optKeyCurveId,                //
729             "KeyPairAlgorithm", optKeyPairAlgorithm,    //
730             "KeyUsage", optKeyUsage,                    //
731             "Organization", organization,               //
732             "OrganizationalUnit", organizationalUnit,   //
733             "State", state,                             //
734             "Surname", optSurname,                      //
735             "UnstructuredName", optUnstructuredName     //
736             ))
737     {
738         return;
739     }
740 
741     // bmcweb has no way to store or decode a private key challenge
742     // password, which will likely cause bmcweb to crash on startup
743     // if this is not set on a post so not allowing the user to set
744     // value
745     if (!optChallengePassword->empty())
746     {
747         messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR",
748                                               "ChallengePassword");
749         return;
750     }
751 
752     std::string objectPath;
753     std::string service;
754     if (certURI.starts_with(std::format(
755             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
756             BMCWEB_REDFISH_MANAGER_URI_NAME)))
757     {
758         objectPath = certs::httpsObjectPath;
759         service = certs::httpsServiceName;
760     }
761     else if (certURI.starts_with(
762                  "/redfish/v1/AccountService/LDAP/Certificates"))
763     {
764         objectPath = certs::ldapObjectPath;
765         service = certs::ldapServiceName;
766     }
767     else
768     {
769         messages::actionParameterNotSupported(
770             asyncResp->res, "CertificateCollection", "GenerateCSR");
771         return;
772     }
773 
774     // supporting only EC and RSA algorithm
775     if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
776     {
777         messages::actionParameterNotSupported(
778             asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
779         return;
780     }
781 
782     // supporting only 2048 key bit length for RSA algorithm due to
783     // time consumed in generating private key
784     if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength)
785     {
786         messages::propertyValueNotInList(asyncResp->res, *optKeyBitLength,
787                                          "KeyBitLength");
788         return;
789     }
790 
791     // validate KeyUsage supporting only 1 type based on URL
792     if (certURI.starts_with(std::format(
793             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
794             BMCWEB_REDFISH_MANAGER_URI_NAME)))
795     {
796         if (optKeyUsage->empty())
797         {
798             optKeyUsage->emplace_back("ServerAuthentication");
799         }
800         else if (optKeyUsage->size() == 1)
801         {
802             if ((*optKeyUsage)[0] != "ServerAuthentication")
803             {
804                 messages::propertyValueNotInList(asyncResp->res,
805                                                  (*optKeyUsage)[0], "KeyUsage");
806                 return;
807             }
808         }
809         else
810         {
811             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
812                                                   "GenerateCSR");
813             return;
814         }
815     }
816     else if (certURI.starts_with(
817                  "/redfish/v1/AccountService/LDAP/Certificates"))
818     {
819         if (optKeyUsage->empty())
820         {
821             optKeyUsage->emplace_back("ClientAuthentication");
822         }
823         else if (optKeyUsage->size() == 1)
824         {
825             if ((*optKeyUsage)[0] != "ClientAuthentication")
826             {
827                 messages::propertyValueNotInList(asyncResp->res,
828                                                  (*optKeyUsage)[0], "KeyUsage");
829                 return;
830             }
831         }
832         else
833         {
834             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
835                                                   "GenerateCSR");
836             return;
837         }
838     }
839 
840     // Only allow one CSR matcher at a time so setting retry
841     // time-out and timer expiry to 10 seconds for now.
842     static const int timeOut = 10;
843     if (csrMatcher)
844     {
845         messages::serviceTemporarilyUnavailable(asyncResp->res,
846                                                 std::to_string(timeOut));
847         return;
848     }
849 
850     // Make this static so it survives outside this method
851     static boost::asio::steady_timer timeout(getIoContext());
852     timeout.expires_after(std::chrono::seconds(timeOut));
853     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
854         csrMatcher = nullptr;
855         if (ec)
856         {
857             // operation_aborted is expected if timer is canceled
858             // before completion.
859             if (ec != boost::asio::error::operation_aborted)
860             {
861                 BMCWEB_LOG_ERROR("Async_wait failed {}", ec);
862             }
863             return;
864         }
865         BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR");
866         messages::internalError(asyncResp->res);
867     });
868 
869     // create a matcher to wait on CSR object
870     BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath);
871     std::string match("type='signal',"
872                       "interface='org.freedesktop.DBus.ObjectManager',"
873                       "path='" +
874                       objectPath +
875                       "',"
876                       "member='InterfacesAdded'");
877     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
878         *crow::connections::systemBus, match,
879         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
880             timeout.cancel();
881             if (m.is_method_error())
882             {
883                 BMCWEB_LOG_ERROR("Dbus method error!!!");
884                 messages::internalError(asyncResp->res);
885                 return;
886             }
887 
888             dbus::utility::DBusInterfacesMap interfacesProperties;
889 
890             sdbusplus::message::object_path csrObjectPath;
891             m.read(csrObjectPath, interfacesProperties);
892             BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str);
893             for (const auto& interface : interfacesProperties)
894             {
895                 if (interface.first == "xyz.openbmc_project.Certs.CSR")
896                 {
897                     getCSR(asyncResp, certURI, service, objectPath,
898                            csrObjectPath.str);
899                     break;
900                 }
901             }
902         });
903     crow::connections::systemBus->async_method_call(
904         [asyncResp](const boost::system::error_code& ec, const std::string&) {
905             if (ec)
906             {
907                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message());
908                 messages::internalError(asyncResp->res);
909                 return;
910             }
911         },
912         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
913         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
914         commonName, *optContactPerson, country, *optEmail, *optGivenName,
915         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
916         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
917         *optUnstructuredName);
918 }
919 
requestRoutesCertificateService(App & app)920 inline void requestRoutesCertificateService(App& app)
921 {
922     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
923         .privileges(redfish::privileges::getCertificateService)
924         .methods(boost::beast::http::verb::get)(
925             std::bind_front(handleCertificateServiceGet, std::ref(app)));
926 
927     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
928         .privileges(redfish::privileges::getCertificateLocations)
929         .methods(boost::beast::http::verb::get)(
930             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
931 
932     BMCWEB_ROUTE(
933         app,
934         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
935         .privileges(redfish::privileges::postCertificateService)
936         .methods(boost::beast::http::verb::post)(
937             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
938 
939     BMCWEB_ROUTE(
940         app,
941         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
942         .privileges(redfish::privileges::postCertificateService)
943         .methods(boost::beast::http::verb::post)(
944             std::bind_front(handleGenerateCSRAction, std::ref(app)));
945 } // requestRoutesCertificateService
946 
handleHTTPSCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)947 inline void handleHTTPSCertificateCollectionGet(
948     App& app, const crow::Request& req,
949     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
950     const std::string& managerId)
951 {
952     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
953     {
954         return;
955     }
956 
957     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
958     {
959         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
960         return;
961     }
962 
963     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
964         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
965         BMCWEB_REDFISH_MANAGER_URI_NAME);
966     asyncResp->res.jsonValue["@odata.type"] =
967         "#CertificateCollection.CertificateCollection";
968     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
969     asyncResp->res.jsonValue["Description"] =
970         "A Collection of HTTPS certificate instances";
971 
972     getCertificateList(asyncResp, certs::httpsObjectPath,
973                        "/Members"_json_pointer,
974                        "/Members@odata.count"_json_pointer);
975 }
976 
handleHTTPSCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)977 inline void handleHTTPSCertificateCollectionPost(
978     App& app, const crow::Request& req,
979     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
980     const std::string& managerId)
981 {
982     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
983     {
984         return;
985     }
986 
987     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
988     {
989         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
990         return;
991     }
992 
993     BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost");
994 
995     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
996     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
997 
998     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
999 
1000     if (certHttpBody.empty())
1001     {
1002         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1003         messages::unrecognizedRequestBody(asyncResp->res);
1004         return;
1005     }
1006 
1007     std::shared_ptr<CertificateFile> certFile =
1008         std::make_shared<CertificateFile>(certHttpBody);
1009 
1010     crow::connections::systemBus->async_method_call(
1011         [asyncResp, certFile](const boost::system::error_code& ec,
1012                               const std::string& objectPath) {
1013             if (ec)
1014             {
1015                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1016                 messages::internalError(asyncResp->res);
1017                 return;
1018             }
1019 
1020             sdbusplus::message::object_path path(objectPath);
1021             std::string certId = path.filename();
1022             const boost::urls::url certURL = boost::urls::format(
1023                 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1024                 BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1025             getCertificateProperties(asyncResp, objectPath,
1026                                      certs::httpsServiceName, certId, certURL,
1027                                      "HTTPS Certificate");
1028             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
1029                              certFile->getCertFilePath());
1030         },
1031         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
1032         "Install", certFile->getCertFilePath());
1033 }
1034 
handleHTTPSCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1035 inline void handleHTTPSCertificateGet(
1036     App& app, const crow::Request& req,
1037     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1038     const std::string& managerId, const std::string& certId)
1039 {
1040     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1041     {
1042         return;
1043     }
1044 
1045     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1046     {
1047         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1048         return;
1049     }
1050 
1051     BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", certId);
1052     const boost::urls::url certURL = boost::urls::format(
1053         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1054         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1055     std::string objPath =
1056         sdbusplus::message::object_path(certs::httpsObjectPath) / certId;
1057     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName,
1058                              certId, certURL, "HTTPS Certificate");
1059 }
1060 
requestRoutesHTTPSCertificate(App & app)1061 inline void requestRoutesHTTPSCertificate(App& app)
1062 {
1063     BMCWEB_ROUTE(
1064         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1065         .privileges(redfish::privileges::getCertificateCollection)
1066         .methods(boost::beast::http::verb::get)(std::bind_front(
1067             handleHTTPSCertificateCollectionGet, std::ref(app)));
1068 
1069     BMCWEB_ROUTE(
1070         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1071         .privileges(redfish::privileges::postCertificateCollection)
1072         .methods(boost::beast::http::verb::post)(std::bind_front(
1073             handleHTTPSCertificateCollectionPost, std::ref(app)));
1074 
1075     BMCWEB_ROUTE(
1076         app,
1077         "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/<str>/")
1078         .privileges(redfish::privileges::getCertificate)
1079         .methods(boost::beast::http::verb::get)(
1080             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
1081 }
1082 
handleLDAPCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1083 inline void handleLDAPCertificateCollectionGet(
1084     App& app, const crow::Request& req,
1085     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1086 {
1087     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1088     {
1089         return;
1090     }
1091 
1092     asyncResp->res.jsonValue["@odata.id"] =
1093         "/redfish/v1/AccountService/LDAP/Certificates";
1094     asyncResp->res.jsonValue["@odata.type"] =
1095         "#CertificateCollection.CertificateCollection";
1096     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1097     asyncResp->res.jsonValue["Description"] =
1098         "A Collection of LDAP certificate instances";
1099 
1100     getCertificateList(asyncResp, certs::ldapObjectPath,
1101                        "/Members"_json_pointer,
1102                        "/Members@odata.count"_json_pointer);
1103 }
1104 
handleLDAPCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1105 inline void handleLDAPCertificateCollectionPost(
1106     App& app, const crow::Request& req,
1107     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1108 {
1109     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1110     {
1111         return;
1112     }
1113     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1114 
1115     if (certHttpBody.empty())
1116     {
1117         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1118         messages::unrecognizedRequestBody(asyncResp->res);
1119         return;
1120     }
1121 
1122     std::shared_ptr<CertificateFile> certFile =
1123         std::make_shared<CertificateFile>(certHttpBody);
1124 
1125     crow::connections::systemBus->async_method_call(
1126         [asyncResp, certFile](const boost::system::error_code& ec,
1127                               const std::string& objectPath) {
1128             if (ec)
1129             {
1130                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1131                 messages::internalError(asyncResp->res);
1132                 return;
1133             }
1134 
1135             sdbusplus::message::object_path path(objectPath);
1136             std::string certId = path.filename();
1137             const boost::urls::url certURL = boost::urls::format(
1138                 "/redfish/v1/AccountService/LDAP/Certificates/{}", certId);
1139             getCertificateProperties(asyncResp, objectPath,
1140                                      certs::ldapServiceName, certId, certURL,
1141                                      "LDAP Certificate");
1142             BMCWEB_LOG_DEBUG("LDAP certificate install file={}",
1143                              certFile->getCertFilePath());
1144         },
1145         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1146         "Install", certFile->getCertFilePath());
1147 }
1148 
handleLDAPCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1149 inline void handleLDAPCertificateGet(
1150     App& app, const crow::Request& req,
1151     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1152 {
1153     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1154     {
1155         return;
1156     }
1157 
1158     BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id);
1159     const boost::urls::url certURL = boost::urls::format(
1160         "/redfish/v1/AccountService/LDAP/Certificates/{}", id);
1161     std::string objPath =
1162         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1163     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1164                              certURL, "LDAP Certificate");
1165 }
1166 
handleLDAPCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1167 inline void handleLDAPCertificateDelete(
1168     App& app, const crow::Request& req,
1169     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1170 {
1171     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1172     {
1173         return;
1174     }
1175 
1176     BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id);
1177     std::string objPath =
1178         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1179 
1180     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1181 }
1182 
requestRoutesLDAPCertificate(App & app)1183 inline void requestRoutesLDAPCertificate(App& app)
1184 {
1185     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1186         .privileges(redfish::privileges::getCertificateCollection)
1187         .methods(boost::beast::http::verb::get)(
1188             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1189 
1190     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1191         .privileges(redfish::privileges::postCertificateCollection)
1192         .methods(boost::beast::http::verb::post)(std::bind_front(
1193             handleLDAPCertificateCollectionPost, std::ref(app)));
1194 
1195     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1196         .privileges(redfish::privileges::getCertificate)
1197         .methods(boost::beast::http::verb::get)(
1198             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1199 
1200     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1201         .privileges(redfish::privileges::deleteCertificate)
1202         .methods(boost::beast::http::verb::delete_)(
1203             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1204 } // requestRoutesLDAPCertificate
1205 
handleTrustStoreCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1206 inline void handleTrustStoreCertificateCollectionGet(
1207     App& app, const crow::Request& req,
1208     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1209     const std::string& managerId)
1210 {
1211     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1212     {
1213         return;
1214     }
1215 
1216     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1217     {
1218         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1219         return;
1220     }
1221 
1222     asyncResp->res.jsonValue["@odata.id"] =
1223         boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates/",
1224                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1225     asyncResp->res.jsonValue["@odata.type"] =
1226         "#CertificateCollection.CertificateCollection";
1227     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1228     asyncResp->res.jsonValue["Description"] =
1229         "A Collection of TrustStore certificate instances";
1230 
1231     getCertificateList(asyncResp, certs::authorityObjectPath,
1232                        "/Members"_json_pointer,
1233                        "/Members@odata.count"_json_pointer);
1234 }
1235 
handleTrustStoreCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1236 inline void handleTrustStoreCertificateCollectionPost(
1237     App& app, const crow::Request& req,
1238     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1239     const std::string& managerId)
1240 {
1241     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1242     {
1243         return;
1244     }
1245 
1246     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1247     {
1248         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1249         return;
1250     }
1251 
1252     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1253 
1254     if (certHttpBody.empty())
1255     {
1256         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1257         messages::unrecognizedRequestBody(asyncResp->res);
1258         return;
1259     }
1260 
1261     std::shared_ptr<CertificateFile> certFile =
1262         std::make_shared<CertificateFile>(certHttpBody);
1263     crow::connections::systemBus->async_method_call(
1264         [asyncResp, certFile](const boost::system::error_code& ec,
1265                               const std::string& objectPath) {
1266             if (ec)
1267             {
1268                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1269                 messages::internalError(asyncResp->res);
1270                 return;
1271             }
1272 
1273             sdbusplus::message::object_path path(objectPath);
1274             std::string certId = path.filename();
1275             const boost::urls::url certURL = boost::urls::format(
1276                 "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
1277                 BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1278             getCertificateProperties(asyncResp, objectPath,
1279                                      certs::authorityServiceName, certId,
1280                                      certURL, "TrustStore Certificate");
1281             BMCWEB_LOG_DEBUG("TrustStore certificate install file={}",
1282                              certFile->getCertFilePath());
1283         },
1284         certs::authorityServiceName, certs::authorityObjectPath,
1285         certs::certInstallIntf, "Install", certFile->getCertFilePath());
1286 }
1287 
handleTrustStoreCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1288 inline void handleTrustStoreCertificateGet(
1289     App& app, const crow::Request& req,
1290     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1291     const std::string& managerId, const std::string& certId)
1292 {
1293     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1294     {
1295         return;
1296     }
1297 
1298     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1299     {
1300         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1301         return;
1302     }
1303 
1304     BMCWEB_LOG_DEBUG("Truststore Certificate ID={}", certId);
1305     const boost::urls::url certURL = boost::urls::format(
1306         "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
1307         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1308     std::string objPath =
1309         sdbusplus::message::object_path(certs::authorityObjectPath) / certId;
1310     getCertificateProperties(asyncResp, objPath, certs::authorityServiceName,
1311                              certId, certURL, "TrustStore Certificate");
1312 }
1313 
handleTrustStoreCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1314 inline void handleTrustStoreCertificateDelete(
1315     App& app, const crow::Request& req,
1316     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1317     const std::string& managerId, const std::string& certId)
1318 {
1319     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1320     {
1321         return;
1322     }
1323 
1324     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1325     {
1326         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1327         return;
1328     }
1329 
1330     BMCWEB_LOG_DEBUG("Delete TrustStore Certificate ID={}", certId);
1331     std::string objPath =
1332         sdbusplus::message::object_path(certs::authorityObjectPath) / certId;
1333 
1334     deleteCertificate(asyncResp, certs::authorityServiceName, objPath);
1335 }
1336 
requestRoutesTrustStoreCertificate(App & app)1337 inline void requestRoutesTrustStoreCertificate(App& app)
1338 {
1339     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/")
1340         .privileges(redfish::privileges::getCertificate)
1341         .methods(boost::beast::http::verb::get)(std::bind_front(
1342             handleTrustStoreCertificateCollectionGet, std::ref(app)));
1343 
1344     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/")
1345         .privileges(redfish::privileges::postCertificateCollection)
1346         .methods(boost::beast::http::verb::post)(std::bind_front(
1347             handleTrustStoreCertificateCollectionPost, std::ref(app)));
1348 
1349     BMCWEB_ROUTE(app,
1350                  "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/")
1351         .privileges(redfish::privileges::getCertificate)
1352         .methods(boost::beast::http::verb::get)(
1353             std::bind_front(handleTrustStoreCertificateGet, std::ref(app)));
1354 
1355     BMCWEB_ROUTE(app,
1356                  "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/")
1357         .privileges(redfish::privileges::deleteCertificate)
1358         .methods(boost::beast::http::verb::delete_)(
1359             std::bind_front(handleTrustStoreCertificateDelete, std::ref(app)));
1360 } // requestRoutesTrustStoreCertificate
1361 } // namespace redfish
1362