xref: /openbmc/bmcweb/redfish-core/lib/certificate_service.hpp (revision 177612aaa0633cf9d5aef0b763a43135cf552d9b)
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     dbus::utility::async_method_call(
427         asyncResp,
428         [asyncResp,
429          id{objectPath.filename()}](const boost::system::error_code& ec) {
430             if (ec)
431             {
432                 messages::resourceNotFound(asyncResp->res, "Certificate", id);
433                 return;
434             }
435             BMCWEB_LOG_INFO("Certificate deleted");
436             asyncResp->res.result(boost::beast::http::status::no_content);
437         },
438         service, objectPath, certs::objDeleteIntf, "Delete");
439 }
440 
handleCertificateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)441 inline void handleCertificateServiceGet(
442     App& app, const crow::Request& req,
443     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
444 {
445     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
446     {
447         return;
448     }
449 
450     if (req.session == nullptr)
451     {
452         messages::internalError(asyncResp->res);
453         return;
454     }
455 
456     asyncResp->res.jsonValue["@odata.type"] =
457         "#CertificateService.v1_0_0.CertificateService";
458     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/CertificateService";
459     asyncResp->res.jsonValue["Id"] = "CertificateService";
460     asyncResp->res.jsonValue["Name"] = "Certificate Service";
461     asyncResp->res.jsonValue["Description"] =
462         "Actions available to manage certificates";
463     // /redfish/v1/CertificateService/CertificateLocations is something
464     // only ConfigureManager can access then only display when the user
465     // has permissions ConfigureManager
466     Privileges effectiveUserPrivileges =
467         redfish::getUserPrivileges(*req.session);
468     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
469                                          effectiveUserPrivileges))
470     {
471         asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] =
472             "/redfish/v1/CertificateService/CertificateLocations";
473     }
474     nlohmann::json& actions = asyncResp->res.jsonValue["Actions"];
475     nlohmann::json& replace = actions["#CertificateService.ReplaceCertificate"];
476     replace["target"] =
477         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate";
478     nlohmann::json::array_t allowed;
479     allowed.emplace_back("PEM");
480     replace["CertificateType@Redfish.AllowableValues"] = std::move(allowed);
481     actions["#CertificateService.GenerateCSR"]["target"] =
482         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR";
483 }
484 
handleCertificateLocationsGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)485 inline void handleCertificateLocationsGet(
486     App& app, const crow::Request& req,
487     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
488 {
489     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
490     {
491         return;
492     }
493     asyncResp->res.jsonValue["@odata.id"] =
494         "/redfish/v1/CertificateService/CertificateLocations";
495     asyncResp->res.jsonValue["@odata.type"] =
496         "#CertificateLocations.v1_0_0.CertificateLocations";
497     asyncResp->res.jsonValue["Name"] = "Certificate Locations";
498     asyncResp->res.jsonValue["Id"] = "CertificateLocations";
499     asyncResp->res.jsonValue["Description"] =
500         "Defines a resource that an administrator can use in order to "
501         "locate all certificates installed on a given service";
502 
503     getCertificateList(asyncResp, certs::baseObjectPath,
504                        "/Links/Certificates"_json_pointer,
505                        "/Links/Certificates@odata.count"_json_pointer);
506 }
507 
handleError(const std::string_view dbusErrorName,const std::string & id,const std::string & certificate,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)508 inline void handleError(const std::string_view dbusErrorName,
509                         const std::string& id, const std::string& certificate,
510                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
511 {
512     if (dbusErrorName == "org.freedesktop.DBus.Error.UnknownObject")
513     {
514         messages::resourceNotFound(asyncResp->res, "Certificate", id);
515     }
516     else if (dbusErrorName ==
517              "xyz.openbmc_project.Certs.Error.InvalidCertificate")
518     {
519         messages::propertyValueIncorrect(asyncResp->res, "Certificate",
520                                          certificate);
521     }
522     else
523     {
524         messages::internalError(asyncResp->res);
525     }
526 }
527 
handleReplaceCertificateAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)528 inline void handleReplaceCertificateAction(
529     App& app, const crow::Request& req,
530     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
531 {
532     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
533     {
534         return;
535     }
536     std::string certificate;
537     std::string certURI;
538     std::optional<std::string> certificateType = "PEM";
539 
540     if (!json_util::readJsonAction(             //
541             req, asyncResp->res,                //
542             "CertificateString", certificate,   //
543             "CertificateType", certificateType, //
544             "CertificateUri/@odata.id", certURI //
545             ))
546     {
547         BMCWEB_LOG_ERROR("Required parameters are missing");
548         return;
549     }
550 
551     if (!certificateType)
552     {
553         // should never happen, but it never hurts to be paranoid.
554         return;
555     }
556     if (certificateType != "PEM")
557     {
558         messages::actionParameterNotSupported(asyncResp->res, "CertificateType",
559                                               "ReplaceCertificate");
560         return;
561     }
562 
563     BMCWEB_LOG_INFO("Certificate URI to replace: {}", certURI);
564 
565     boost::system::result<boost::urls::url> parsedUrl =
566         boost::urls::parse_relative_ref(certURI);
567     if (!parsedUrl)
568     {
569         messages::actionParameterValueFormatError(
570             asyncResp->res, certURI, "CertificateUri", "ReplaceCertificate");
571         return;
572     }
573 
574     std::string id;
575     sdbusplus::message::object_path objectPath;
576     std::string name;
577     std::string service;
578     if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", "Managers",
579                                        "bmc", "NetworkProtocol", "HTTPS",
580                                        "Certificates", std::ref(id)))
581     {
582         objectPath = sdbusplus::message::object_path(certs::httpsObjectPath) /
583                      id;
584         name = "HTTPS certificate";
585         service = certs::httpsServiceName;
586     }
587     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
588                                             "AccountService", "LDAP",
589                                             "Certificates", std::ref(id)))
590     {
591         objectPath = sdbusplus::message::object_path(certs::ldapObjectPath) /
592                      id;
593         name = "LDAP certificate";
594         service = certs::ldapServiceName;
595     }
596     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
597                                             "Managers", "bmc", "Truststore",
598                                             "Certificates", std::ref(id)))
599     {
600         objectPath =
601             sdbusplus::message::object_path(certs::authorityObjectPath) / id;
602         name = "TrustStore certificate";
603         service = certs::authorityServiceName;
604     }
605     else
606     {
607         messages::actionParameterNotSupported(asyncResp->res, "CertificateUri",
608                                               "ReplaceCertificate");
609         return;
610     }
611 
612     std::shared_ptr<CertificateFile> certFile =
613         std::make_shared<CertificateFile>(certificate);
614     dbus::utility::async_method_call(
615         asyncResp,
616         [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, name,
617          certificate](const boost::system::error_code& ec,
618                       sdbusplus::message_t& m) {
619             if (ec)
620             {
621                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
622                 const sd_bus_error* dbusError = m.get_error();
623                 if ((dbusError != nullptr) && (dbusError->name != nullptr))
624                 {
625                     handleError(dbusError->name, id, certificate, asyncResp);
626                 }
627                 else
628                 {
629                     messages::internalError(asyncResp->res);
630                 }
631                 return;
632             }
633             getCertificateProperties(asyncResp, objectPath, service, id, url,
634                                      name);
635             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
636                              certFile->getCertFilePath());
637         },
638         service, objectPath, certs::certReplaceIntf, "Replace",
639         certFile->getCertFilePath());
640 }
641 
642 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
643 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher;
644 /**
645  * @brief Read data from CSR D-bus object and set to response
646  *
647  * @param[in] asyncResp Shared pointer to the response message
648  * @param[in] certURI Link to certificate collection URI
649  * @param[in] service D-Bus service name
650  * @param[in] certObjPath certificate D-Bus object path
651  * @param[in] csrObjPath CSR D-Bus object path
652  * @return None
653  */
getCSR(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & certURI,const std::string & service,const std::string & certObjPath,const std::string & csrObjPath)654 inline void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
655                    const std::string& certURI, const std::string& service,
656                    const std::string& certObjPath,
657                    const std::string& csrObjPath)
658 {
659     BMCWEB_LOG_DEBUG("getCSR CertObjectPath{} CSRObjectPath={} service={}",
660                      certObjPath, csrObjPath, service);
661     dbus::utility::async_method_call(
662         asyncResp,
663         [asyncResp,
664          certURI](const boost::system::error_code& ec, const std::string& csr) {
665             if (ec)
666             {
667                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
668                 messages::internalError(asyncResp->res);
669                 return;
670             }
671             if (csr.empty())
672             {
673                 BMCWEB_LOG_ERROR("CSR read is empty");
674                 messages::internalError(asyncResp->res);
675                 return;
676             }
677             asyncResp->res.jsonValue["CSRString"] = csr;
678             asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
679                 certURI;
680         },
681         service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
682 }
683 
handleGenerateCSRAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)684 inline void handleGenerateCSRAction(
685     App& app, const crow::Request& req,
686     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
687 {
688     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
689     {
690         return;
691     }
692     static const int rsaKeyBitLength = 2048;
693 
694     // Required parameters
695     std::string city;
696     std::string commonName;
697     std::string country;
698     std::string organization;
699     std::string organizationalUnit;
700     std::string state;
701     std::string certURI;
702 
703     // Optional parameters
704     std::optional<std::vector<std::string>> optAlternativeNames =
705         std::vector<std::string>();
706     std::optional<std::string> optContactPerson = "";
707     std::optional<std::string> optChallengePassword = "";
708     std::optional<std::string> optEmail = "";
709     std::optional<std::string> optGivenName = "";
710     std::optional<std::string> optInitials = "";
711     std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
712     std::optional<std::string> optKeyCurveId = "secp384r1";
713     std::optional<std::string> optKeyPairAlgorithm = "EC";
714     std::optional<std::vector<std::string>> optKeyUsage =
715         std::vector<std::string>();
716     std::optional<std::string> optSurname = "";
717     std::optional<std::string> optUnstructuredName = "";
718     if (!json_util::readJsonAction(                     //
719             req, asyncResp->res,                        //
720             "AlternativeNames", optAlternativeNames,    //
721             "CertificateCollection/@odata.id", certURI, //
722             "ChallengePassword", optChallengePassword,  //
723             "City", city,                               //
724             "CommonName", commonName,                   //
725             "ContactPerson", optContactPerson,          //
726             "Country", country,                         //
727             "Email", optEmail,                          //
728             "GivenName", optGivenName,                  //
729             "Initials", optInitials,                    //
730             "KeyBitLength", optKeyBitLength,            //
731             "KeyCurveId", optKeyCurveId,                //
732             "KeyPairAlgorithm", optKeyPairAlgorithm,    //
733             "KeyUsage", optKeyUsage,                    //
734             "Organization", organization,               //
735             "OrganizationalUnit", organizationalUnit,   //
736             "State", state,                             //
737             "Surname", optSurname,                      //
738             "UnstructuredName", optUnstructuredName     //
739             ))
740     {
741         return;
742     }
743 
744     // bmcweb has no way to store or decode a private key challenge
745     // password, which will likely cause bmcweb to crash on startup
746     // if this is not set on a post so not allowing the user to set
747     // value
748     if (!optChallengePassword->empty())
749     {
750         messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR",
751                                               "ChallengePassword");
752         return;
753     }
754 
755     std::string objectPath;
756     std::string service;
757     if (certURI.starts_with(std::format(
758             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
759             BMCWEB_REDFISH_MANAGER_URI_NAME)))
760     {
761         objectPath = certs::httpsObjectPath;
762         service = certs::httpsServiceName;
763     }
764     else if (certURI.starts_with(
765                  "/redfish/v1/AccountService/LDAP/Certificates"))
766     {
767         objectPath = certs::ldapObjectPath;
768         service = certs::ldapServiceName;
769     }
770     else
771     {
772         messages::actionParameterNotSupported(
773             asyncResp->res, "CertificateCollection", "GenerateCSR");
774         return;
775     }
776 
777     // supporting only EC and RSA algorithm
778     if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
779     {
780         messages::actionParameterNotSupported(
781             asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
782         return;
783     }
784 
785     // supporting only 2048 key bit length for RSA algorithm due to
786     // time consumed in generating private key
787     if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength)
788     {
789         messages::propertyValueNotInList(asyncResp->res, *optKeyBitLength,
790                                          "KeyBitLength");
791         return;
792     }
793 
794     // validate KeyUsage supporting only 1 type based on URL
795     if (certURI.starts_with(std::format(
796             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
797             BMCWEB_REDFISH_MANAGER_URI_NAME)))
798     {
799         if (optKeyUsage->empty())
800         {
801             optKeyUsage->emplace_back("ServerAuthentication");
802         }
803         else if (optKeyUsage->size() == 1)
804         {
805             if ((*optKeyUsage)[0] != "ServerAuthentication")
806             {
807                 messages::propertyValueNotInList(asyncResp->res,
808                                                  (*optKeyUsage)[0], "KeyUsage");
809                 return;
810             }
811         }
812         else
813         {
814             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
815                                                   "GenerateCSR");
816             return;
817         }
818     }
819     else if (certURI.starts_with(
820                  "/redfish/v1/AccountService/LDAP/Certificates"))
821     {
822         if (optKeyUsage->empty())
823         {
824             optKeyUsage->emplace_back("ClientAuthentication");
825         }
826         else if (optKeyUsage->size() == 1)
827         {
828             if ((*optKeyUsage)[0] != "ClientAuthentication")
829             {
830                 messages::propertyValueNotInList(asyncResp->res,
831                                                  (*optKeyUsage)[0], "KeyUsage");
832                 return;
833             }
834         }
835         else
836         {
837             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
838                                                   "GenerateCSR");
839             return;
840         }
841     }
842 
843     // Only allow one CSR matcher at a time so setting retry
844     // time-out and timer expiry to 10 seconds for now.
845     static const int timeOut = 10;
846     if (csrMatcher)
847     {
848         messages::serviceTemporarilyUnavailable(asyncResp->res,
849                                                 std::to_string(timeOut));
850         return;
851     }
852 
853     // Make this static so it survives outside this method
854     static boost::asio::steady_timer timeout(getIoContext());
855     timeout.expires_after(std::chrono::seconds(timeOut));
856     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
857         csrMatcher = nullptr;
858         if (ec)
859         {
860             // operation_aborted is expected if timer is canceled
861             // before completion.
862             if (ec != boost::asio::error::operation_aborted)
863             {
864                 BMCWEB_LOG_ERROR("Async_wait failed {}", ec);
865             }
866             return;
867         }
868         BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR");
869         messages::internalError(asyncResp->res);
870     });
871 
872     // create a matcher to wait on CSR object
873     BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath);
874     std::string match("type='signal',"
875                       "interface='org.freedesktop.DBus.ObjectManager',"
876                       "path='" +
877                       objectPath +
878                       "',"
879                       "member='InterfacesAdded'");
880     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
881         *crow::connections::systemBus, match,
882         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
883             timeout.cancel();
884             if (m.is_method_error())
885             {
886                 BMCWEB_LOG_ERROR("Dbus method error!!!");
887                 messages::internalError(asyncResp->res);
888                 return;
889             }
890 
891             dbus::utility::DBusInterfacesMap interfacesProperties;
892 
893             sdbusplus::message::object_path csrObjectPath;
894             m.read(csrObjectPath, interfacesProperties);
895             BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str);
896             for (const auto& interface : interfacesProperties)
897             {
898                 if (interface.first == "xyz.openbmc_project.Certs.CSR")
899                 {
900                     getCSR(asyncResp, certURI, service, objectPath,
901                            csrObjectPath.str);
902                     break;
903                 }
904             }
905         });
906     dbus::utility::async_method_call(
907         asyncResp,
908         [asyncResp](const boost::system::error_code& ec, const std::string&) {
909             if (ec)
910             {
911                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message());
912                 messages::internalError(asyncResp->res);
913                 return;
914             }
915         },
916         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
917         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
918         commonName, *optContactPerson, country, *optEmail, *optGivenName,
919         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
920         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
921         *optUnstructuredName);
922 }
923 
requestRoutesCertificateService(App & app)924 inline void requestRoutesCertificateService(App& app)
925 {
926     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
927         .privileges(redfish::privileges::getCertificateService)
928         .methods(boost::beast::http::verb::get)(
929             std::bind_front(handleCertificateServiceGet, std::ref(app)));
930 
931     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
932         .privileges(redfish::privileges::getCertificateLocations)
933         .methods(boost::beast::http::verb::get)(
934             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
935 
936     BMCWEB_ROUTE(
937         app,
938         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
939         .privileges(redfish::privileges::postCertificateService)
940         .methods(boost::beast::http::verb::post)(
941             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
942 
943     BMCWEB_ROUTE(
944         app,
945         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
946         .privileges(redfish::privileges::postCertificateService)
947         .methods(boost::beast::http::verb::post)(
948             std::bind_front(handleGenerateCSRAction, std::ref(app)));
949 } // requestRoutesCertificateService
950 
handleHTTPSCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)951 inline void handleHTTPSCertificateCollectionGet(
952     App& app, const crow::Request& req,
953     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
954     const std::string& managerId)
955 {
956     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
957     {
958         return;
959     }
960 
961     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
962     {
963         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
964         return;
965     }
966 
967     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
968         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
969         BMCWEB_REDFISH_MANAGER_URI_NAME);
970     asyncResp->res.jsonValue["@odata.type"] =
971         "#CertificateCollection.CertificateCollection";
972     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
973     asyncResp->res.jsonValue["Description"] =
974         "A Collection of HTTPS certificate instances";
975 
976     getCertificateList(asyncResp, certs::httpsObjectPath,
977                        "/Members"_json_pointer,
978                        "/Members@odata.count"_json_pointer);
979 }
980 
handleHTTPSCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)981 inline void handleHTTPSCertificateCollectionPost(
982     App& app, const crow::Request& req,
983     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
984     const std::string& managerId)
985 {
986     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
987     {
988         return;
989     }
990 
991     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
992     {
993         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
994         return;
995     }
996 
997     BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost");
998 
999     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
1000     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
1001 
1002     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1003 
1004     if (certHttpBody.empty())
1005     {
1006         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1007         messages::unrecognizedRequestBody(asyncResp->res);
1008         return;
1009     }
1010 
1011     std::shared_ptr<CertificateFile> certFile =
1012         std::make_shared<CertificateFile>(certHttpBody);
1013 
1014     dbus::utility::async_method_call(
1015         asyncResp,
1016         [asyncResp, certFile](const boost::system::error_code& ec,
1017                               const std::string& objectPath) {
1018             if (ec)
1019             {
1020                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1021                 messages::internalError(asyncResp->res);
1022                 return;
1023             }
1024 
1025             sdbusplus::message::object_path path(objectPath);
1026             std::string certId = path.filename();
1027             const boost::urls::url certURL = boost::urls::format(
1028                 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1029                 BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1030             getCertificateProperties(asyncResp, objectPath,
1031                                      certs::httpsServiceName, certId, certURL,
1032                                      "HTTPS Certificate");
1033             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
1034                              certFile->getCertFilePath());
1035         },
1036         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
1037         "Install", certFile->getCertFilePath());
1038 }
1039 
handleHTTPSCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1040 inline void handleHTTPSCertificateGet(
1041     App& app, const crow::Request& req,
1042     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1043     const std::string& managerId, const std::string& certId)
1044 {
1045     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1046     {
1047         return;
1048     }
1049 
1050     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1051     {
1052         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1053         return;
1054     }
1055 
1056     BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", certId);
1057     const boost::urls::url certURL = boost::urls::format(
1058         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1059         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1060     std::string objPath =
1061         sdbusplus::message::object_path(certs::httpsObjectPath) / certId;
1062     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName,
1063                              certId, certURL, "HTTPS Certificate");
1064 }
1065 
requestRoutesHTTPSCertificate(App & app)1066 inline void requestRoutesHTTPSCertificate(App& app)
1067 {
1068     BMCWEB_ROUTE(
1069         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1070         .privileges(redfish::privileges::getCertificateCollection)
1071         .methods(boost::beast::http::verb::get)(std::bind_front(
1072             handleHTTPSCertificateCollectionGet, std::ref(app)));
1073 
1074     BMCWEB_ROUTE(
1075         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1076         .privileges(redfish::privileges::postCertificateCollection)
1077         .methods(boost::beast::http::verb::post)(std::bind_front(
1078             handleHTTPSCertificateCollectionPost, std::ref(app)));
1079 
1080     BMCWEB_ROUTE(
1081         app,
1082         "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/<str>/")
1083         .privileges(redfish::privileges::getCertificate)
1084         .methods(boost::beast::http::verb::get)(
1085             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
1086 }
1087 
handleLDAPCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1088 inline void handleLDAPCertificateCollectionGet(
1089     App& app, const crow::Request& req,
1090     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1091 {
1092     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1093     {
1094         return;
1095     }
1096 
1097     asyncResp->res.jsonValue["@odata.id"] =
1098         "/redfish/v1/AccountService/LDAP/Certificates";
1099     asyncResp->res.jsonValue["@odata.type"] =
1100         "#CertificateCollection.CertificateCollection";
1101     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1102     asyncResp->res.jsonValue["Description"] =
1103         "A Collection of LDAP certificate instances";
1104 
1105     getCertificateList(asyncResp, certs::ldapObjectPath,
1106                        "/Members"_json_pointer,
1107                        "/Members@odata.count"_json_pointer);
1108 }
1109 
handleLDAPCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1110 inline void handleLDAPCertificateCollectionPost(
1111     App& app, const crow::Request& req,
1112     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1113 {
1114     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1115     {
1116         return;
1117     }
1118     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1119 
1120     if (certHttpBody.empty())
1121     {
1122         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1123         messages::unrecognizedRequestBody(asyncResp->res);
1124         return;
1125     }
1126 
1127     std::shared_ptr<CertificateFile> certFile =
1128         std::make_shared<CertificateFile>(certHttpBody);
1129 
1130     dbus::utility::async_method_call(
1131         asyncResp,
1132         [asyncResp, certFile](const boost::system::error_code& ec,
1133                               const std::string& objectPath) {
1134             if (ec)
1135             {
1136                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1137                 messages::internalError(asyncResp->res);
1138                 return;
1139             }
1140 
1141             sdbusplus::message::object_path path(objectPath);
1142             std::string certId = path.filename();
1143             const boost::urls::url certURL = boost::urls::format(
1144                 "/redfish/v1/AccountService/LDAP/Certificates/{}", certId);
1145             getCertificateProperties(asyncResp, objectPath,
1146                                      certs::ldapServiceName, certId, certURL,
1147                                      "LDAP Certificate");
1148             BMCWEB_LOG_DEBUG("LDAP certificate install file={}",
1149                              certFile->getCertFilePath());
1150         },
1151         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1152         "Install", certFile->getCertFilePath());
1153 }
1154 
handleLDAPCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1155 inline void handleLDAPCertificateGet(
1156     App& app, const crow::Request& req,
1157     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1158 {
1159     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1160     {
1161         return;
1162     }
1163 
1164     BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id);
1165     const boost::urls::url certURL = boost::urls::format(
1166         "/redfish/v1/AccountService/LDAP/Certificates/{}", id);
1167     std::string objPath =
1168         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1169     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1170                              certURL, "LDAP Certificate");
1171 }
1172 
handleLDAPCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1173 inline void handleLDAPCertificateDelete(
1174     App& app, const crow::Request& req,
1175     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1176 {
1177     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1178     {
1179         return;
1180     }
1181 
1182     BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id);
1183     std::string objPath =
1184         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1185 
1186     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1187 }
1188 
requestRoutesLDAPCertificate(App & app)1189 inline void requestRoutesLDAPCertificate(App& app)
1190 {
1191     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1192         .privileges(redfish::privileges::getCertificateCollection)
1193         .methods(boost::beast::http::verb::get)(
1194             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1195 
1196     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1197         .privileges(redfish::privileges::postCertificateCollection)
1198         .methods(boost::beast::http::verb::post)(std::bind_front(
1199             handleLDAPCertificateCollectionPost, std::ref(app)));
1200 
1201     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1202         .privileges(redfish::privileges::getCertificate)
1203         .methods(boost::beast::http::verb::get)(
1204             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1205 
1206     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1207         .privileges(redfish::privileges::deleteCertificate)
1208         .methods(boost::beast::http::verb::delete_)(
1209             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1210 } // requestRoutesLDAPCertificate
1211 
handleTrustStoreCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1212 inline void handleTrustStoreCertificateCollectionGet(
1213     App& app, const crow::Request& req,
1214     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1215     const std::string& managerId)
1216 {
1217     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1218     {
1219         return;
1220     }
1221 
1222     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1223     {
1224         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1225         return;
1226     }
1227 
1228     asyncResp->res.jsonValue["@odata.id"] =
1229         boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates/",
1230                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1231     asyncResp->res.jsonValue["@odata.type"] =
1232         "#CertificateCollection.CertificateCollection";
1233     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1234     asyncResp->res.jsonValue["Description"] =
1235         "A Collection of TrustStore certificate instances";
1236 
1237     getCertificateList(asyncResp, certs::authorityObjectPath,
1238                        "/Members"_json_pointer,
1239                        "/Members@odata.count"_json_pointer);
1240 }
1241 
handleTrustStoreCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1242 inline void handleTrustStoreCertificateCollectionPost(
1243     App& app, const crow::Request& req,
1244     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1245     const std::string& managerId)
1246 {
1247     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1248     {
1249         return;
1250     }
1251 
1252     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1253     {
1254         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1255         return;
1256     }
1257 
1258     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1259 
1260     if (certHttpBody.empty())
1261     {
1262         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1263         messages::unrecognizedRequestBody(asyncResp->res);
1264         return;
1265     }
1266 
1267     std::shared_ptr<CertificateFile> certFile =
1268         std::make_shared<CertificateFile>(certHttpBody);
1269     dbus::utility::async_method_call(
1270         asyncResp,
1271         [asyncResp, certFile](const boost::system::error_code& ec,
1272                               const std::string& objectPath) {
1273             if (ec)
1274             {
1275                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1276                 messages::internalError(asyncResp->res);
1277                 return;
1278             }
1279 
1280             sdbusplus::message::object_path path(objectPath);
1281             std::string certId = path.filename();
1282             const boost::urls::url certURL = boost::urls::format(
1283                 "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
1284                 BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1285             getCertificateProperties(asyncResp, objectPath,
1286                                      certs::authorityServiceName, certId,
1287                                      certURL, "TrustStore Certificate");
1288             BMCWEB_LOG_DEBUG("TrustStore certificate install file={}",
1289                              certFile->getCertFilePath());
1290         },
1291         certs::authorityServiceName, certs::authorityObjectPath,
1292         certs::certInstallIntf, "Install", certFile->getCertFilePath());
1293 }
1294 
handleTrustStoreCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1295 inline void handleTrustStoreCertificateGet(
1296     App& app, const crow::Request& req,
1297     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1298     const std::string& managerId, const std::string& certId)
1299 {
1300     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1301     {
1302         return;
1303     }
1304 
1305     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1306     {
1307         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1308         return;
1309     }
1310 
1311     BMCWEB_LOG_DEBUG("Truststore Certificate ID={}", certId);
1312     const boost::urls::url certURL = boost::urls::format(
1313         "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
1314         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1315     std::string objPath =
1316         sdbusplus::message::object_path(certs::authorityObjectPath) / certId;
1317     getCertificateProperties(asyncResp, objPath, certs::authorityServiceName,
1318                              certId, certURL, "TrustStore Certificate");
1319 }
1320 
handleTrustStoreCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1321 inline void handleTrustStoreCertificateDelete(
1322     App& app, const crow::Request& req,
1323     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1324     const std::string& managerId, const std::string& certId)
1325 {
1326     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1327     {
1328         return;
1329     }
1330 
1331     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1332     {
1333         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1334         return;
1335     }
1336 
1337     BMCWEB_LOG_DEBUG("Delete TrustStore Certificate ID={}", certId);
1338     std::string objPath =
1339         sdbusplus::message::object_path(certs::authorityObjectPath) / certId;
1340 
1341     deleteCertificate(asyncResp, certs::authorityServiceName, objPath);
1342 }
1343 
requestRoutesTrustStoreCertificate(App & app)1344 inline void requestRoutesTrustStoreCertificate(App& app)
1345 {
1346     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/")
1347         .privileges(redfish::privileges::getCertificate)
1348         .methods(boost::beast::http::verb::get)(std::bind_front(
1349             handleTrustStoreCertificateCollectionGet, std::ref(app)));
1350 
1351     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/")
1352         .privileges(redfish::privileges::postCertificateCollection)
1353         .methods(boost::beast::http::verb::post)(std::bind_front(
1354             handleTrustStoreCertificateCollectionPost, std::ref(app)));
1355 
1356     BMCWEB_ROUTE(app,
1357                  "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/")
1358         .privileges(redfish::privileges::getCertificate)
1359         .methods(boost::beast::http::verb::get)(
1360             std::bind_front(handleTrustStoreCertificateGet, std::ref(app)));
1361 
1362     BMCWEB_ROUTE(app,
1363                  "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/")
1364         .privileges(redfish::privileges::deleteCertificate)
1365         .methods(boost::beast::http::verb::delete_)(
1366             std::bind_front(handleTrustStoreCertificateDelete, std::ref(app)));
1367 } // requestRoutesTrustStoreCertificate
1368 } // namespace redfish
1369