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