1 /*
2 // Copyright (c) 2018 IBM Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <boost/convert.hpp>
21 #include <boost/convert/strtol.hpp>
22 #include <variant>
23 namespace redfish
24 {
25 namespace certs
26 {
27 constexpr char const *httpsObjectPath =
28     "/xyz/openbmc_project/certs/server/https";
29 constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install";
30 constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
31 constexpr char const *objDeleteIntf = "xyz.openbmc_project.Object.Delete";
32 constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate";
33 constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties";
34 constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
35 constexpr char const *ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
36 constexpr char const *httpsServiceName =
37     "xyz.openbmc_project.Certs.Manager.Server.Https";
38 constexpr char const *ldapServiceName =
39     "xyz.openbmc_project.Certs.Manager.Client.Ldap";
40 constexpr char const *authorityServiceName =
41     "xyz.openbmc_project.Certs.Manager.Authority.Ldap";
42 constexpr char const *authorityObjectPath =
43     "/xyz/openbmc_project/certs/authority/ldap";
44 } // namespace certs
45 
46 /**
47  * The Certificate schema defines a Certificate Service which represents the
48  * actions available to manage certificates and links to where certificates
49  * are installed.
50  */
51 class CertificateService : public Node
52 {
53   public:
54     CertificateService(CrowApp &app) :
55         Node(app, "/redfish/v1/CertificateService/")
56     {
57         // TODO: Issue#61 No entries are available for Certificate
58         // sevice at https://www.dmtf.org/standards/redfish
59         // "redfish standard registries". Need to modify after DMTF
60         // publish Privilege details for certificate service
61         entityPrivileges = {
62             {boost::beast::http::verb::get, {{"Login"}}},
63             {boost::beast::http::verb::head, {{"Login"}}},
64             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
65             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
66             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
67             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
68     }
69 
70   private:
71     void doGet(crow::Response &res, const crow::Request &req,
72                const std::vector<std::string> &params) override
73     {
74         res.jsonValue = {
75             {"@odata.type", "#CertificateService.v1_0_0.CertificateService"},
76             {"@odata.id", "/redfish/v1/CertificateService"},
77             {"@odata.context",
78              "/redfish/v1/$metadata#CertificateService.CertificateService"},
79             {"Id", "CertificateService"},
80             {"Name", "Certificate Service"},
81             {"Description", "Actions available to manage certificates"}};
82         res.jsonValue["CertificateLocations"] = {
83             {"@odata.id",
84              "/redfish/v1/CertificateService/CertificateLocations"}};
85         res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = {
86             {"target", "/redfish/v1/CertificateService/Actions/"
87                        "CertificateService.ReplaceCertificate"},
88             {"CertificateType@Redfish.AllowableValues", {"PEM"}}};
89         res.jsonValue["Actions"]["#CertificateService.GenerateCSR"] = {
90             {"target", "/redfish/v1/CertificateService/Actions/"
91                        "CertificateService.GenerateCSR"}};
92         res.end();
93     }
94 }; // CertificateService
95 
96 /**
97  * @brief Find the ID specified in the URL
98  * Finds the numbers specified after the last "/" in the URL and returns.
99  * @param[in] path URL
100  * @return -1 on failure and number on success
101  */
102 long getIDFromURL(const std::string_view url)
103 {
104     std::size_t found = url.rfind("/");
105     if (found == std::string::npos)
106     {
107         return -1;
108     }
109 
110     if ((found + 1) < url.length())
111     {
112         std::string_view str = url.substr(found + 1);
113 
114         return boost::convert<long>(str, boost::cnv::strtol()).value_or(-1);
115     }
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             messages::serviceTemporarilyUnavailable(asyncResp->res,
425                                                     std::to_string(TIME_OUT));
426             return;
427         }
428 
429         // Make this static so it survives outside this method
430         static boost::asio::steady_timer timeout(*req.ioService);
431         timeout.expires_after(std::chrono::seconds(TIME_OUT));
432         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
433             csrMatcher = nullptr;
434             if (ec)
435             {
436                 // operation_aborted is expected if timer is canceled before
437                 // completion.
438                 if (ec != boost::asio::error::operation_aborted)
439                 {
440                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
441                 }
442                 return;
443             }
444             BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
445             messages::internalError(asyncResp->res);
446         });
447 
448         // create a matcher to wait on CSR object
449         BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
450         std::string match("type='signal',"
451                           "interface='org.freedesktop.DBus.ObjectManager',"
452                           "path='" +
453                           objectPath +
454                           "',"
455                           "member='InterfacesAdded'");
456         csrMatcher = std::make_unique<sdbusplus::bus::match::match>(
457             *crow::connections::systemBus, match,
458             [asyncResp, service, objectPath,
459              certURI](sdbusplus::message::message &m) {
460                 timeout.cancel();
461                 if (m.is_method_error())
462                 {
463                     BMCWEB_LOG_ERROR << "Dbus method error!!!";
464                     messages::internalError(asyncResp->res);
465                     return;
466                 }
467                 std::vector<std::pair<
468                     std::string, std::vector<std::pair<
469                                      std::string, std::variant<std::string>>>>>
470                     interfacesProperties;
471                 sdbusplus::message::object_path csrObjectPath;
472                 m.read(csrObjectPath, interfacesProperties);
473                 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
474                 for (auto &interface : interfacesProperties)
475                 {
476                     if (interface.first == "xyz.openbmc_project.Certs.CSR")
477                     {
478                         getCSR(asyncResp, certURI, service, objectPath,
479                                csrObjectPath.str);
480                         break;
481                     }
482                 }
483             });
484         crow::connections::systemBus->async_method_call(
485             [asyncResp](const boost::system::error_code &ec,
486                         const std::string &path) {
487                 if (ec)
488                 {
489                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
490                     messages::internalError(asyncResp->res);
491                     return;
492                 }
493             },
494             service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
495             "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
496             commonName, *optContactPerson, country, *optEmail, *optGivenName,
497             *optInitials, *optKeyBitLength, *optKeyCurveId,
498             *optKeyPairAlgorithm, *optKeyUsage, organization,
499             organizationalUnit, state, *optSurname, *optUnstructuredName);
500     }
501 }; // CertificateActionGenerateCSR
502 
503 /**
504  * @brief Parse and update Certficate Issue/Subject property
505  *
506  * @param[in] asyncResp Shared pointer to the response message
507  * @param[in] str  Issuer/Subject value in key=value pairs
508  * @param[in] type Issuer/Subject
509  * @return None
510  */
511 static void updateCertIssuerOrSubject(nlohmann::json &out,
512                                       const std::string_view value)
513 {
514     // example: O=openbmc-project.xyz,CN=localhost
515     std::string_view::iterator i = value.begin();
516     while (i != value.end())
517     {
518         std::string_view::iterator tokenBegin = i;
519         while (i != value.end() && *i != '=')
520         {
521             i++;
522         }
523         if (i == value.end())
524         {
525             break;
526         }
527         const std::string_view key(tokenBegin,
528                                    static_cast<size_t>(i - tokenBegin));
529         i++;
530         tokenBegin = i;
531         while (i != value.end() && *i != ',')
532         {
533             i++;
534         }
535         const std::string_view val(tokenBegin,
536                                    static_cast<size_t>(i - tokenBegin));
537         if (key == "L")
538         {
539             out["City"] = val;
540         }
541         else if (key == "CN")
542         {
543             out["CommonName"] = val;
544         }
545         else if (key == "C")
546         {
547             out["Country"] = val;
548         }
549         else if (key == "O")
550         {
551             out["Organization"] = val;
552         }
553         else if (key == "OU")
554         {
555             out["OrganizationalUnit"] = val;
556         }
557         else if (key == "ST")
558         {
559             out["State"] = val;
560         }
561         // skip comma character
562         if (i != value.end())
563         {
564             i++;
565         }
566     }
567 }
568 
569 /**
570  * @brief Retrieve the certificates properties and append to the response
571  * message
572  *
573  * @param[in] asyncResp Shared pointer to the response message
574  * @param[in] objectPath  Path of the D-Bus service object
575  * @param[in] certId  Id of the certificate
576  * @param[in] certURL  URL of the certificate object
577  * @param[in] name  name of the certificate
578  * @return None
579  */
580 static void getCertificateProperties(
581     const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
582     const std::string &service, long certId, const std::string &certURL,
583     const std::string &name)
584 {
585     using PropertyType =
586         std::variant<std::string, uint64_t, std::vector<std::string>>;
587     using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
588     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
589                      << " certId=" << certId << " certURl=" << certURL;
590     crow::connections::systemBus->async_method_call(
591         [asyncResp, certURL, certId, name](const boost::system::error_code ec,
592                                            const PropertiesMap &properties) {
593             if (ec)
594             {
595                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
596                 messages::resourceNotFound(asyncResp->res, name,
597                                            std::to_string(certId));
598                 return;
599             }
600             asyncResp->res.jsonValue = {
601                 {"@odata.id", certURL},
602                 {"@odata.type", "#Certificate.v1_0_0.Certificate"},
603                 {"@odata.context",
604                  "/redfish/v1/$metadata#Certificate.Certificate"},
605                 {"Id", std::to_string(certId)},
606                 {"Name", name},
607                 {"Description", name}};
608             for (const auto &property : properties)
609             {
610                 if (property.first == "CertificateString")
611                 {
612                     asyncResp->res.jsonValue["CertificateString"] = "";
613                     const std::string *value =
614                         std::get_if<std::string>(&property.second);
615                     if (value)
616                     {
617                         asyncResp->res.jsonValue["CertificateString"] = *value;
618                     }
619                 }
620                 else if (property.first == "KeyUsage")
621                 {
622                     nlohmann::json &keyUsage =
623                         asyncResp->res.jsonValue["KeyUsage"];
624                     keyUsage = nlohmann::json::array();
625                     const std::vector<std::string> *value =
626                         std::get_if<std::vector<std::string>>(&property.second);
627                     if (value)
628                     {
629                         for (const std::string &usage : *value)
630                         {
631                             keyUsage.push_back(usage);
632                         }
633                     }
634                 }
635                 else if (property.first == "Issuer")
636                 {
637                     const std::string *value =
638                         std::get_if<std::string>(&property.second);
639                     if (value)
640                     {
641                         updateCertIssuerOrSubject(
642                             asyncResp->res.jsonValue["Issuer"], *value);
643                     }
644                 }
645                 else if (property.first == "Subject")
646                 {
647                     const std::string *value =
648                         std::get_if<std::string>(&property.second);
649                     if (value)
650                     {
651                         updateCertIssuerOrSubject(
652                             asyncResp->res.jsonValue["Subject"], *value);
653                     }
654                 }
655                 else if (property.first == "ValidNotAfter")
656                 {
657                     const uint64_t *value =
658                         std::get_if<uint64_t>(&property.second);
659                     if (value)
660                     {
661                         std::time_t time = static_cast<std::time_t>(*value);
662                         asyncResp->res.jsonValue["ValidNotAfter"] =
663                             crow::utility::getDateTime(time);
664                     }
665                 }
666                 else if (property.first == "ValidNotBefore")
667                 {
668                     const uint64_t *value =
669                         std::get_if<uint64_t>(&property.second);
670                     if (value)
671                     {
672                         std::time_t time = static_cast<std::time_t>(*value);
673                         asyncResp->res.jsonValue["ValidNotBefore"] =
674                             crow::utility::getDateTime(time);
675                     }
676                 }
677             }
678             asyncResp->res.addHeader("Location", certURL);
679         },
680         service, objectPath, certs::dbusPropIntf, "GetAll",
681         certs::certPropIntf);
682 }
683 
684 using GetObjectType =
685     std::vector<std::pair<std::string, std::vector<std::string>>>;
686 
687 /**
688  * Action to replace an existing certificate
689  */
690 class CertificateActionsReplaceCertificate : public Node
691 {
692   public:
693     CertificateActionsReplaceCertificate(CrowApp &app) :
694         Node(app, "/redfish/v1/CertificateService/Actions/"
695                   "CertificateService.ReplaceCertificate/")
696     {
697         entityPrivileges = {
698             {boost::beast::http::verb::get, {{"Login"}}},
699             {boost::beast::http::verb::head, {{"Login"}}},
700             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
701             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
702             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
703             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
704     }
705 
706   private:
707     void doPost(crow::Response &res, const crow::Request &req,
708                 const std::vector<std::string> &params) override
709     {
710         std::string certificate;
711         nlohmann::json certificateUri;
712         std::optional<std::string> certificateType = "PEM";
713         auto asyncResp = std::make_shared<AsyncResp>(res);
714         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
715                                  certificate, "CertificateUri", certificateUri,
716                                  "CertificateType", certificateType))
717         {
718             BMCWEB_LOG_ERROR << "Required parameters are missing";
719             messages::internalError(asyncResp->res);
720             return;
721         }
722 
723         if (!certificateType)
724         {
725             // should never happen, but it never hurts to be paranoid.
726             return;
727         }
728         if (certificateType != "PEM")
729         {
730             messages::actionParameterNotSupported(
731                 asyncResp->res, "CertificateType", "ReplaceCertificate");
732             return;
733         }
734 
735         std::string certURI;
736         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
737                                           "@odata.id", certURI))
738         {
739             messages::actionParameterMissing(
740                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
741             return;
742         }
743 
744         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
745         long id = getIDFromURL(certURI);
746         if (id < 0)
747         {
748             messages::actionParameterValueFormatError(asyncResp->res, certURI,
749                                                       "CertificateUri",
750                                                       "ReplaceCertificate");
751             return;
752         }
753         std::string objectPath;
754         std::string name;
755         std::string service;
756         if (boost::starts_with(
757                 certURI,
758                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
759         {
760             objectPath =
761                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
762             name = "HTTPS certificate";
763             service = certs::httpsServiceName;
764         }
765         else if (boost::starts_with(
766                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
767         {
768             objectPath =
769                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
770             name = "LDAP certificate";
771             service = certs::ldapServiceName;
772         }
773         else if (boost::starts_with(
774                      certURI,
775                      "/redfish/v1/Managers/bmc/Truststore/Certificates/"))
776         {
777             objectPath = std::string(certs::authorityObjectPath) + "/" +
778                          std::to_string(id);
779             name = "TrustStore certificate";
780             service = certs::authorityServiceName;
781         }
782         else
783         {
784             messages::actionParameterNotSupported(
785                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
786             return;
787         }
788 
789         std::shared_ptr<CertificateFile> certFile =
790             std::make_shared<CertificateFile>(certificate);
791         crow::connections::systemBus->async_method_call(
792             [asyncResp, certFile, objectPath, service, certURI, id,
793              name](const boost::system::error_code ec) {
794                 if (ec)
795                 {
796                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
797                     messages::resourceNotFound(asyncResp->res, name,
798                                                std::to_string(id));
799                     return;
800                 }
801                 getCertificateProperties(asyncResp, objectPath, service, id,
802                                          certURI, name);
803                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
804                                  << certFile->getCertFilePath();
805             },
806             service, objectPath, certs::certReplaceIntf, "Replace",
807             certFile->getCertFilePath());
808     }
809 }; // CertificateActionsReplaceCertificate
810 
811 /**
812  * Certificate resource describes a certificate used to prove the identity
813  * of a component, account or service.
814  */
815 class HTTPSCertificate : public Node
816 {
817   public:
818     template <typename CrowApp>
819     HTTPSCertificate(CrowApp &app) :
820         Node(app,
821              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
822              "<str>/",
823              std::string())
824     {
825         entityPrivileges = {
826             {boost::beast::http::verb::get, {{"Login"}}},
827             {boost::beast::http::verb::head, {{"Login"}}},
828             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
829             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
830             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
831             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
832     }
833 
834     void doGet(crow::Response &res, const crow::Request &req,
835                const std::vector<std::string> &params) override
836     {
837         auto asyncResp = std::make_shared<AsyncResp>(res);
838         if (params.size() != 1)
839         {
840             messages::internalError(asyncResp->res);
841             return;
842         }
843         long id = getIDFromURL(req.url);
844 
845         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
846         std::string certURL =
847             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
848             std::to_string(id);
849         std::string objectPath = certs::httpsObjectPath;
850         objectPath += "/";
851         objectPath += std::to_string(id);
852         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
853                                  id, certURL, "HTTPS Certificate");
854     }
855 
856 }; // namespace redfish
857 
858 /**
859  * Collection of HTTPS certificates
860  */
861 class HTTPSCertificateCollection : public Node
862 {
863   public:
864     template <typename CrowApp>
865     HTTPSCertificateCollection(CrowApp &app) :
866         Node(app,
867              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
868     {
869         entityPrivileges = {
870             {boost::beast::http::verb::get, {{"Login"}}},
871             {boost::beast::http::verb::head, {{"Login"}}},
872             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
873             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
874             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
875             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
876     }
877     void doGet(crow::Response &res, const crow::Request &req,
878                const std::vector<std::string> &params) override
879     {
880         res.jsonValue = {
881             {"@odata.id",
882              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
883             {"@odata.type", "#CertificateCollection.CertificateCollection"},
884             {"@odata.context",
885              "/redfish/v1/"
886              "$metadata#CertificateCollection.CertificateCollection"},
887             {"Name", "HTTPS Certificates Collection"},
888             {"Description", "A Collection of HTTPS certificate instances"}};
889         auto asyncResp = std::make_shared<AsyncResp>(res);
890         crow::connections::systemBus->async_method_call(
891             [asyncResp](const boost::system::error_code ec,
892                         const ManagedObjectType &certs) {
893                 if (ec)
894                 {
895                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
896                     messages::internalError(asyncResp->res);
897                     return;
898                 }
899                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
900                 members = nlohmann::json::array();
901                 for (const auto &cert : certs)
902                 {
903                     long id = getIDFromURL(cert.first.str);
904                     if (id >= 0)
905                     {
906                         members.push_back(
907                             {{"@odata.id",
908                               "/redfish/v1/Managers/bmc/"
909                               "NetworkProtocol/HTTPS/Certificates/" +
910                                   std::to_string(id)}});
911                     }
912                 }
913                 asyncResp->res.jsonValue["Members@odata.count"] =
914                     members.size();
915             },
916             certs::httpsServiceName, certs::httpsObjectPath,
917             certs::dbusObjManagerIntf, "GetManagedObjects");
918     }
919 
920     void doPost(crow::Response &res, const crow::Request &req,
921                 const std::vector<std::string> &params) override
922     {
923         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
924         auto asyncResp = std::make_shared<AsyncResp>(res);
925         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
926                                     {"Description", "HTTPS Certificate"}};
927 
928         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
929 
930         if (certFileBody.empty())
931         {
932             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
933             messages::unrecognizedRequestBody(asyncResp->res);
934             return;
935         }
936 
937         std::shared_ptr<CertificateFile> certFile =
938             std::make_shared<CertificateFile>(certFileBody);
939 
940         crow::connections::systemBus->async_method_call(
941             [asyncResp, certFile](const boost::system::error_code ec,
942                                   const std::string &objectPath) {
943                 if (ec)
944                 {
945                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
946                     messages::internalError(asyncResp->res);
947                     return;
948                 }
949                 long certId = getIDFromURL(objectPath);
950                 if (certId < 0)
951                 {
952                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
953                                      << objectPath;
954                     messages::internalError(asyncResp->res);
955                     return;
956                 }
957                 std::string certURL =
958                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
959                     "Certificates/" +
960                     std::to_string(certId);
961                 getCertificateProperties(asyncResp, objectPath,
962                                          certs::httpsServiceName, certId,
963                                          certURL, "HTTPS Certificate");
964                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
965                                  << certFile->getCertFilePath();
966             },
967             certs::httpsServiceName, certs::httpsObjectPath,
968             certs::certInstallIntf, "Install", certFile->getCertFilePath());
969     }
970 }; // HTTPSCertificateCollection
971 
972 /**
973  * The certificate location schema defines a resource that an administrator
974  * can use in order to locate all certificates installed on a given service.
975  */
976 class CertificateLocations : public Node
977 {
978   public:
979     template <typename CrowApp>
980     CertificateLocations(CrowApp &app) :
981         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
982     {
983         entityPrivileges = {
984             {boost::beast::http::verb::get, {{"Login"}}},
985             {boost::beast::http::verb::head, {{"Login"}}},
986             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
987             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
988             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
989             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
990     }
991 
992   private:
993     void doGet(crow::Response &res, const crow::Request &req,
994                const std::vector<std::string> &params) override
995     {
996         res.jsonValue = {
997             {"@odata.id",
998              "/redfish/v1/CertificateService/CertificateLocations"},
999             {"@odata.type",
1000              "#CertificateLocations.v1_0_0.CertificateLocations"},
1001             {"@odata.context",
1002              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
1003             {"Name", "Certificate Locations"},
1004             {"Id", "CertificateLocations"},
1005             {"Description",
1006              "Defines a resource that an administrator can use in order to "
1007              "locate all certificates installed on a given service"}};
1008         auto asyncResp = std::make_shared<AsyncResp>(res);
1009         nlohmann::json &links =
1010             asyncResp->res.jsonValue["Links"]["Certificates"];
1011         links = nlohmann::json::array();
1012         getCertificateLocations(
1013             asyncResp,
1014             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
1015             certs::httpsObjectPath, certs::httpsServiceName);
1016         getCertificateLocations(asyncResp,
1017                                 "/redfish/v1/AccountService/LDAP/Certificates/",
1018                                 certs::ldapObjectPath, certs::ldapServiceName);
1019         getCertificateLocations(
1020             asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/",
1021             certs::authorityObjectPath, certs::authorityServiceName);
1022     }
1023     /**
1024      * @brief Retrieve the certificates installed list and append to the
1025      * response
1026      *
1027      * @param[in] asyncResp Shared pointer to the response message
1028      * @param[in] certURL  Path of the certificate object
1029      * @param[in] path  Path of the D-Bus service object
1030      * @return None
1031      */
1032     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
1033                                  const std::string &certURL,
1034                                  const std::string &path,
1035                                  const std::string &service)
1036     {
1037         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
1038                          << " Path=" << path << " service= " << service;
1039         crow::connections::systemBus->async_method_call(
1040             [asyncResp, certURL](const boost::system::error_code ec,
1041                                  const ManagedObjectType &certs) {
1042                 if (ec)
1043                 {
1044                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1045                     messages::internalError(asyncResp->res);
1046                     return;
1047                 }
1048                 nlohmann::json &links =
1049                     asyncResp->res.jsonValue["Links"]["Certificates"];
1050                 for (auto &cert : certs)
1051                 {
1052                     long id = getIDFromURL(cert.first.str);
1053                     if (id >= 0)
1054                     {
1055                         links.push_back(
1056                             {{"@odata.id", certURL + std::to_string(id)}});
1057                     }
1058                 }
1059                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
1060                     links.size();
1061             },
1062             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
1063     }
1064 }; // CertificateLocations
1065 
1066 /**
1067  * Collection of LDAP certificates
1068  */
1069 class LDAPCertificateCollection : public Node
1070 {
1071   public:
1072     template <typename CrowApp>
1073     LDAPCertificateCollection(CrowApp &app) :
1074         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1075     {
1076         entityPrivileges = {
1077             {boost::beast::http::verb::get, {{"Login"}}},
1078             {boost::beast::http::verb::head, {{"Login"}}},
1079             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1080             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1081             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1082             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1083     }
1084     void doGet(crow::Response &res, const crow::Request &req,
1085                const std::vector<std::string> &params) override
1086     {
1087         res.jsonValue = {
1088             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
1089             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1090             {"@odata.context",
1091              "/redfish/v1/"
1092              "$metadata#CertificateCollection.CertificateCollection"},
1093             {"Name", "LDAP Certificates Collection"},
1094             {"Description", "A Collection of LDAP certificate instances"}};
1095         auto asyncResp = std::make_shared<AsyncResp>(res);
1096         crow::connections::systemBus->async_method_call(
1097             [asyncResp](const boost::system::error_code ec,
1098                         const ManagedObjectType &certs) {
1099                 if (ec)
1100                 {
1101                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1102                     messages::internalError(asyncResp->res);
1103                     return;
1104                 }
1105                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1106                 members = nlohmann::json::array();
1107                 for (const auto &cert : certs)
1108                 {
1109                     long id = getIDFromURL(cert.first.str);
1110                     if (id >= 0)
1111                     {
1112                         members.push_back(
1113                             {{"@odata.id", "/redfish/v1/AccountService/"
1114                                            "LDAP/Certificates/" +
1115                                                std::to_string(id)}});
1116                     }
1117                 }
1118                 asyncResp->res.jsonValue["Members@odata.count"] =
1119                     members.size();
1120             },
1121             certs::ldapServiceName, certs::ldapObjectPath,
1122             certs::dbusObjManagerIntf, "GetManagedObjects");
1123     }
1124 
1125     void doPost(crow::Response &res, const crow::Request &req,
1126                 const std::vector<std::string> &params) override
1127     {
1128         auto asyncResp = std::make_shared<AsyncResp>(res);
1129         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1130 
1131         if (certFileBody.empty())
1132         {
1133             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1134             messages::unrecognizedRequestBody(asyncResp->res);
1135             return;
1136         }
1137 
1138         std::shared_ptr<CertificateFile> certFile =
1139             std::make_shared<CertificateFile>(certFileBody);
1140 
1141         crow::connections::systemBus->async_method_call(
1142             [asyncResp, certFile](const boost::system::error_code ec,
1143                                   const std::string &objectPath) {
1144                 if (ec)
1145                 {
1146                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1147                     messages::internalError(asyncResp->res);
1148                     return;
1149                 }
1150                 long certId = getIDFromURL(objectPath);
1151                 if (certId < 0)
1152                 {
1153                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1154                                      << objectPath;
1155                     messages::internalError(asyncResp->res);
1156                     return;
1157                 }
1158                 std::string certURL =
1159                     "/redfish/v1/AccountService/LDAP/Certificates/" +
1160                     std::to_string(certId);
1161                 getCertificateProperties(asyncResp, objectPath,
1162                                          certs::ldapServiceName, certId,
1163                                          certURL, "LDAP Certificate");
1164                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1165                                  << certFile->getCertFilePath();
1166             },
1167             certs::ldapServiceName, certs::ldapObjectPath,
1168             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1169     }
1170 }; // LDAPCertificateCollection
1171 
1172 /**
1173  * Certificate resource describes a certificate used to prove the identity
1174  * of a component, account or service.
1175  */
1176 class LDAPCertificate : public Node
1177 {
1178   public:
1179     template <typename CrowApp>
1180     LDAPCertificate(CrowApp &app) :
1181         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
1182              std::string())
1183     {
1184         entityPrivileges = {
1185             {boost::beast::http::verb::get, {{"Login"}}},
1186             {boost::beast::http::verb::head, {{"Login"}}},
1187             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1188             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1189             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1190             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1191     }
1192 
1193     void doGet(crow::Response &res, const crow::Request &req,
1194                const std::vector<std::string> &params) override
1195     {
1196         auto asyncResp = std::make_shared<AsyncResp>(res);
1197         long id = getIDFromURL(req.url);
1198         if (id < 0)
1199         {
1200             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1201             messages::internalError(asyncResp->res);
1202             return;
1203         }
1204         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1205         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1206                               std::to_string(id);
1207         std::string objectPath = certs::ldapObjectPath;
1208         objectPath += "/";
1209         objectPath += std::to_string(id);
1210         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1211                                  id, certURL, "LDAP Certificate");
1212     }
1213 }; // LDAPCertificate
1214 /**
1215  * Collection of TrustStoreCertificate certificates
1216  */
1217 class TrustStoreCertificateCollection : public Node
1218 {
1219   public:
1220     template <typename CrowApp>
1221     TrustStoreCertificateCollection(CrowApp &app) :
1222         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1223     {
1224         entityPrivileges = {
1225             {boost::beast::http::verb::get, {{"Login"}}},
1226             {boost::beast::http::verb::head, {{"Login"}}},
1227             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1228             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1229             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1230             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1231     }
1232     void doGet(crow::Response &res, const crow::Request &req,
1233                const std::vector<std::string> &params) override
1234     {
1235         res.jsonValue = {
1236             {"@odata.id", "/redfish/v1/Managers/bmc/Truststore/Certificates/"},
1237             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1238             {"@odata.context",
1239              "/redfish/v1/"
1240              "$metadata#CertificateCollection.CertificateCollection"},
1241             {"Name", "TrustStore Certificates Collection"},
1242             {"Description",
1243              "A Collection of TrustStore certificate instances"}};
1244         auto asyncResp = std::make_shared<AsyncResp>(res);
1245         crow::connections::systemBus->async_method_call(
1246             [asyncResp](const boost::system::error_code ec,
1247                         const ManagedObjectType &certs) {
1248                 if (ec)
1249                 {
1250                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1251                     messages::internalError(asyncResp->res);
1252                     return;
1253                 }
1254                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1255                 members = nlohmann::json::array();
1256                 for (const auto &cert : certs)
1257                 {
1258                     long id = getIDFromURL(cert.first.str);
1259                     if (id >= 0)
1260                     {
1261                         members.push_back(
1262                             {{"@odata.id", "/redfish/v1/Managers/bmc/"
1263                                            "Truststore/Certificates/" +
1264                                                std::to_string(id)}});
1265                     }
1266                 }
1267                 asyncResp->res.jsonValue["Members@odata.count"] =
1268                     members.size();
1269             },
1270             certs::authorityServiceName, certs::authorityObjectPath,
1271             certs::dbusObjManagerIntf, "GetManagedObjects");
1272     }
1273 
1274     void doPost(crow::Response &res, const crow::Request &req,
1275                 const std::vector<std::string> &params) override
1276     {
1277         auto asyncResp = std::make_shared<AsyncResp>(res);
1278         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1279 
1280         if (certFileBody.empty())
1281         {
1282             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1283             messages::unrecognizedRequestBody(asyncResp->res);
1284             return;
1285         }
1286 
1287         std::shared_ptr<CertificateFile> certFile =
1288             std::make_shared<CertificateFile>(certFileBody);
1289         crow::connections::systemBus->async_method_call(
1290             [asyncResp, certFile](const boost::system::error_code ec,
1291                                   const std::string &objectPath) {
1292                 if (ec)
1293                 {
1294                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1295                     messages::internalError(asyncResp->res);
1296                     return;
1297                 }
1298                 long certId = getIDFromURL(objectPath);
1299                 if (certId < 0)
1300                 {
1301                     BMCWEB_LOG_ERROR << "Invalid objectPath value"
1302                                      << objectPath;
1303                     messages::internalError(asyncResp->res);
1304                     return;
1305                 }
1306                 std::string certURL = "/redfish/v1/Managers/bmc/"
1307                                       "Truststore/Certificates/" +
1308                                       std::to_string(certId);
1309 
1310                 getCertificateProperties(asyncResp, objectPath,
1311                                          certs::authorityServiceName, certId,
1312                                          certURL, "TrustStore Certificate");
1313                 BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1314                                  << certFile->getCertFilePath();
1315             },
1316             certs::authorityServiceName, certs::authorityObjectPath,
1317             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1318     }
1319 }; // TrustStoreCertificateCollection
1320 
1321 /**
1322  * Certificate resource describes a certificate used to prove the identity
1323  * of a component, account or service.
1324  */
1325 class TrustStoreCertificate : public Node
1326 {
1327   public:
1328     template <typename CrowApp>
1329     TrustStoreCertificate(CrowApp &app) :
1330         Node(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/",
1331              std::string())
1332     {
1333         entityPrivileges = {
1334             {boost::beast::http::verb::get, {{"Login"}}},
1335             {boost::beast::http::verb::head, {{"Login"}}},
1336             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1337             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1338             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1339             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1340     }
1341 
1342     void doGet(crow::Response &res, const crow::Request &req,
1343                const std::vector<std::string> &params) override
1344     {
1345         auto asyncResp = std::make_shared<AsyncResp>(res);
1346         long id = getIDFromURL(req.url);
1347         if (id < 0)
1348         {
1349             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1350             messages::internalError(asyncResp->res);
1351             return;
1352         }
1353         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1354                          << std::to_string(id);
1355         std::string certURL =
1356             "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1357             std::to_string(id);
1358         std::string objectPath = certs::authorityObjectPath;
1359         objectPath += "/";
1360         objectPath += std::to_string(id);
1361         getCertificateProperties(asyncResp, objectPath,
1362                                  certs::authorityServiceName, id, certURL,
1363                                  "TrustStore Certificate");
1364     }
1365 
1366     void doDelete(crow::Response &res, const crow::Request &req,
1367                   const std::vector<std::string> &params) override
1368     {
1369         auto asyncResp = std::make_shared<AsyncResp>(res);
1370 
1371         if (params.size() != 1)
1372         {
1373             messages::internalError(asyncResp->res);
1374             return;
1375         }
1376 
1377         long id = getIDFromURL(req.url);
1378         if (id < 0)
1379         {
1380             BMCWEB_LOG_ERROR << "Invalid url value: " << req.url;
1381             messages::resourceNotFound(asyncResp->res, "TrustStore Certificate",
1382                                        std::string(req.url));
1383             return;
1384         }
1385         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID="
1386                          << std::to_string(id);
1387         std::string certPath = certs::authorityObjectPath;
1388         certPath += "/";
1389         certPath += std::to_string(id);
1390 
1391         crow::connections::systemBus->async_method_call(
1392             [asyncResp, id](const boost::system::error_code ec) {
1393                 if (ec)
1394                 {
1395                     messages::resourceNotFound(asyncResp->res,
1396                                                "TrustStore Certificate",
1397                                                std::to_string(id));
1398                     return;
1399                 }
1400                 BMCWEB_LOG_INFO << "Certificate deleted";
1401                 asyncResp->res.result(boost::beast::http::status::no_content);
1402             },
1403             certs::authorityServiceName, certPath, certs::objDeleteIntf,
1404             "Delete");
1405     }
1406 }; // TrustStoreCertificate
1407 } // namespace redfish
1408