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