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