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                 timeout.cancel();
462                 if (m.is_method_error())
463                 {
464                     BMCWEB_LOG_ERROR << "Dbus method error!!!";
465                     messages::internalError(asyncResp->res);
466                     return;
467                 }
468                 std::vector<std::pair<
469                     std::string, std::vector<std::pair<
470                                      std::string, std::variant<std::string>>>>>
471                     interfacesProperties;
472                 sdbusplus::message::object_path csrObjectPath;
473                 m.read(csrObjectPath, interfacesProperties);
474                 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
475                 for (auto &interface : interfacesProperties)
476                 {
477                     if (interface.first == "xyz.openbmc_project.Certs.CSR")
478                     {
479                         getCSR(asyncResp, certURI, service, objectPath,
480                                csrObjectPath.str);
481                         break;
482                     }
483                 }
484             });
485         crow::connections::systemBus->async_method_call(
486             [asyncResp](const boost::system::error_code &ec,
487                         const std::string &path) {
488                 if (ec)
489                 {
490                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
491                     messages::internalError(asyncResp->res);
492                     return;
493                 }
494             },
495             service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
496             "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
497             commonName, *optContactPerson, country, *optEmail, *optGivenName,
498             *optInitials, *optKeyBitLength, *optKeyCurveId,
499             *optKeyPairAlgorithm, *optKeyUsage, organization,
500             organizationalUnit, state, *optSurname, *optUnstructuredName);
501     }
502 }; // CertificateActionGenerateCSR
503 
504 /**
505  * @brief Parse and update Certficate Issue/Subject property
506  *
507  * @param[in] asyncResp Shared pointer to the response message
508  * @param[in] str  Issuer/Subject value in key=value pairs
509  * @param[in] type Issuer/Subject
510  * @return None
511  */
512 static void updateCertIssuerOrSubject(nlohmann::json &out,
513                                       const std::string_view value)
514 {
515     // example: O=openbmc-project.xyz,CN=localhost
516     std::string_view::iterator i = value.begin();
517     while (i != value.end())
518     {
519         std::string_view::iterator tokenBegin = i;
520         while (i != value.end() && *i != '=')
521         {
522             i++;
523         }
524         if (i == value.end())
525         {
526             break;
527         }
528         const std::string_view key(tokenBegin,
529                                    static_cast<size_t>(i - tokenBegin));
530         i++;
531         tokenBegin = i;
532         while (i != value.end() && *i != ',')
533         {
534             i++;
535         }
536         const std::string_view val(tokenBegin,
537                                    static_cast<size_t>(i - tokenBegin));
538         if (key == "L")
539         {
540             out["City"] = val;
541         }
542         else if (key == "CN")
543         {
544             out["CommonName"] = val;
545         }
546         else if (key == "C")
547         {
548             out["Country"] = val;
549         }
550         else if (key == "O")
551         {
552             out["Organization"] = val;
553         }
554         else if (key == "OU")
555         {
556             out["OrganizationalUnit"] = val;
557         }
558         else if (key == "ST")
559         {
560             out["State"] = val;
561         }
562         // skip comma character
563         if (i != value.end())
564         {
565             i++;
566         }
567     }
568 }
569 
570 /**
571  * @brief Retrieve the certificates properties and append to the response
572  * message
573  *
574  * @param[in] asyncResp Shared pointer to the response message
575  * @param[in] objectPath  Path of the D-Bus service object
576  * @param[in] certId  Id of the certificate
577  * @param[in] certURL  URL of the certificate object
578  * @param[in] name  name of the certificate
579  * @return None
580  */
581 static void getCertificateProperties(
582     const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
583     const std::string &service, long certId, const std::string &certURL,
584     const std::string &name)
585 {
586     using PropertyType =
587         std::variant<std::string, uint64_t, std::vector<std::string>>;
588     using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
589     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
590                      << " certId=" << certId << " certURl=" << certURL;
591     crow::connections::systemBus->async_method_call(
592         [asyncResp, certURL, certId, name](const boost::system::error_code ec,
593                                            const PropertiesMap &properties) {
594             if (ec)
595             {
596                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
597                 messages::resourceNotFound(asyncResp->res, name,
598                                            std::to_string(certId));
599                 return;
600             }
601             asyncResp->res.jsonValue = {
602                 {"@odata.id", certURL},
603                 {"@odata.type", "#Certificate.v1_0_0.Certificate"},
604                 {"@odata.context",
605                  "/redfish/v1/$metadata#Certificate.Certificate"},
606                 {"Id", std::to_string(certId)},
607                 {"Name", name},
608                 {"Description", name}};
609             for (const auto &property : properties)
610             {
611                 if (property.first == "CertificateString")
612                 {
613                     asyncResp->res.jsonValue["CertificateString"] = "";
614                     const std::string *value =
615                         std::get_if<std::string>(&property.second);
616                     if (value)
617                     {
618                         asyncResp->res.jsonValue["CertificateString"] = *value;
619                     }
620                 }
621                 else if (property.first == "KeyUsage")
622                 {
623                     nlohmann::json &keyUsage =
624                         asyncResp->res.jsonValue["KeyUsage"];
625                     keyUsage = nlohmann::json::array();
626                     const std::vector<std::string> *value =
627                         std::get_if<std::vector<std::string>>(&property.second);
628                     if (value)
629                     {
630                         for (const std::string &usage : *value)
631                         {
632                             keyUsage.push_back(usage);
633                         }
634                     }
635                 }
636                 else if (property.first == "Issuer")
637                 {
638                     const std::string *value =
639                         std::get_if<std::string>(&property.second);
640                     if (value)
641                     {
642                         updateCertIssuerOrSubject(
643                             asyncResp->res.jsonValue["Issuer"], *value);
644                     }
645                 }
646                 else if (property.first == "Subject")
647                 {
648                     const std::string *value =
649                         std::get_if<std::string>(&property.second);
650                     if (value)
651                     {
652                         updateCertIssuerOrSubject(
653                             asyncResp->res.jsonValue["Subject"], *value);
654                     }
655                 }
656                 else if (property.first == "ValidNotAfter")
657                 {
658                     const uint64_t *value =
659                         std::get_if<uint64_t>(&property.second);
660                     if (value)
661                     {
662                         std::time_t time = static_cast<std::time_t>(*value);
663                         asyncResp->res.jsonValue["ValidNotAfter"] =
664                             crow::utility::getDateTime(time);
665                     }
666                 }
667                 else if (property.first == "ValidNotBefore")
668                 {
669                     const uint64_t *value =
670                         std::get_if<uint64_t>(&property.second);
671                     if (value)
672                     {
673                         std::time_t time = static_cast<std::time_t>(*value);
674                         asyncResp->res.jsonValue["ValidNotBefore"] =
675                             crow::utility::getDateTime(time);
676                     }
677                 }
678             }
679             asyncResp->res.addHeader("Location", certURL);
680         },
681         service, objectPath, certs::dbusPropIntf, "GetAll",
682         certs::certPropIntf);
683 }
684 
685 using GetObjectType =
686     std::vector<std::pair<std::string, std::vector<std::string>>>;
687 
688 /**
689  * Action to replace an existing certificate
690  */
691 class CertificateActionsReplaceCertificate : public Node
692 {
693   public:
694     CertificateActionsReplaceCertificate(CrowApp &app) :
695         Node(app, "/redfish/v1/CertificateService/Actions/"
696                   "CertificateService.ReplaceCertificate/")
697     {
698         entityPrivileges = {
699             {boost::beast::http::verb::get, {{"Login"}}},
700             {boost::beast::http::verb::head, {{"Login"}}},
701             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
702             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
703             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
704             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
705     }
706 
707   private:
708     void doPost(crow::Response &res, const crow::Request &req,
709                 const std::vector<std::string> &params) override
710     {
711         std::string certificate;
712         nlohmann::json certificateUri;
713         std::optional<std::string> certificateType = "PEM";
714         auto asyncResp = std::make_shared<AsyncResp>(res);
715         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
716                                  certificate, "CertificateUri", certificateUri,
717                                  "CertificateType", certificateType))
718         {
719             BMCWEB_LOG_ERROR << "Required parameters are missing";
720             messages::internalError(asyncResp->res);
721             return;
722         }
723 
724         if (!certificateType)
725         {
726             // should never happen, but it never hurts to be paranoid.
727             return;
728         }
729         if (certificateType != "PEM")
730         {
731             messages::actionParameterNotSupported(
732                 asyncResp->res, "CertificateType", "ReplaceCertificate");
733             return;
734         }
735 
736         std::string certURI;
737         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
738                                           "@odata.id", certURI))
739         {
740             messages::actionParameterMissing(
741                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
742             return;
743         }
744 
745         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
746         long id = getIDFromURL(certURI);
747         if (id < 0)
748         {
749             messages::actionParameterValueFormatError(asyncResp->res, certURI,
750                                                       "CertificateUri",
751                                                       "ReplaceCertificate");
752             return;
753         }
754         std::string objectPath;
755         std::string name;
756         std::string service;
757         if (boost::starts_with(
758                 certURI,
759                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
760         {
761             objectPath =
762                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
763             name = "HTTPS certificate";
764             service = certs::httpsServiceName;
765         }
766         else if (boost::starts_with(
767                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
768         {
769             objectPath =
770                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
771             name = "LDAP certificate";
772             service = certs::ldapServiceName;
773         }
774         else if (boost::starts_with(
775                      certURI,
776                      "/redfish/v1/Managers/bmc/Truststore/Certificates/"))
777         {
778             objectPath = std::string(certs::authorityObjectPath) + "/" +
779                          std::to_string(id);
780             name = "TrustStore certificate";
781             service = certs::authorityServiceName;
782         }
783         else
784         {
785             messages::actionParameterNotSupported(
786                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
787             return;
788         }
789 
790         std::shared_ptr<CertificateFile> certFile =
791             std::make_shared<CertificateFile>(certificate);
792         crow::connections::systemBus->async_method_call(
793             [asyncResp, certFile, objectPath, service, certURI, id,
794              name](const boost::system::error_code ec) {
795                 if (ec)
796                 {
797                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
798                     messages::resourceNotFound(asyncResp->res, name,
799                                                std::to_string(id));
800                     return;
801                 }
802                 getCertificateProperties(asyncResp, objectPath, service, id,
803                                          certURI, name);
804                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
805                                  << certFile->getCertFilePath();
806             },
807             service, objectPath, certs::certReplaceIntf, "Replace",
808             certFile->getCertFilePath());
809     }
810 }; // CertificateActionsReplaceCertificate
811 
812 /**
813  * Certificate resource describes a certificate used to prove the identity
814  * of a component, account or service.
815  */
816 class HTTPSCertificate : public Node
817 {
818   public:
819     template <typename CrowApp>
820     HTTPSCertificate(CrowApp &app) :
821         Node(app,
822              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
823              "<str>/",
824              std::string())
825     {
826         entityPrivileges = {
827             {boost::beast::http::verb::get, {{"Login"}}},
828             {boost::beast::http::verb::head, {{"Login"}}},
829             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
830             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
831             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
832             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
833     }
834 
835     void doGet(crow::Response &res, const crow::Request &req,
836                const std::vector<std::string> &params) override
837     {
838         auto asyncResp = std::make_shared<AsyncResp>(res);
839         if (params.size() != 1)
840         {
841             messages::internalError(asyncResp->res);
842             return;
843         }
844         long id = getIDFromURL(req.url);
845 
846         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
847         std::string certURL =
848             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
849             std::to_string(id);
850         std::string objectPath = certs::httpsObjectPath;
851         objectPath += "/";
852         objectPath += std::to_string(id);
853         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
854                                  id, certURL, "HTTPS Certificate");
855     }
856 
857 }; // namespace redfish
858 
859 /**
860  * Collection of HTTPS certificates
861  */
862 class HTTPSCertificateCollection : public Node
863 {
864   public:
865     template <typename CrowApp>
866     HTTPSCertificateCollection(CrowApp &app) :
867         Node(app,
868              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
869     {
870         entityPrivileges = {
871             {boost::beast::http::verb::get, {{"Login"}}},
872             {boost::beast::http::verb::head, {{"Login"}}},
873             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
874             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
875             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
876             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
877     }
878     void doGet(crow::Response &res, const crow::Request &req,
879                const std::vector<std::string> &params) override
880     {
881         res.jsonValue = {
882             {"@odata.id",
883              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
884             {"@odata.type", "#CertificateCollection.CertificateCollection"},
885             {"@odata.context",
886              "/redfish/v1/"
887              "$metadata#CertificateCollection.CertificateCollection"},
888             {"Name", "HTTPS Certificates Collection"},
889             {"Description", "A Collection of HTTPS certificate instances"}};
890         auto asyncResp = std::make_shared<AsyncResp>(res);
891         crow::connections::systemBus->async_method_call(
892             [asyncResp](const boost::system::error_code ec,
893                         const ManagedObjectType &certs) {
894                 if (ec)
895                 {
896                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
897                     messages::internalError(asyncResp->res);
898                     return;
899                 }
900                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
901                 members = nlohmann::json::array();
902                 for (const auto &cert : certs)
903                 {
904                     long id = getIDFromURL(cert.first.str);
905                     if (id >= 0)
906                     {
907                         members.push_back(
908                             {{"@odata.id",
909                               "/redfish/v1/Managers/bmc/"
910                               "NetworkProtocol/HTTPS/Certificates/" +
911                                   std::to_string(id)}});
912                     }
913                 }
914                 asyncResp->res.jsonValue["Members@odata.count"] =
915                     members.size();
916             },
917             certs::httpsServiceName, certs::httpsObjectPath,
918             certs::dbusObjManagerIntf, "GetManagedObjects");
919     }
920 
921     void doPost(crow::Response &res, const crow::Request &req,
922                 const std::vector<std::string> &params) override
923     {
924         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
925         auto asyncResp = std::make_shared<AsyncResp>(res);
926         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
927                                     {"Description", "HTTPS Certificate"}};
928 
929         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
930 
931         if (certFileBody.empty())
932         {
933             return;
934         }
935 
936         std::shared_ptr<CertificateFile> certFile =
937             std::make_shared<CertificateFile>(certFileBody);
938 
939         crow::connections::systemBus->async_method_call(
940             [asyncResp, certFile](const boost::system::error_code ec,
941                                   const std::string &objectPath) {
942                 if (ec)
943                 {
944                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
945                     messages::internalError(asyncResp->res);
946                     return;
947                 }
948                 long certId = getIDFromURL(objectPath);
949                 if (certId < 0)
950                 {
951                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
952                                      << objectPath;
953                     messages::internalError(asyncResp->res);
954                     return;
955                 }
956                 std::string certURL =
957                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
958                     "Certificates/" +
959                     std::to_string(certId);
960                 getCertificateProperties(asyncResp, objectPath,
961                                          certs::httpsServiceName, certId,
962                                          certURL, "HTTPS Certificate");
963                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
964                                  << certFile->getCertFilePath();
965             },
966             certs::httpsServiceName, certs::httpsObjectPath,
967             certs::certInstallIntf, "Install", certFile->getCertFilePath());
968     }
969 }; // HTTPSCertificateCollection
970 
971 /**
972  * The certificate location schema defines a resource that an administrator
973  * can use in order to locate all certificates installed on a given service.
974  */
975 class CertificateLocations : public Node
976 {
977   public:
978     template <typename CrowApp>
979     CertificateLocations(CrowApp &app) :
980         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
981     {
982         entityPrivileges = {
983             {boost::beast::http::verb::get, {{"Login"}}},
984             {boost::beast::http::verb::head, {{"Login"}}},
985             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
986             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
987             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
988             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
989     }
990 
991   private:
992     void doGet(crow::Response &res, const crow::Request &req,
993                const std::vector<std::string> &params) override
994     {
995         res.jsonValue = {
996             {"@odata.id",
997              "/redfish/v1/CertificateService/CertificateLocations"},
998             {"@odata.type",
999              "#CertificateLocations.v1_0_0.CertificateLocations"},
1000             {"@odata.context",
1001              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
1002             {"Name", "Certificate Locations"},
1003             {"Id", "CertificateLocations"},
1004             {"Description",
1005              "Defines a resource that an administrator can use in order to "
1006              "locate all certificates installed on a given service"}};
1007         auto asyncResp = std::make_shared<AsyncResp>(res);
1008         nlohmann::json &links =
1009             asyncResp->res.jsonValue["Links"]["Certificates"];
1010         links = nlohmann::json::array();
1011         getCertificateLocations(
1012             asyncResp,
1013             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
1014             certs::httpsObjectPath, certs::httpsServiceName);
1015         getCertificateLocations(asyncResp,
1016                                 "/redfish/v1/AccountService/LDAP/Certificates/",
1017                                 certs::ldapObjectPath, certs::ldapServiceName);
1018         getCertificateLocations(
1019             asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/",
1020             certs::authorityObjectPath, certs::authorityServiceName);
1021     }
1022     /**
1023      * @brief Retrieve the certificates installed list and append to the
1024      * response
1025      *
1026      * @param[in] asyncResp Shared pointer to the response message
1027      * @param[in] certURL  Path of the certificate object
1028      * @param[in] path  Path of the D-Bus service object
1029      * @return None
1030      */
1031     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
1032                                  const std::string &certURL,
1033                                  const std::string &path,
1034                                  const std::string &service)
1035     {
1036         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
1037                          << " Path=" << path << " service= " << service;
1038         crow::connections::systemBus->async_method_call(
1039             [asyncResp, certURL](const boost::system::error_code ec,
1040                                  const ManagedObjectType &certs) {
1041                 if (ec)
1042                 {
1043                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1044                     messages::internalError(asyncResp->res);
1045                     return;
1046                 }
1047                 nlohmann::json &links =
1048                     asyncResp->res.jsonValue["Links"]["Certificates"];
1049                 for (auto &cert : certs)
1050                 {
1051                     long id = getIDFromURL(cert.first.str);
1052                     if (id >= 0)
1053                     {
1054                         links.push_back(
1055                             {{"@odata.id", certURL + std::to_string(id)}});
1056                     }
1057                 }
1058                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
1059                     links.size();
1060             },
1061             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
1062     }
1063 }; // CertificateLocations
1064 
1065 /**
1066  * Collection of LDAP certificates
1067  */
1068 class LDAPCertificateCollection : public Node
1069 {
1070   public:
1071     template <typename CrowApp>
1072     LDAPCertificateCollection(CrowApp &app) :
1073         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1074     {
1075         entityPrivileges = {
1076             {boost::beast::http::verb::get, {{"Login"}}},
1077             {boost::beast::http::verb::head, {{"Login"}}},
1078             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1079             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1080             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1081             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1082     }
1083     void doGet(crow::Response &res, const crow::Request &req,
1084                const std::vector<std::string> &params) override
1085     {
1086         res.jsonValue = {
1087             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
1088             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1089             {"@odata.context",
1090              "/redfish/v1/"
1091              "$metadata#CertificateCollection.CertificateCollection"},
1092             {"Name", "LDAP Certificates Collection"},
1093             {"Description", "A Collection of LDAP certificate instances"}};
1094         auto asyncResp = std::make_shared<AsyncResp>(res);
1095         crow::connections::systemBus->async_method_call(
1096             [asyncResp](const boost::system::error_code ec,
1097                         const ManagedObjectType &certs) {
1098                 if (ec)
1099                 {
1100                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1101                     messages::internalError(asyncResp->res);
1102                     return;
1103                 }
1104                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1105                 members = nlohmann::json::array();
1106                 for (const auto &cert : certs)
1107                 {
1108                     long id = getIDFromURL(cert.first.str);
1109                     if (id >= 0)
1110                     {
1111                         members.push_back(
1112                             {{"@odata.id", "/redfish/v1/AccountService/"
1113                                            "LDAP/Certificates/" +
1114                                                std::to_string(id)}});
1115                     }
1116                 }
1117                 asyncResp->res.jsonValue["Members@odata.count"] =
1118                     members.size();
1119             },
1120             certs::ldapServiceName, certs::ldapObjectPath,
1121             certs::dbusObjManagerIntf, "GetManagedObjects");
1122     }
1123 
1124     void doPost(crow::Response &res, const crow::Request &req,
1125                 const std::vector<std::string> &params) override
1126     {
1127         auto asyncResp = std::make_shared<AsyncResp>(res);
1128         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1129 
1130         if (certFileBody.empty())
1131         {
1132             return;
1133         }
1134 
1135         std::shared_ptr<CertificateFile> certFile =
1136             std::make_shared<CertificateFile>(certFileBody);
1137 
1138         crow::connections::systemBus->async_method_call(
1139             [asyncResp, certFile](const boost::system::error_code ec,
1140                                   const std::string &objectPath) {
1141                 if (ec)
1142                 {
1143                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1144                     messages::internalError(asyncResp->res);
1145                     return;
1146                 }
1147                 long certId = getIDFromURL(objectPath);
1148                 if (certId < 0)
1149                 {
1150                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1151                                      << objectPath;
1152                     messages::internalError(asyncResp->res);
1153                     return;
1154                 }
1155                 std::string certURL =
1156                     "/redfish/v1/AccountService/LDAP/Certificates/" +
1157                     std::to_string(certId);
1158                 getCertificateProperties(asyncResp, objectPath,
1159                                          certs::ldapServiceName, certId,
1160                                          certURL, "LDAP Certificate");
1161                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1162                                  << certFile->getCertFilePath();
1163             },
1164             certs::ldapServiceName, certs::ldapObjectPath,
1165             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1166     }
1167 }; // LDAPCertificateCollection
1168 
1169 /**
1170  * Certificate resource describes a certificate used to prove the identity
1171  * of a component, account or service.
1172  */
1173 class LDAPCertificate : public Node
1174 {
1175   public:
1176     template <typename CrowApp>
1177     LDAPCertificate(CrowApp &app) :
1178         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
1179              std::string())
1180     {
1181         entityPrivileges = {
1182             {boost::beast::http::verb::get, {{"Login"}}},
1183             {boost::beast::http::verb::head, {{"Login"}}},
1184             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1185             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1186             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1187             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1188     }
1189 
1190     void doGet(crow::Response &res, const crow::Request &req,
1191                const std::vector<std::string> &params) override
1192     {
1193         auto asyncResp = std::make_shared<AsyncResp>(res);
1194         long id = getIDFromURL(req.url);
1195         if (id < 0)
1196         {
1197             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1198             messages::internalError(asyncResp->res);
1199             return;
1200         }
1201         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1202         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1203                               std::to_string(id);
1204         std::string objectPath = certs::ldapObjectPath;
1205         objectPath += "/";
1206         objectPath += std::to_string(id);
1207         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1208                                  id, certURL, "LDAP Certificate");
1209     }
1210 }; // LDAPCertificate
1211 /**
1212  * Collection of TrustStoreCertificate certificates
1213  */
1214 class TrustStoreCertificateCollection : public Node
1215 {
1216   public:
1217     template <typename CrowApp>
1218     TrustStoreCertificateCollection(CrowApp &app) :
1219         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1220     {
1221         entityPrivileges = {
1222             {boost::beast::http::verb::get, {{"Login"}}},
1223             {boost::beast::http::verb::head, {{"Login"}}},
1224             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1225             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1226             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1227             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1228     }
1229     void doGet(crow::Response &res, const crow::Request &req,
1230                const std::vector<std::string> &params) override
1231     {
1232         res.jsonValue = {
1233             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates/"},
1234             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1235             {"@odata.context",
1236              "/redfish/v1/"
1237              "$metadata#CertificateCollection.CertificateCollection"},
1238             {"Name", "TrustStore Certificates Collection"},
1239             {"Description",
1240              "A Collection of TrustStore certificate instances"}};
1241         auto asyncResp = std::make_shared<AsyncResp>(res);
1242         crow::connections::systemBus->async_method_call(
1243             [asyncResp](const boost::system::error_code ec,
1244                         const ManagedObjectType &certs) {
1245                 if (ec)
1246                 {
1247                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1248                     messages::internalError(asyncResp->res);
1249                     return;
1250                 }
1251                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1252                 members = nlohmann::json::array();
1253                 for (const auto &cert : certs)
1254                 {
1255                     long id = getIDFromURL(cert.first.str);
1256                     if (id >= 0)
1257                     {
1258                         members.push_back(
1259                             {{"@odata.id", "/redfish/v1/Managers/bmc/"
1260                                            "Truststore/Certificates/" +
1261                                                std::to_string(id)}});
1262                     }
1263                 }
1264                 asyncResp->res.jsonValue["Members@odata.count"] =
1265                     members.size();
1266             },
1267             certs::authorityServiceName, certs::authorityObjectPath,
1268             certs::dbusObjManagerIntf, "GetManagedObjects");
1269     }
1270 
1271     void doPost(crow::Response &res, const crow::Request &req,
1272                 const std::vector<std::string> &params) override
1273     {
1274         std::shared_ptr<CertificateFile> certFile =
1275             std::make_shared<CertificateFile>(req.body);
1276         auto asyncResp = std::make_shared<AsyncResp>(res);
1277         crow::connections::systemBus->async_method_call(
1278             [asyncResp, certFile](const boost::system::error_code ec,
1279                                   const std::string &objectPath) {
1280                 if (ec)
1281                 {
1282                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1283                     messages::internalError(asyncResp->res);
1284                     return;
1285                 }
1286                 long certId = getIDFromURL(objectPath);
1287                 if (certId < 0)
1288                 {
1289                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1290                                      << objectPath;
1291                     messages::internalError(asyncResp->res);
1292                     return;
1293                 }
1294                 std::string certURL = "/redfish/v1/Managers/bmc/"
1295                                       "Truststore/Certificates/" +
1296                                       std::to_string(certId);
1297 
1298                 getCertificateProperties(asyncResp, objectPath,
1299                                          certs::authorityServiceName, certId,
1300                                          certURL, "TrustStore Certificate");
1301                 BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1302                                  << certFile->getCertFilePath();
1303             },
1304             certs::authorityServiceName, certs::authorityObjectPath,
1305             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1306     }
1307 }; // TrustStoreCertificateCollection
1308 
1309 /**
1310  * Certificate resource describes a certificate used to prove the identity
1311  * of a component, account or service.
1312  */
1313 class TrustStoreCertificate : public Node
1314 {
1315   public:
1316     template <typename CrowApp>
1317     TrustStoreCertificate(CrowApp &app) :
1318         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/",
1319              std::string())
1320     {
1321         entityPrivileges = {
1322             {boost::beast::http::verb::get, {{"Login"}}},
1323             {boost::beast::http::verb::head, {{"Login"}}},
1324             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1325             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1326             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1327             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1328     }
1329 
1330     void doGet(crow::Response &res, const crow::Request &req,
1331                const std::vector<std::string> &params) override
1332     {
1333         auto asyncResp = std::make_shared<AsyncResp>(res);
1334         long id = getIDFromURL(req.url);
1335         if (id < 0)
1336         {
1337             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1338             messages::internalError(asyncResp->res);
1339             return;
1340         }
1341         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1342                          << std::to_string(id);
1343         std::string certURL =
1344             "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1345             std::to_string(id);
1346         std::string objectPath = certs::authorityObjectPath;
1347         objectPath += "/";
1348         objectPath += std::to_string(id);
1349         getCertificateProperties(asyncResp, objectPath,
1350                                  certs::authorityServiceName, id, certURL,
1351                                  "TrustStore Certificate");
1352     }
1353 }; // TrustStoreCertificate
1354 } // namespace redfish
1355