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