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