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