xref: /openbmc/bmcweb/redfish-core/lib/certificate_service.hpp (revision aae437ed4a30ae63056e52e60a8fe96d57289130)
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* httpsServiceName =
69     "xyz.openbmc_project.Certs.Manager.Server.Https";
70 constexpr const char* ldapServiceName =
71     "xyz.openbmc_project.Certs.Manager.Client.Ldap";
72 constexpr const char* authorityServiceName =
73     "xyz.openbmc_project.Certs.Manager.Authority.Truststore";
74 constexpr const char* baseObjectPath = "/xyz/openbmc_project/certs";
75 constexpr const char* httpsObjectPath =
76     "/xyz/openbmc_project/certs/server/https";
77 constexpr const char* ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
78 constexpr const char* authorityObjectPath =
79     "/xyz/openbmc_project/certs/authority/truststore";
80 } // namespace certs
81 
82 /**
83  * The Certificate schema defines a Certificate Service which represents the
84  * actions available to manage certificates and links to where certificates
85  * are installed.
86  */
87 
getCertificateFromReqBody(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const crow::Request & req)88 inline std::string getCertificateFromReqBody(
89     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
90     const crow::Request& req)
91 {
92     nlohmann::json reqJson;
93     JsonParseResult ret = parseRequestAsJson(req, reqJson);
94     if (ret != JsonParseResult::Success)
95     {
96         // We did not receive JSON request, proceed as it is RAW data
97         return req.body();
98     }
99 
100     std::string certificate;
101     std::optional<std::string> certificateType = "PEM";
102 
103     if (!json_util::readJsonPatch(             //
104             req, asyncResp->res,               //
105             "CertificateString", certificate,  //
106             "CertificateType", certificateType //
107             ))
108     {
109         BMCWEB_LOG_ERROR("Required parameters are missing");
110         messages::internalError(asyncResp->res);
111         return {};
112     }
113 
114     if (*certificateType != "PEM")
115     {
116         messages::propertyValueNotInList(asyncResp->res, *certificateType,
117                                          "CertificateType");
118         return {};
119     }
120 
121     return certificate;
122 }
123 
124 /**
125  * Class to create a temporary certificate file for uploading to system
126  */
127 class CertificateFile
128 {
129   public:
130     CertificateFile() = delete;
131     CertificateFile(const CertificateFile&) = delete;
132     CertificateFile& operator=(const CertificateFile&) = delete;
133     CertificateFile(CertificateFile&&) = delete;
134     CertificateFile& operator=(CertificateFile&&) = delete;
CertificateFile(const std::string & certString)135     explicit CertificateFile(const std::string& certString)
136     {
137         std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C',
138                                             'e', 'r', 't', 's', '.', 'X',
139                                             'X', 'X', 'X', 'X', 'X', '\0'};
140         // NOLINTNEXTLINE(misc-include-cleaner)
141         char* tempDirectory = mkdtemp(dirTemplate.data());
142         if (tempDirectory != nullptr)
143         {
144             certDirectory = tempDirectory;
145             certificateFile = certDirectory / "cert.pem";
146             std::ofstream out(certificateFile,
147                               std::ofstream::out | std::ofstream::binary |
148                                   std::ofstream::trunc);
149             out << certString;
150             out.close();
151             BMCWEB_LOG_DEBUG("Creating certificate file{}",
152                              certificateFile.string());
153         }
154     }
~CertificateFile()155     ~CertificateFile()
156     {
157         if (std::filesystem::exists(certDirectory))
158         {
159             BMCWEB_LOG_DEBUG("Removing certificate file{}",
160                              certificateFile.string());
161             std::error_code ec;
162             std::filesystem::remove_all(certDirectory, ec);
163             if (ec)
164             {
165                 BMCWEB_LOG_ERROR("Failed to remove temp directory{}",
166                                  certDirectory.string());
167             }
168         }
169     }
getCertFilePath()170     std::string getCertFilePath()
171     {
172         return certificateFile;
173     }
174 
175   private:
176     std::filesystem::path certificateFile;
177     std::filesystem::path certDirectory;
178 };
179 
180 /**
181  * @brief Parse and update Certificate Issue/Subject property
182  *
183  * @param[in] asyncResp Shared pointer to the response message
184  * @param[in] str  Issuer/Subject value in key=value pairs
185  * @param[in] type Issuer/Subject
186  * @return None
187  */
updateCertIssuerOrSubject(nlohmann::json & out,std::string_view value)188 inline void updateCertIssuerOrSubject(nlohmann::json& out,
189                                       std::string_view value)
190 {
191     // example: O=openbmc-project.xyz,CN=localhost
192     std::string_view::iterator i = value.begin();
193     while (i != value.end())
194     {
195         std::string_view::iterator tokenBegin = i;
196         while (i != value.end() && *i != '=')
197         {
198             std::advance(i, 1);
199         }
200         if (i == value.end())
201         {
202             break;
203         }
204         std::string_view key(tokenBegin, static_cast<size_t>(i - tokenBegin));
205         std::advance(i, 1);
206         tokenBegin = i;
207         while (i != value.end() && *i != ',')
208         {
209             std::advance(i, 1);
210         }
211         std::string_view val(tokenBegin, static_cast<size_t>(i - tokenBegin));
212         if (key == "L")
213         {
214             out["City"] = val;
215         }
216         else if (key == "CN")
217         {
218             out["CommonName"] = val;
219         }
220         else if (key == "C")
221         {
222             out["Country"] = val;
223         }
224         else if (key == "O")
225         {
226             out["Organization"] = val;
227         }
228         else if (key == "OU")
229         {
230             out["OrganizationalUnit"] = val;
231         }
232         else if (key == "ST")
233         {
234             out["State"] = val;
235         }
236         // skip comma character
237         if (i != value.end())
238         {
239             std::advance(i, 1);
240         }
241     }
242 }
243 
244 /**
245  * @brief Retrieve the installed certificate list
246  *
247  * @param[in] asyncResp Shared pointer to the response message
248  * @param[in] basePath DBus object path to search
249  * @param[in] listPtr Json pointer to the list in asyncResp
250  * @param[in] countPtr Json pointer to the count in asyncResp
251  * @return None
252  */
getCertificateList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & basePath,const nlohmann::json::json_pointer & listPtr,const nlohmann::json::json_pointer & countPtr)253 inline void getCertificateList(
254     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
255     const std::string& basePath, const nlohmann::json::json_pointer& listPtr,
256     const nlohmann::json::json_pointer& countPtr)
257 {
258     constexpr std::array<std::string_view, 1> interfaces = {
259         certs::certPropIntf};
260     dbus::utility::getSubTreePaths(
261         basePath, 0, interfaces,
262         [asyncResp, listPtr, countPtr](
263             const boost::system::error_code& ec,
264             const dbus::utility::MapperGetSubTreePathsResponse& certPaths) {
265             if (ec)
266             {
267                 BMCWEB_LOG_ERROR("Certificate collection query failed: {}", ec);
268                 messages::internalError(asyncResp->res);
269                 return;
270             }
271 
272             nlohmann::json& links = asyncResp->res.jsonValue[listPtr];
273             links = nlohmann::json::array();
274             for (const auto& certPath : certPaths)
275             {
276                 sdbusplus::message::object_path objPath(certPath);
277                 std::string certId = objPath.filename();
278                 if (certId.empty())
279                 {
280                     BMCWEB_LOG_ERROR("Invalid certificate objPath {}",
281                                      certPath);
282                     continue;
283                 }
284 
285                 boost::urls::url certURL;
286                 if (objPath.parent_path() == certs::httpsObjectPath)
287                 {
288                     certURL = boost::urls::format(
289                         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
290                         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
291                 }
292                 else if (objPath.parent_path() == certs::ldapObjectPath)
293                 {
294                     certURL = boost::urls::format(
295                         "/redfish/v1/AccountService/LDAP/Certificates/{}",
296                         certId);
297                 }
298                 else if (objPath.parent_path() == certs::authorityObjectPath)
299                 {
300                     certURL = boost::urls::format(
301                         "/redfish/v1/Managers/{}/Truststore/Certificates/{}",
302                         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
303                 }
304                 else
305                 {
306                     continue;
307                 }
308 
309                 nlohmann::json::object_t link;
310                 link["@odata.id"] = certURL;
311                 links.emplace_back(std::move(link));
312             }
313 
314             asyncResp->res.jsonValue[countPtr] = links.size();
315         });
316 }
317 
318 /**
319  * @brief Retrieve the certificates properties and append to the response
320  * message
321  *
322  * @param[in] asyncResp Shared pointer to the response message
323  * @param[in] objectPath  Path of the D-Bus service object
324  * @param[in] certId  Id of the certificate
325  * @param[in] certURL  URL of the certificate object
326  * @param[in] name  name of the certificate
327  * @return None
328  */
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)329 inline void getCertificateProperties(
330     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
331     const std::string& objectPath, const std::string& service,
332     const std::string& certId, const boost::urls::url& certURL,
333     const std::string& name)
334 {
335     BMCWEB_LOG_DEBUG("getCertificateProperties Path={} certId={} certURl={}",
336                      objectPath, certId, certURL);
337     dbus::utility::getAllProperties(
338         service, objectPath, certs::certPropIntf,
339         [asyncResp, certURL, certId,
340          name](const boost::system::error_code& ec,
341                const dbus::utility::DBusPropertiesMap& properties) {
342             if (ec)
343             {
344                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
345                 messages::resourceNotFound(asyncResp->res, "Certificate",
346                                            certId);
347                 return;
348             }
349 
350             const std::string* certificateString = nullptr;
351             const std::vector<std::string>* keyUsage = nullptr;
352             const std::string* issuer = nullptr;
353             const std::string* subject = nullptr;
354             const uint64_t* validNotAfter = nullptr;
355             const uint64_t* validNotBefore = nullptr;
356 
357             const bool success = sdbusplus::unpackPropertiesNoThrow(
358                 dbus_utils::UnpackErrorPrinter(), properties,
359                 "CertificateString", certificateString, "KeyUsage", keyUsage,
360                 "Issuer", issuer, "Subject", subject, "ValidNotAfter",
361                 validNotAfter, "ValidNotBefore", validNotBefore);
362 
363             if (!success)
364             {
365                 messages::internalError(asyncResp->res);
366                 return;
367             }
368 
369             asyncResp->res.jsonValue["@odata.id"] = certURL;
370             asyncResp->res.jsonValue["@odata.type"] =
371                 "#Certificate.v1_0_0.Certificate";
372             asyncResp->res.jsonValue["Id"] = certId;
373             asyncResp->res.jsonValue["Name"] = name;
374             asyncResp->res.jsonValue["Description"] = name;
375             asyncResp->res.jsonValue["CertificateString"] = "";
376             asyncResp->res.jsonValue["KeyUsage"] = nlohmann::json::array();
377 
378             if (certificateString != nullptr)
379             {
380                 asyncResp->res.jsonValue["CertificateString"] =
381                     *certificateString;
382             }
383 
384             if (keyUsage != nullptr)
385             {
386                 asyncResp->res.jsonValue["KeyUsage"] = *keyUsage;
387             }
388 
389             if (issuer != nullptr)
390             {
391                 updateCertIssuerOrSubject(asyncResp->res.jsonValue["Issuer"],
392                                           *issuer);
393             }
394 
395             if (subject != nullptr)
396             {
397                 updateCertIssuerOrSubject(asyncResp->res.jsonValue["Subject"],
398                                           *subject);
399             }
400 
401             if (validNotAfter != nullptr)
402             {
403                 asyncResp->res.jsonValue["ValidNotAfter"] =
404                     redfish::time_utils::getDateTimeUint(*validNotAfter);
405             }
406 
407             if (validNotBefore != nullptr)
408             {
409                 asyncResp->res.jsonValue["ValidNotBefore"] =
410                     redfish::time_utils::getDateTimeUint(*validNotBefore);
411             }
412 
413             asyncResp->res.addHeader(
414                 boost::beast::http::field::location,
415                 std::string_view(certURL.data(), certURL.size()));
416         });
417 }
418 
deleteCertificate(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & service,const sdbusplus::message::object_path & objectPath)419 inline void deleteCertificate(
420     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
421     const std::string& service,
422     const sdbusplus::message::object_path& objectPath)
423 {
424     dbus::utility::async_method_call(
425         asyncResp,
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     dbus::utility::async_method_call(
613         asyncResp,
614         [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, name,
615          certificate](const boost::system::error_code& ec,
616                       sdbusplus::message_t& m) {
617             if (ec)
618             {
619                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
620                 const sd_bus_error* dbusError = m.get_error();
621                 if ((dbusError != nullptr) && (dbusError->name != nullptr))
622                 {
623                     handleError(dbusError->name, id, certificate, asyncResp);
624                 }
625                 else
626                 {
627                     messages::internalError(asyncResp->res);
628                 }
629                 return;
630             }
631             getCertificateProperties(asyncResp, objectPath, service, id, url,
632                                      name);
633             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
634                              certFile->getCertFilePath());
635         },
636         service, objectPath, certs::certReplaceIntf, "Replace",
637         certFile->getCertFilePath());
638 }
639 
640 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
641 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher;
642 /**
643  * @brief Read data from CSR D-bus object and set to response
644  *
645  * @param[in] asyncResp Shared pointer to the response message
646  * @param[in] certURI Link to certificate collection URI
647  * @param[in] service D-Bus service name
648  * @param[in] certObjPath certificate D-Bus object path
649  * @param[in] csrObjPath CSR D-Bus object path
650  * @return None
651  */
getCSR(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & certURI,const std::string & service,const std::string & certObjPath,const std::string & csrObjPath)652 inline void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
653                    const std::string& certURI, const std::string& service,
654                    const std::string& certObjPath,
655                    const std::string& csrObjPath)
656 {
657     BMCWEB_LOG_DEBUG("getCSR CertObjectPath{} CSRObjectPath={} service={}",
658                      certObjPath, csrObjPath, service);
659     dbus::utility::async_method_call(
660         asyncResp,
661         [asyncResp,
662          certURI](const boost::system::error_code& ec, const std::string& csr) {
663             if (ec)
664             {
665                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
666                 messages::internalError(asyncResp->res);
667                 return;
668             }
669             if (csr.empty())
670             {
671                 BMCWEB_LOG_ERROR("CSR read is empty");
672                 messages::internalError(asyncResp->res);
673                 return;
674             }
675             asyncResp->res.jsonValue["CSRString"] = csr;
676             asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
677                 certURI;
678         },
679         service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
680 }
681 
handleGenerateCSRAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)682 inline void handleGenerateCSRAction(
683     App& app, const crow::Request& req,
684     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
685 {
686     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
687     {
688         return;
689     }
690     static const int rsaKeyBitLength = 2048;
691 
692     // Required parameters
693     std::string city;
694     std::string commonName;
695     std::string country;
696     std::string organization;
697     std::string organizationalUnit;
698     std::string state;
699     std::string certURI;
700 
701     // Optional parameters
702     std::optional<std::vector<std::string>> optAlternativeNames =
703         std::vector<std::string>();
704     std::optional<std::string> optContactPerson = "";
705     std::optional<std::string> optChallengePassword = "";
706     std::optional<std::string> optEmail = "";
707     std::optional<std::string> optGivenName = "";
708     std::optional<std::string> optInitials = "";
709     std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
710     std::optional<std::string> optKeyCurveId = "secp384r1";
711     std::optional<std::string> optKeyPairAlgorithm = "EC";
712     std::optional<std::vector<std::string>> optKeyUsage =
713         std::vector<std::string>();
714     std::optional<std::string> optSurname = "";
715     std::optional<std::string> optUnstructuredName = "";
716     if (!json_util::readJsonAction(                     //
717             req, asyncResp->res,                        //
718             "AlternativeNames", optAlternativeNames,    //
719             "CertificateCollection/@odata.id", certURI, //
720             "ChallengePassword", optChallengePassword,  //
721             "City", city,                               //
722             "CommonName", commonName,                   //
723             "ContactPerson", optContactPerson,          //
724             "Country", country,                         //
725             "Email", optEmail,                          //
726             "GivenName", optGivenName,                  //
727             "Initials", optInitials,                    //
728             "KeyBitLength", optKeyBitLength,            //
729             "KeyCurveId", optKeyCurveId,                //
730             "KeyPairAlgorithm", optKeyPairAlgorithm,    //
731             "KeyUsage", optKeyUsage,                    //
732             "Organization", organization,               //
733             "OrganizationalUnit", organizationalUnit,   //
734             "State", state,                             //
735             "Surname", optSurname,                      //
736             "UnstructuredName", optUnstructuredName     //
737             ))
738     {
739         return;
740     }
741 
742     // bmcweb has no way to store or decode a private key challenge
743     // password, which will likely cause bmcweb to crash on startup
744     // if this is not set on a post so not allowing the user to set
745     // value
746     if (!optChallengePassword->empty())
747     {
748         messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR",
749                                               "ChallengePassword");
750         return;
751     }
752 
753     std::string objectPath;
754     std::string service;
755     if (certURI.starts_with(std::format(
756             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
757             BMCWEB_REDFISH_MANAGER_URI_NAME)))
758     {
759         objectPath = certs::httpsObjectPath;
760         service = certs::httpsServiceName;
761     }
762     else if (certURI.starts_with(
763                  "/redfish/v1/AccountService/LDAP/Certificates"))
764     {
765         objectPath = certs::ldapObjectPath;
766         service = certs::ldapServiceName;
767     }
768     else
769     {
770         messages::actionParameterNotSupported(
771             asyncResp->res, "CertificateCollection", "GenerateCSR");
772         return;
773     }
774 
775     // supporting only EC and RSA algorithm
776     if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
777     {
778         messages::actionParameterNotSupported(
779             asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
780         return;
781     }
782 
783     // supporting only 2048 key bit length for RSA algorithm due to
784     // time consumed in generating private key
785     if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength)
786     {
787         messages::propertyValueNotInList(asyncResp->res, *optKeyBitLength,
788                                          "KeyBitLength");
789         return;
790     }
791 
792     // validate KeyUsage supporting only 1 type based on URL
793     if (certURI.starts_with(std::format(
794             "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
795             BMCWEB_REDFISH_MANAGER_URI_NAME)))
796     {
797         if (optKeyUsage->empty())
798         {
799             optKeyUsage->emplace_back("ServerAuthentication");
800         }
801         else if (optKeyUsage->size() == 1)
802         {
803             if ((*optKeyUsage)[0] != "ServerAuthentication")
804             {
805                 messages::propertyValueNotInList(asyncResp->res,
806                                                  (*optKeyUsage)[0], "KeyUsage");
807                 return;
808             }
809         }
810         else
811         {
812             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
813                                                   "GenerateCSR");
814             return;
815         }
816     }
817     else if (certURI.starts_with(
818                  "/redfish/v1/AccountService/LDAP/Certificates"))
819     {
820         if (optKeyUsage->empty())
821         {
822             optKeyUsage->emplace_back("ClientAuthentication");
823         }
824         else if (optKeyUsage->size() == 1)
825         {
826             if ((*optKeyUsage)[0] != "ClientAuthentication")
827             {
828                 messages::propertyValueNotInList(asyncResp->res,
829                                                  (*optKeyUsage)[0], "KeyUsage");
830                 return;
831             }
832         }
833         else
834         {
835             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
836                                                   "GenerateCSR");
837             return;
838         }
839     }
840 
841     // Only allow one CSR matcher at a time so setting retry
842     // time-out and timer expiry to 10 seconds for now.
843     static const int timeOut = 10;
844     if (csrMatcher)
845     {
846         messages::serviceTemporarilyUnavailable(asyncResp->res,
847                                                 std::to_string(timeOut));
848         return;
849     }
850 
851     // Make this static so it survives outside this method
852     static boost::asio::steady_timer timeout(getIoContext());
853     timeout.expires_after(std::chrono::seconds(timeOut));
854     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
855         csrMatcher = nullptr;
856         if (ec)
857         {
858             // operation_aborted is expected if timer is canceled
859             // before completion.
860             if (ec != boost::asio::error::operation_aborted)
861             {
862                 BMCWEB_LOG_ERROR("Async_wait failed {}", ec);
863             }
864             return;
865         }
866         BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR");
867         messages::internalError(asyncResp->res);
868     });
869 
870     // create a matcher to wait on CSR object
871     BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath);
872     std::string match("type='signal',"
873                       "interface='org.freedesktop.DBus.ObjectManager',"
874                       "path='" +
875                       objectPath +
876                       "',"
877                       "member='InterfacesAdded'");
878     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
879         *crow::connections::systemBus, match,
880         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
881             timeout.cancel();
882             if (m.is_method_error())
883             {
884                 BMCWEB_LOG_ERROR("Dbus method error!!!");
885                 messages::internalError(asyncResp->res);
886                 return;
887             }
888 
889             dbus::utility::DBusInterfacesMap interfacesProperties;
890 
891             sdbusplus::message::object_path csrObjectPath;
892             m.read(csrObjectPath, interfacesProperties);
893             BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str);
894             for (const auto& interface : interfacesProperties)
895             {
896                 if (interface.first == "xyz.openbmc_project.Certs.CSR")
897                 {
898                     getCSR(asyncResp, certURI, service, objectPath,
899                            csrObjectPath.str);
900                     break;
901                 }
902             }
903         });
904     dbus::utility::async_method_call(
905         asyncResp,
906         [asyncResp](const boost::system::error_code& ec, const std::string&) {
907             if (ec)
908             {
909                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message());
910                 messages::internalError(asyncResp->res);
911                 return;
912             }
913         },
914         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
915         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
916         commonName, *optContactPerson, country, *optEmail, *optGivenName,
917         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
918         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
919         *optUnstructuredName);
920 }
921 
requestRoutesCertificateService(App & app)922 inline void requestRoutesCertificateService(App& app)
923 {
924     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
925         .privileges(redfish::privileges::getCertificateService)
926         .methods(boost::beast::http::verb::get)(
927             std::bind_front(handleCertificateServiceGet, std::ref(app)));
928 
929     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
930         .privileges(redfish::privileges::getCertificateLocations)
931         .methods(boost::beast::http::verb::get)(
932             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
933 
934     BMCWEB_ROUTE(
935         app,
936         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
937         .privileges(redfish::privileges::postCertificateService)
938         .methods(boost::beast::http::verb::post)(
939             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
940 
941     BMCWEB_ROUTE(
942         app,
943         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
944         .privileges(redfish::privileges::postCertificateService)
945         .methods(boost::beast::http::verb::post)(
946             std::bind_front(handleGenerateCSRAction, std::ref(app)));
947 } // requestRoutesCertificateService
948 
handleHTTPSCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)949 inline void handleHTTPSCertificateCollectionGet(
950     App& app, const crow::Request& req,
951     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
952     const std::string& managerId)
953 {
954     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
955     {
956         return;
957     }
958 
959     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
960     {
961         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
962         return;
963     }
964 
965     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
966         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates",
967         BMCWEB_REDFISH_MANAGER_URI_NAME);
968     asyncResp->res.jsonValue["@odata.type"] =
969         "#CertificateCollection.CertificateCollection";
970     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
971     asyncResp->res.jsonValue["Description"] =
972         "A Collection of HTTPS certificate instances";
973 
974     getCertificateList(asyncResp, certs::httpsObjectPath,
975                        "/Members"_json_pointer,
976                        "/Members@odata.count"_json_pointer);
977 }
978 
handleHTTPSCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)979 inline void handleHTTPSCertificateCollectionPost(
980     App& app, const crow::Request& req,
981     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
982     const std::string& managerId)
983 {
984     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
985     {
986         return;
987     }
988 
989     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
990     {
991         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
992         return;
993     }
994 
995     BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost");
996 
997     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
998     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
999 
1000     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1001 
1002     if (certHttpBody.empty())
1003     {
1004         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1005         messages::unrecognizedRequestBody(asyncResp->res);
1006         return;
1007     }
1008 
1009     std::shared_ptr<CertificateFile> certFile =
1010         std::make_shared<CertificateFile>(certHttpBody);
1011 
1012     dbus::utility::async_method_call(
1013         asyncResp,
1014         [asyncResp, certFile](const boost::system::error_code& ec,
1015                               const std::string& objectPath) {
1016             if (ec)
1017             {
1018                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1019                 messages::internalError(asyncResp->res);
1020                 return;
1021             }
1022 
1023             sdbusplus::message::object_path path(objectPath);
1024             std::string certId = path.filename();
1025             const boost::urls::url certURL = boost::urls::format(
1026                 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1027                 BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1028             getCertificateProperties(asyncResp, objectPath,
1029                                      certs::httpsServiceName, certId, certURL,
1030                                      "HTTPS Certificate");
1031             BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
1032                              certFile->getCertFilePath());
1033         },
1034         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
1035         "Install", certFile->getCertFilePath());
1036 }
1037 
handleHTTPSCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1038 inline void handleHTTPSCertificateGet(
1039     App& app, const crow::Request& req,
1040     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1041     const std::string& managerId, const std::string& certId)
1042 {
1043     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1044     {
1045         return;
1046     }
1047 
1048     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1049     {
1050         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1051         return;
1052     }
1053 
1054     BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", certId);
1055     const boost::urls::url certURL = boost::urls::format(
1056         "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}",
1057         BMCWEB_REDFISH_MANAGER_URI_NAME, certId);
1058     std::string objPath =
1059         sdbusplus::message::object_path(certs::httpsObjectPath) / certId;
1060     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName,
1061                              certId, certURL, "HTTPS Certificate");
1062 }
1063 
requestRoutesHTTPSCertificate(App & app)1064 inline void requestRoutesHTTPSCertificate(App& app)
1065 {
1066     BMCWEB_ROUTE(
1067         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1068         .privileges(redfish::privileges::getCertificateCollection)
1069         .methods(boost::beast::http::verb::get)(std::bind_front(
1070             handleHTTPSCertificateCollectionGet, std::ref(app)));
1071 
1072     BMCWEB_ROUTE(
1073         app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/")
1074         .privileges(redfish::privileges::postCertificateCollection)
1075         .methods(boost::beast::http::verb::post)(std::bind_front(
1076             handleHTTPSCertificateCollectionPost, std::ref(app)));
1077 
1078     BMCWEB_ROUTE(
1079         app,
1080         "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/<str>/")
1081         .privileges(redfish::privileges::getCertificate)
1082         .methods(boost::beast::http::verb::get)(
1083             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
1084 }
1085 
handleLDAPCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1086 inline void handleLDAPCertificateCollectionGet(
1087     App& app, const crow::Request& req,
1088     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1089 {
1090     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1091     {
1092         return;
1093     }
1094 
1095     asyncResp->res.jsonValue["@odata.id"] =
1096         "/redfish/v1/AccountService/LDAP/Certificates";
1097     asyncResp->res.jsonValue["@odata.type"] =
1098         "#CertificateCollection.CertificateCollection";
1099     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1100     asyncResp->res.jsonValue["Description"] =
1101         "A Collection of LDAP certificate instances";
1102 
1103     getCertificateList(asyncResp, certs::ldapObjectPath,
1104                        "/Members"_json_pointer,
1105                        "/Members@odata.count"_json_pointer);
1106 }
1107 
handleLDAPCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1108 inline void handleLDAPCertificateCollectionPost(
1109     App& app, const crow::Request& req,
1110     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1111 {
1112     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1113     {
1114         return;
1115     }
1116     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1117 
1118     if (certHttpBody.empty())
1119     {
1120         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1121         messages::unrecognizedRequestBody(asyncResp->res);
1122         return;
1123     }
1124 
1125     std::shared_ptr<CertificateFile> certFile =
1126         std::make_shared<CertificateFile>(certHttpBody);
1127 
1128     dbus::utility::async_method_call(
1129         asyncResp,
1130         [asyncResp, certFile](const boost::system::error_code& ec,
1131                               const std::string& objectPath) {
1132             if (ec)
1133             {
1134                 BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1135                 messages::internalError(asyncResp->res);
1136                 return;
1137             }
1138 
1139             sdbusplus::message::object_path path(objectPath);
1140             std::string certId = path.filename();
1141             const boost::urls::url certURL = boost::urls::format(
1142                 "/redfish/v1/AccountService/LDAP/Certificates/{}", certId);
1143             getCertificateProperties(asyncResp, objectPath,
1144                                      certs::ldapServiceName, certId, certURL,
1145                                      "LDAP Certificate");
1146             BMCWEB_LOG_DEBUG("LDAP certificate install file={}",
1147                              certFile->getCertFilePath());
1148         },
1149         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1150         "Install", certFile->getCertFilePath());
1151 }
1152 
handleLDAPCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1153 inline void handleLDAPCertificateGet(
1154     App& app, const crow::Request& req,
1155     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1156 {
1157     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1158     {
1159         return;
1160     }
1161 
1162     BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id);
1163     const boost::urls::url certURL = boost::urls::format(
1164         "/redfish/v1/AccountService/LDAP/Certificates/{}", id);
1165     std::string objPath =
1166         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1167     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1168                              certURL, "LDAP Certificate");
1169 }
1170 
handleLDAPCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1171 inline void handleLDAPCertificateDelete(
1172     App& app, const crow::Request& req,
1173     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1174 {
1175     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1176     {
1177         return;
1178     }
1179 
1180     BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id);
1181     std::string objPath =
1182         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1183 
1184     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1185 }
1186 
requestRoutesLDAPCertificate(App & app)1187 inline void requestRoutesLDAPCertificate(App& app)
1188 {
1189     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1190         .privileges(redfish::privileges::getCertificateCollection)
1191         .methods(boost::beast::http::verb::get)(
1192             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1193 
1194     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1195         .privileges(redfish::privileges::postCertificateCollection)
1196         .methods(boost::beast::http::verb::post)(std::bind_front(
1197             handleLDAPCertificateCollectionPost, std::ref(app)));
1198 
1199     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1200         .privileges(redfish::privileges::getCertificate)
1201         .methods(boost::beast::http::verb::get)(
1202             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1203 
1204     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1205         .privileges(redfish::privileges::deleteCertificate)
1206         .methods(boost::beast::http::verb::delete_)(
1207             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1208 } // requestRoutesLDAPCertificate
1209 
handleTrustStoreCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1210 inline void handleTrustStoreCertificateCollectionGet(
1211     App& app, const crow::Request& req,
1212     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1213     const std::string& managerId)
1214 {
1215     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1216     {
1217         return;
1218     }
1219 
1220     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1221     {
1222         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1223         return;
1224     }
1225 
1226     asyncResp->res.jsonValue["@odata.id"] =
1227         boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates/",
1228                             BMCWEB_REDFISH_MANAGER_URI_NAME);
1229     asyncResp->res.jsonValue["@odata.type"] =
1230         "#CertificateCollection.CertificateCollection";
1231     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1232     asyncResp->res.jsonValue["Description"] =
1233         "A Collection of TrustStore certificate instances";
1234 
1235     getCertificateList(asyncResp, certs::authorityObjectPath,
1236                        "/Members"_json_pointer,
1237                        "/Members@odata.count"_json_pointer);
1238 }
1239 
handleTrustStoreCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1240 inline void handleTrustStoreCertificateCollectionPost(
1241     App& app, const crow::Request& req,
1242     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1243     const std::string& managerId)
1244 {
1245     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1246     {
1247         return;
1248     }
1249 
1250     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
1251     {
1252         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
1253         return;
1254     }
1255 
1256     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1257 
1258     if (certHttpBody.empty())
1259     {
1260         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1261         messages::unrecognizedRequestBody(asyncResp->res);
1262         return;
1263     }
1264 
1265     std::shared_ptr<CertificateFile> certFile =
1266         std::make_shared<CertificateFile>(certHttpBody);
1267     dbus::utility::async_method_call(
1268         asyncResp,
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