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