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 
23 #include <variant>
24 namespace redfish
25 {
26 namespace certs
27 {
28 constexpr char const* httpsObjectPath =
29     "/xyz/openbmc_project/certs/server/https";
30 constexpr char const* certInstallIntf = "xyz.openbmc_project.Certs.Install";
31 constexpr char const* certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
32 constexpr char const* objDeleteIntf = "xyz.openbmc_project.Object.Delete";
33 constexpr char const* certPropIntf = "xyz.openbmc_project.Certs.Certificate";
34 constexpr char const* dbusPropIntf = "org.freedesktop.DBus.Properties";
35 constexpr char const* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
36 constexpr char const* ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
37 constexpr char const* httpsServiceName =
38     "xyz.openbmc_project.Certs.Manager.Server.Https";
39 constexpr char const* ldapServiceName =
40     "xyz.openbmc_project.Certs.Manager.Client.Ldap";
41 constexpr char const* authorityServiceName =
42     "xyz.openbmc_project.Certs.Manager.Authority.Ldap";
43 constexpr char const* authorityObjectPath =
44     "/xyz/openbmc_project/certs/authority/ldap";
45 } // namespace certs
46 
47 /**
48  * The Certificate schema defines a Certificate Service which represents the
49  * actions available to manage certificates and links to where certificates
50  * are installed.
51  */
52 class CertificateService : public Node
53 {
54   public:
55     CertificateService(App& app) : Node(app, "/redfish/v1/CertificateService/")
56     {
57         // TODO: Issue#61 No entries are available for Certificate
58         // service 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&,
72                const std::vector<std::string>&) 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 inline 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 inline 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         std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C',
165                                             'e', 'r', 't', 's', '.', 'X',
166                                             'X', 'X', 'X', 'X', 'X', '\0'};
167         char* tempDirectory = mkdtemp(dirTemplate.data());
168         if (tempDirectory)
169         {
170             certDirectory = tempDirectory;
171             certificateFile = certDirectory / "cert.pem";
172             std::ofstream out(certificateFile, std::ofstream::out |
173                                                    std::ofstream::binary |
174                                                    std::ofstream::trunc);
175             out << certString;
176             out.close();
177             BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile;
178         }
179     }
180     ~CertificateFile()
181     {
182         if (std::filesystem::exists(certDirectory))
183         {
184             BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile;
185             std::error_code ec;
186             std::filesystem::remove_all(certDirectory, ec);
187             if (ec)
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(App& 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>&) override
267     {
268         static const int rsaKeyBitLength = 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 = rsaKeyBitLength;
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 != rsaKeyBitLength)
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 timeOut = 10;
420         if (csrMatcher)
421         {
422             messages::serviceTemporarilyUnavailable(asyncResp->res,
423                                                     std::to_string(timeOut));
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(timeOut));
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&) {
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 Certificate 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(App& 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>&) 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     HTTPSCertificate(App& app) :
815         Node(app,
816              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
817              "<str>/",
818              std::string())
819     {
820         entityPrivileges = {
821             {boost::beast::http::verb::get, {{"Login"}}},
822             {boost::beast::http::verb::head, {{"Login"}}},
823             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
824             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
825             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
826             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
827     }
828 
829     void doGet(crow::Response& res, const crow::Request& req,
830                const std::vector<std::string>& params) override
831     {
832         auto asyncResp = std::make_shared<AsyncResp>(res);
833         if (params.size() != 1)
834         {
835             messages::internalError(asyncResp->res);
836             return;
837         }
838         long id = getIDFromURL(req.url);
839 
840         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
841         std::string certURL =
842             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
843             std::to_string(id);
844         std::string objectPath = certs::httpsObjectPath;
845         objectPath += "/";
846         objectPath += std::to_string(id);
847         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
848                                  id, certURL, "HTTPS Certificate");
849     }
850 
851 }; // namespace redfish
852 
853 /**
854  * Collection of HTTPS certificates
855  */
856 class HTTPSCertificateCollection : public Node
857 {
858   public:
859     HTTPSCertificateCollection(App& app) :
860         Node(app,
861              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
862     {
863         entityPrivileges = {
864             {boost::beast::http::verb::get, {{"Login"}}},
865             {boost::beast::http::verb::head, {{"Login"}}},
866             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
867             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
868             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
869             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
870     }
871     void doGet(crow::Response& res, const crow::Request&,
872                const std::vector<std::string>&) override
873     {
874         res.jsonValue = {
875             {"@odata.id",
876              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
877             {"@odata.type", "#CertificateCollection.CertificateCollection"},
878             {"Name", "HTTPS Certificates Collection"},
879             {"Description", "A Collection of HTTPS certificate instances"}};
880         auto asyncResp = std::make_shared<AsyncResp>(res);
881         crow::connections::systemBus->async_method_call(
882             [asyncResp](const boost::system::error_code ec,
883                         const ManagedObjectType& certs) {
884                 if (ec)
885                 {
886                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
887                     messages::internalError(asyncResp->res);
888                     return;
889                 }
890                 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
891                 members = nlohmann::json::array();
892                 for (const auto& cert : certs)
893                 {
894                     long id = getIDFromURL(cert.first.str);
895                     if (id >= 0)
896                     {
897                         members.push_back(
898                             {{"@odata.id",
899                               "/redfish/v1/Managers/bmc/"
900                               "NetworkProtocol/HTTPS/Certificates/" +
901                                   std::to_string(id)}});
902                     }
903                 }
904                 asyncResp->res.jsonValue["Members@odata.count"] =
905                     members.size();
906             },
907             certs::httpsServiceName, certs::httpsObjectPath,
908             certs::dbusObjManagerIntf, "GetManagedObjects");
909     }
910 
911     void doPost(crow::Response& res, const crow::Request& req,
912                 const std::vector<std::string>&) override
913     {
914         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
915         auto asyncResp = std::make_shared<AsyncResp>(res);
916         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
917                                     {"Description", "HTTPS Certificate"}};
918 
919         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
920 
921         if (certFileBody.empty())
922         {
923             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
924             messages::unrecognizedRequestBody(asyncResp->res);
925             return;
926         }
927 
928         std::shared_ptr<CertificateFile> certFile =
929             std::make_shared<CertificateFile>(certFileBody);
930 
931         crow::connections::systemBus->async_method_call(
932             [asyncResp, certFile](const boost::system::error_code ec,
933                                   const std::string& objectPath) {
934                 if (ec)
935                 {
936                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
937                     messages::internalError(asyncResp->res);
938                     return;
939                 }
940                 long certId = getIDFromURL(objectPath);
941                 if (certId < 0)
942                 {
943                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
944                                      << objectPath;
945                     messages::internalError(asyncResp->res);
946                     return;
947                 }
948                 std::string certURL =
949                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
950                     "Certificates/" +
951                     std::to_string(certId);
952                 getCertificateProperties(asyncResp, objectPath,
953                                          certs::httpsServiceName, certId,
954                                          certURL, "HTTPS Certificate");
955                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
956                                  << certFile->getCertFilePath();
957             },
958             certs::httpsServiceName, certs::httpsObjectPath,
959             certs::certInstallIntf, "Install", certFile->getCertFilePath());
960     }
961 }; // HTTPSCertificateCollection
962 
963 /**
964  * The certificate location schema defines a resource that an administrator
965  * can use in order to locate all certificates installed on a given service.
966  */
967 class CertificateLocations : public Node
968 {
969   public:
970     CertificateLocations(App& app) :
971         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
972     {
973         entityPrivileges = {
974             {boost::beast::http::verb::get, {{"Login"}}},
975             {boost::beast::http::verb::head, {{"Login"}}},
976             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
977             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
978             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
979             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
980     }
981 
982   private:
983     void doGet(crow::Response& res, const crow::Request&,
984                const std::vector<std::string>&) override
985     {
986         res.jsonValue = {
987             {"@odata.id",
988              "/redfish/v1/CertificateService/CertificateLocations"},
989             {"@odata.type",
990              "#CertificateLocations.v1_0_0.CertificateLocations"},
991             {"Name", "Certificate Locations"},
992             {"Id", "CertificateLocations"},
993             {"Description",
994              "Defines a resource that an administrator can use in order to "
995              "locate all certificates installed on a given service"}};
996         auto asyncResp = std::make_shared<AsyncResp>(res);
997         nlohmann::json& links =
998             asyncResp->res.jsonValue["Links"]["Certificates"];
999         links = nlohmann::json::array();
1000         getCertificateLocations(
1001             asyncResp,
1002             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
1003             certs::httpsObjectPath, certs::httpsServiceName);
1004         getCertificateLocations(asyncResp,
1005                                 "/redfish/v1/AccountService/LDAP/Certificates/",
1006                                 certs::ldapObjectPath, certs::ldapServiceName);
1007         getCertificateLocations(
1008             asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/",
1009             certs::authorityObjectPath, certs::authorityServiceName);
1010     }
1011     /**
1012      * @brief Retrieve the certificates installed list and append to the
1013      * response
1014      *
1015      * @param[in] asyncResp Shared pointer to the response message
1016      * @param[in] certURL  Path of the certificate object
1017      * @param[in] path  Path of the D-Bus service object
1018      * @return None
1019      */
1020     void getCertificateLocations(std::shared_ptr<AsyncResp>& asyncResp,
1021                                  const std::string& certURL,
1022                                  const std::string& path,
1023                                  const std::string& service)
1024     {
1025         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
1026                          << " Path=" << path << " service= " << service;
1027         crow::connections::systemBus->async_method_call(
1028             [asyncResp, certURL](const boost::system::error_code ec,
1029                                  const ManagedObjectType& certs) {
1030                 if (ec)
1031                 {
1032                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1033                     messages::internalError(asyncResp->res);
1034                     return;
1035                 }
1036                 nlohmann::json& links =
1037                     asyncResp->res.jsonValue["Links"]["Certificates"];
1038                 for (auto& cert : certs)
1039                 {
1040                     long id = getIDFromURL(cert.first.str);
1041                     if (id >= 0)
1042                     {
1043                         links.push_back(
1044                             {{"@odata.id", certURL + std::to_string(id)}});
1045                     }
1046                 }
1047                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
1048                     links.size();
1049             },
1050             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
1051     }
1052 }; // CertificateLocations
1053 
1054 /**
1055  * Collection of LDAP certificates
1056  */
1057 class LDAPCertificateCollection : public Node
1058 {
1059   public:
1060     LDAPCertificateCollection(App& app) :
1061         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1062     {
1063         entityPrivileges = {
1064             {boost::beast::http::verb::get, {{"Login"}}},
1065             {boost::beast::http::verb::head, {{"Login"}}},
1066             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1067             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1068             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1069             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1070     }
1071     void doGet(crow::Response& res, const crow::Request&,
1072                const std::vector<std::string>&) override
1073     {
1074         res.jsonValue = {
1075             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
1076             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1077             {"Name", "LDAP Certificates Collection"},
1078             {"Description", "A Collection of LDAP certificate instances"}};
1079         auto asyncResp = std::make_shared<AsyncResp>(res);
1080         crow::connections::systemBus->async_method_call(
1081             [asyncResp](const boost::system::error_code ec,
1082                         const ManagedObjectType& certs) {
1083                 if (ec)
1084                 {
1085                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1086                     messages::internalError(asyncResp->res);
1087                     return;
1088                 }
1089                 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
1090                 members = nlohmann::json::array();
1091                 for (const auto& cert : certs)
1092                 {
1093                     long id = getIDFromURL(cert.first.str);
1094                     if (id >= 0)
1095                     {
1096                         members.push_back(
1097                             {{"@odata.id", "/redfish/v1/AccountService/"
1098                                            "LDAP/Certificates/" +
1099                                                std::to_string(id)}});
1100                     }
1101                 }
1102                 asyncResp->res.jsonValue["Members@odata.count"] =
1103                     members.size();
1104             },
1105             certs::ldapServiceName, certs::ldapObjectPath,
1106             certs::dbusObjManagerIntf, "GetManagedObjects");
1107     }
1108 
1109     void doPost(crow::Response& res, const crow::Request& req,
1110                 const std::vector<std::string>&) override
1111     {
1112         auto asyncResp = std::make_shared<AsyncResp>(res);
1113         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1114 
1115         if (certFileBody.empty())
1116         {
1117             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1118             messages::unrecognizedRequestBody(asyncResp->res);
1119             return;
1120         }
1121 
1122         std::shared_ptr<CertificateFile> certFile =
1123             std::make_shared<CertificateFile>(certFileBody);
1124 
1125         crow::connections::systemBus->async_method_call(
1126             [asyncResp, certFile](const boost::system::error_code ec,
1127                                   const std::string& objectPath) {
1128                 if (ec)
1129                 {
1130                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1131                     messages::internalError(asyncResp->res);
1132                     return;
1133                 }
1134                 long certId = getIDFromURL(objectPath);
1135                 if (certId < 0)
1136                 {
1137                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1138                                      << objectPath;
1139                     messages::internalError(asyncResp->res);
1140                     return;
1141                 }
1142                 std::string certURL =
1143                     "/redfish/v1/AccountService/LDAP/Certificates/" +
1144                     std::to_string(certId);
1145                 getCertificateProperties(asyncResp, objectPath,
1146                                          certs::ldapServiceName, certId,
1147                                          certURL, "LDAP Certificate");
1148                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1149                                  << certFile->getCertFilePath();
1150             },
1151             certs::ldapServiceName, certs::ldapObjectPath,
1152             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1153     }
1154 }; // LDAPCertificateCollection
1155 
1156 /**
1157  * Certificate resource describes a certificate used to prove the identity
1158  * of a component, account or service.
1159  */
1160 class LDAPCertificate : public Node
1161 {
1162   public:
1163     LDAPCertificate(App& app) :
1164         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
1165              std::string())
1166     {
1167         entityPrivileges = {
1168             {boost::beast::http::verb::get, {{"Login"}}},
1169             {boost::beast::http::verb::head, {{"Login"}}},
1170             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1171             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1172             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1173             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1174     }
1175 
1176     void doGet(crow::Response& res, const crow::Request& req,
1177                const std::vector<std::string>&) override
1178     {
1179         auto asyncResp = std::make_shared<AsyncResp>(res);
1180         long id = getIDFromURL(req.url);
1181         if (id < 0)
1182         {
1183             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1184             messages::internalError(asyncResp->res);
1185             return;
1186         }
1187         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1188         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1189                               std::to_string(id);
1190         std::string objectPath = certs::ldapObjectPath;
1191         objectPath += "/";
1192         objectPath += std::to_string(id);
1193         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1194                                  id, certURL, "LDAP Certificate");
1195     }
1196 }; // LDAPCertificate
1197 /**
1198  * Collection of TrustStoreCertificate certificates
1199  */
1200 class TrustStoreCertificateCollection : public Node
1201 {
1202   public:
1203     TrustStoreCertificateCollection(App& app) :
1204         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1205     {
1206         entityPrivileges = {
1207             {boost::beast::http::verb::get, {{"Login"}}},
1208             {boost::beast::http::verb::head, {{"Login"}}},
1209             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1210             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1211             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1212             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1213     }
1214     void doGet(crow::Response& res, const crow::Request&,
1215                const std::vector<std::string>&) override
1216     {
1217         res.jsonValue = {
1218             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates/"},
1219             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1220             {"Name", "TrustStore Certificates Collection"},
1221             {"Description",
1222              "A Collection of TrustStore certificate instances"}};
1223         auto asyncResp = std::make_shared<AsyncResp>(res);
1224         crow::connections::systemBus->async_method_call(
1225             [asyncResp](const boost::system::error_code ec,
1226                         const ManagedObjectType& certs) {
1227                 if (ec)
1228                 {
1229                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1230                     messages::internalError(asyncResp->res);
1231                     return;
1232                 }
1233                 nlohmann::json& members = asyncResp->res.jsonValue["Members"];
1234                 members = nlohmann::json::array();
1235                 for (const auto& cert : certs)
1236                 {
1237                     long id = getIDFromURL(cert.first.str);
1238                     if (id >= 0)
1239                     {
1240                         members.push_back(
1241                             {{"@odata.id", "/redfish/v1/Managers/bmc/"
1242                                            "Truststore/Certificates/" +
1243                                                std::to_string(id)}});
1244                     }
1245                 }
1246                 asyncResp->res.jsonValue["Members@odata.count"] =
1247                     members.size();
1248             },
1249             certs::authorityServiceName, certs::authorityObjectPath,
1250             certs::dbusObjManagerIntf, "GetManagedObjects");
1251     }
1252 
1253     void doPost(crow::Response& res, const crow::Request& req,
1254                 const std::vector<std::string>&) override
1255     {
1256         auto asyncResp = std::make_shared<AsyncResp>(res);
1257         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1258 
1259         if (certFileBody.empty())
1260         {
1261             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1262             messages::unrecognizedRequestBody(asyncResp->res);
1263             return;
1264         }
1265 
1266         std::shared_ptr<CertificateFile> certFile =
1267             std::make_shared<CertificateFile>(certFileBody);
1268         crow::connections::systemBus->async_method_call(
1269             [asyncResp, certFile](const boost::system::error_code ec,
1270                                   const std::string& objectPath) {
1271                 if (ec)
1272                 {
1273                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1274                     messages::internalError(asyncResp->res);
1275                     return;
1276                 }
1277                 long certId = getIDFromURL(objectPath);
1278                 if (certId < 0)
1279                 {
1280                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1281                                      << objectPath;
1282                     messages::internalError(asyncResp->res);
1283                     return;
1284                 }
1285                 std::string certURL = "/redfish/v1/Managers/bmc/"
1286                                       "Truststore/Certificates/" +
1287                                       std::to_string(certId);
1288 
1289                 getCertificateProperties(asyncResp, objectPath,
1290                                          certs::authorityServiceName, certId,
1291                                          certURL, "TrustStore Certificate");
1292                 BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1293                                  << certFile->getCertFilePath();
1294             },
1295             certs::authorityServiceName, certs::authorityObjectPath,
1296             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1297     }
1298 }; // TrustStoreCertificateCollection
1299 
1300 /**
1301  * Certificate resource describes a certificate used to prove the identity
1302  * of a component, account or service.
1303  */
1304 class TrustStoreCertificate : public Node
1305 {
1306   public:
1307     TrustStoreCertificate(App& app) :
1308         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/",
1309              std::string())
1310     {
1311         entityPrivileges = {
1312             {boost::beast::http::verb::get, {{"Login"}}},
1313             {boost::beast::http::verb::head, {{"Login"}}},
1314             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1315             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1316             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1317             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1318     }
1319 
1320     void doGet(crow::Response& res, const crow::Request& req,
1321                const std::vector<std::string>&) override
1322     {
1323         auto asyncResp = std::make_shared<AsyncResp>(res);
1324         long id = getIDFromURL(req.url);
1325         if (id < 0)
1326         {
1327             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1328             messages::internalError(asyncResp->res);
1329             return;
1330         }
1331         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1332                          << std::to_string(id);
1333         std::string certURL =
1334             "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1335             std::to_string(id);
1336         std::string objectPath = certs::authorityObjectPath;
1337         objectPath += "/";
1338         objectPath += std::to_string(id);
1339         getCertificateProperties(asyncResp, objectPath,
1340                                  certs::authorityServiceName, id, certURL,
1341                                  "TrustStore Certificate");
1342     }
1343 
1344     void doDelete(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 
1349         if (params.size() != 1)
1350         {
1351             messages::internalError(asyncResp->res);
1352             return;
1353         }
1354 
1355         long id = getIDFromURL(req.url);
1356         if (id < 0)
1357         {
1358             BMCWEB_LOG_ERROR << "Invalid url value: " << req.url;
1359             messages::resourceNotFound(asyncResp->res, "TrustStore Certificate",
1360                                        std::string(req.url));
1361             return;
1362         }
1363         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID="
1364                          << std::to_string(id);
1365         std::string certPath = certs::authorityObjectPath;
1366         certPath += "/";
1367         certPath += std::to_string(id);
1368 
1369         crow::connections::systemBus->async_method_call(
1370             [asyncResp, id](const boost::system::error_code ec) {
1371                 if (ec)
1372                 {
1373                     messages::resourceNotFound(asyncResp->res,
1374                                                "TrustStore Certificate",
1375                                                std::to_string(id));
1376                     return;
1377                 }
1378                 BMCWEB_LOG_INFO << "Certificate deleted";
1379                 asyncResp->res.result(boost::beast::http::status::no_content);
1380             },
1381             certs::authorityServiceName, certPath, certs::objDeleteIntf,
1382             "Delete");
1383     }
1384 }; // TrustStoreCertificate
1385 } // namespace redfish
1386