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