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