xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 271584ab78b4c1926f766aa26ddfde7da329059f)
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                 if (ec)
942                 {
943                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
944                     messages::internalError(asyncResp->res);
945                     return;
946                 }
947                 // TODO: Issue#84 supporting only 1 certificate
948                 long certId = 1;
949                 std::string certURL =
950                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
951                     "Certificates/" +
952                     std::to_string(certId);
953                 std::string objectPath = std::string(certs::httpsObjectPath) +
954                                          "/" + std::to_string(certId);
955                 getCertificateProperties(asyncResp, objectPath,
956                                          certs::httpsServiceName, certId,
957                                          certURL, "HTTPS Certificate");
958                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
959                                  << certFile->getCertFilePath();
960             },
961             certs::httpsServiceName, certs::httpsObjectPath,
962             certs::certInstallIntf, "Install", certFile->getCertFilePath());
963     }
964 }; // HTTPSCertificateCollection
965 
966 /**
967  * The certificate location schema defines a resource that an administrator
968  * can use in order to locate all certificates installed on a given service.
969  */
970 class CertificateLocations : public Node
971 {
972   public:
973     template <typename CrowApp>
974     CertificateLocations(CrowApp &app) :
975         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
976     {
977         entityPrivileges = {
978             {boost::beast::http::verb::get, {{"Login"}}},
979             {boost::beast::http::verb::head, {{"Login"}}},
980             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
981             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
982             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
983             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
984     }
985 
986   private:
987     void doGet(crow::Response &res, const crow::Request &req,
988                const std::vector<std::string> &params) override
989     {
990         res.jsonValue = {
991             {"@odata.id",
992              "/redfish/v1/CertificateService/CertificateLocations"},
993             {"@odata.type",
994              "#CertificateLocations.v1_0_0.CertificateLocations"},
995             {"@odata.context",
996              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
997             {"Name", "Certificate Locations"},
998             {"Id", "CertificateLocations"},
999             {"Description",
1000              "Defines a resource that an administrator can use in order to "
1001              "locate all certificates installed on a given service"}};
1002         auto asyncResp = std::make_shared<AsyncResp>(res);
1003         nlohmann::json &links =
1004             asyncResp->res.jsonValue["Links"]["Certificates"];
1005         links = nlohmann::json::array();
1006         getCertificateLocations(
1007             asyncResp,
1008             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
1009             certs::httpsObjectPath, certs::httpsServiceName);
1010         getCertificateLocations(asyncResp,
1011                                 "/redfish/v1/AccountService/LDAP/Certificates/",
1012                                 certs::ldapObjectPath, certs::ldapServiceName);
1013         getCertificateLocations(
1014             asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/",
1015             certs::authorityObjectPath, certs::authorityServiceName);
1016     }
1017     /**
1018      * @brief Retrieve the certificates installed list and append to the
1019      * response
1020      *
1021      * @param[in] asyncResp Shared pointer to the response message
1022      * @param[in] certURL  Path of the certificate object
1023      * @param[in] path  Path of the D-Bus service object
1024      * @return None
1025      */
1026     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
1027                                  const std::string &certURL,
1028                                  const std::string &path,
1029                                  const std::string &service)
1030     {
1031         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
1032                          << " Path=" << path << " service= " << service;
1033         crow::connections::systemBus->async_method_call(
1034             [asyncResp, certURL](const boost::system::error_code ec,
1035                                  const ManagedObjectType &certs) {
1036                 if (ec)
1037                 {
1038                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1039                     messages::internalError(asyncResp->res);
1040                     return;
1041                 }
1042                 nlohmann::json &links =
1043                     asyncResp->res.jsonValue["Links"]["Certificates"];
1044                 for (auto &cert : certs)
1045                 {
1046                     long id = getIDFromURL(cert.first.str);
1047                     if (id >= 0)
1048                     {
1049                         links.push_back(
1050                             {{"@odata.id", certURL + std::to_string(id)}});
1051                     }
1052                 }
1053                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
1054                     links.size();
1055             },
1056             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
1057     }
1058 }; // CertificateLocations
1059 
1060 /**
1061  * Collection of LDAP certificates
1062  */
1063 class LDAPCertificateCollection : public Node
1064 {
1065   public:
1066     template <typename CrowApp>
1067     LDAPCertificateCollection(CrowApp &app) :
1068         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1069     {
1070         entityPrivileges = {
1071             {boost::beast::http::verb::get, {{"Login"}}},
1072             {boost::beast::http::verb::head, {{"Login"}}},
1073             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1074             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1075             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1076             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1077     }
1078     void doGet(crow::Response &res, const crow::Request &req,
1079                const std::vector<std::string> &params) override
1080     {
1081         res.jsonValue = {
1082             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
1083             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1084             {"@odata.context",
1085              "/redfish/v1/"
1086              "$metadata#CertificateCollection.CertificateCollection"},
1087             {"Name", "LDAP Certificates Collection"},
1088             {"Description", "A Collection of LDAP certificate instances"}};
1089         auto asyncResp = std::make_shared<AsyncResp>(res);
1090         crow::connections::systemBus->async_method_call(
1091             [asyncResp](const boost::system::error_code ec,
1092                         const ManagedObjectType &certs) {
1093                 if (ec)
1094                 {
1095                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1096                     messages::internalError(asyncResp->res);
1097                     return;
1098                 }
1099                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1100                 members = nlohmann::json::array();
1101                 for (const auto &cert : certs)
1102                 {
1103                     long id = getIDFromURL(cert.first.str);
1104                     if (id >= 0)
1105                     {
1106                         members.push_back(
1107                             {{"@odata.id", "/redfish/v1/AccountService/"
1108                                            "LDAP/Certificates/" +
1109                                                std::to_string(id)}});
1110                     }
1111                 }
1112                 asyncResp->res.jsonValue["Members@odata.count"] =
1113                     members.size();
1114             },
1115             certs::ldapServiceName, certs::ldapObjectPath,
1116             certs::dbusObjManagerIntf, "GetManagedObjects");
1117     }
1118 
1119     void doPost(crow::Response &res, const crow::Request &req,
1120                 const std::vector<std::string> &params) override
1121     {
1122         auto asyncResp = std::make_shared<AsyncResp>(res);
1123         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1124 
1125         if (certFileBody.empty())
1126         {
1127             return;
1128         }
1129 
1130         std::shared_ptr<CertificateFile> certFile =
1131             std::make_shared<CertificateFile>(certFileBody);
1132 
1133         crow::connections::systemBus->async_method_call(
1134             [asyncResp, certFile](const boost::system::error_code ec) {
1135                 if (ec)
1136                 {
1137                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1138                     messages::internalError(asyncResp->res);
1139                     return;
1140                 }
1141                 //// TODO: Issue#84 supporting only 1 certificate
1142                 long certId = 1;
1143                 std::string certURL =
1144                     "/redfish/v1/AccountService/LDAP/Certificates/" +
1145                     std::to_string(certId);
1146                 std::string objectPath = std::string(certs::ldapObjectPath) +
1147                                          "/" + std::to_string(certId);
1148                 getCertificateProperties(asyncResp, objectPath,
1149                                          certs::ldapServiceName, certId,
1150                                          certURL, "LDAP Certificate");
1151                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1152                                  << certFile->getCertFilePath();
1153             },
1154             certs::ldapServiceName, certs::ldapObjectPath,
1155             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1156     }
1157 }; // LDAPCertificateCollection
1158 
1159 /**
1160  * Certificate resource describes a certificate used to prove the identity
1161  * of a component, account or service.
1162  */
1163 class LDAPCertificate : public Node
1164 {
1165   public:
1166     template <typename CrowApp>
1167     LDAPCertificate(CrowApp &app) :
1168         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
1169              std::string())
1170     {
1171         entityPrivileges = {
1172             {boost::beast::http::verb::get, {{"Login"}}},
1173             {boost::beast::http::verb::head, {{"Login"}}},
1174             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1175             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1176             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1177             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1178     }
1179 
1180     void doGet(crow::Response &res, const crow::Request &req,
1181                const std::vector<std::string> &params) override
1182     {
1183         auto asyncResp = std::make_shared<AsyncResp>(res);
1184         long id = getIDFromURL(req.url);
1185         if (id < 0)
1186         {
1187             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1188             messages::internalError(asyncResp->res);
1189             return;
1190         }
1191         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1192         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1193                               std::to_string(id);
1194         std::string objectPath = certs::ldapObjectPath;
1195         objectPath += "/";
1196         objectPath += std::to_string(id);
1197         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1198                                  id, certURL, "LDAP Certificate");
1199     }
1200 }; // LDAPCertificate
1201 /**
1202  * Collection of TrustStoreCertificate certificates
1203  */
1204 class TrustStoreCertificateCollection : public Node
1205 {
1206   public:
1207     template <typename CrowApp>
1208     TrustStoreCertificateCollection(CrowApp &app) :
1209         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1210     {
1211         entityPrivileges = {
1212             {boost::beast::http::verb::get, {{"Login"}}},
1213             {boost::beast::http::verb::head, {{"Login"}}},
1214             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1215             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1216             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1217             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1218     }
1219     void doGet(crow::Response &res, const crow::Request &req,
1220                const std::vector<std::string> &params) override
1221     {
1222         res.jsonValue = {
1223             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates/"},
1224             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1225             {"@odata.context",
1226              "/redfish/v1/"
1227              "$metadata#CertificateCollection.CertificateCollection"},
1228             {"Name", "TrustStore Certificates Collection"},
1229             {"Description",
1230              "A Collection of TrustStore certificate instances"}};
1231         auto asyncResp = std::make_shared<AsyncResp>(res);
1232         crow::connections::systemBus->async_method_call(
1233             [asyncResp](const boost::system::error_code ec,
1234                         const ManagedObjectType &certs) {
1235                 if (ec)
1236                 {
1237                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1238                     messages::internalError(asyncResp->res);
1239                     return;
1240                 }
1241                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1242                 members = nlohmann::json::array();
1243                 for (const auto &cert : certs)
1244                 {
1245                     long id = getIDFromURL(cert.first.str);
1246                     if (id >= 0)
1247                     {
1248                         members.push_back(
1249                             {{"@odata.id", "/redfish/v1/Managers/bmc/"
1250                                            "Truststore/Certificates/" +
1251                                                std::to_string(id)}});
1252                     }
1253                 }
1254                 asyncResp->res.jsonValue["Members@odata.count"] =
1255                     members.size();
1256             },
1257             certs::authorityServiceName, certs::authorityObjectPath,
1258             certs::dbusObjManagerIntf, "GetManagedObjects");
1259     }
1260 
1261     void doPost(crow::Response &res, const crow::Request &req,
1262                 const std::vector<std::string> &params) override
1263     {
1264         std::shared_ptr<CertificateFile> certFile =
1265             std::make_shared<CertificateFile>(req.body);
1266         auto asyncResp = std::make_shared<AsyncResp>(res);
1267         crow::connections::systemBus->async_method_call(
1268             [asyncResp, certFile](const boost::system::error_code ec) {
1269                 if (ec)
1270                 {
1271                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1272                     messages::internalError(asyncResp->res);
1273                     return;
1274                 }
1275                 //// TODO: Issue#84 supporting only 1 certificate
1276                 long certId = 1;
1277                 std::string certURL = "/redfish/v1/Managers/bmc/"
1278                                       "Truststore/Certificates/" +
1279                                       std::to_string(certId);
1280                 std::string objectPath =
1281                     std::string(certs::authorityObjectPath) + "/" +
1282                     std::to_string(certId);
1283                 getCertificateProperties(asyncResp, objectPath,
1284                                          certs::authorityServiceName, certId,
1285                                          certURL, "TrustStore Certificate");
1286                 BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1287                                  << certFile->getCertFilePath();
1288             },
1289             certs::authorityServiceName, certs::authorityObjectPath,
1290             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1291     }
1292 }; // TrustStoreCertificateCollection
1293 
1294 /**
1295  * Certificate resource describes a certificate used to prove the identity
1296  * of a component, account or service.
1297  */
1298 class TrustStoreCertificate : public Node
1299 {
1300   public:
1301     template <typename CrowApp>
1302     TrustStoreCertificate(CrowApp &app) :
1303         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/",
1304              std::string())
1305     {
1306         entityPrivileges = {
1307             {boost::beast::http::verb::get, {{"Login"}}},
1308             {boost::beast::http::verb::head, {{"Login"}}},
1309             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1310             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1311             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1312             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1313     }
1314 
1315     void doGet(crow::Response &res, const crow::Request &req,
1316                const std::vector<std::string> &params) override
1317     {
1318         auto asyncResp = std::make_shared<AsyncResp>(res);
1319         long id = getIDFromURL(req.url);
1320         if (id < 0)
1321         {
1322             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1323             messages::internalError(asyncResp->res);
1324             return;
1325         }
1326         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1327                          << std::to_string(id);
1328         std::string certURL =
1329             "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1330             std::to_string(id);
1331         std::string objectPath = certs::authorityObjectPath;
1332         objectPath += "/";
1333         objectPath += std::to_string(id);
1334         getCertificateProperties(asyncResp, objectPath,
1335                                  certs::authorityServiceName, id, certURL,
1336                                  "TrustStore Certificate");
1337     }
1338 }; // TrustStoreCertificate
1339 } // namespace redfish
1340