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