xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 3021581655861c74e8c0c71ce1ca49bb7b54f72d)
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
302         {
303             messages::actionParameterNotSupported(
304                 asyncResp->res, "CertificateCollection", "GenerateCSR");
305             return;
306         }
307 
308         // supporting only EC and RSA algorithm
309         if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
310         {
311             messages::actionParameterNotSupported(
312                 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
313             return;
314         }
315 
316         // supporting only 2048 key bit length for RSA algorithm due to time
317         // consumed in generating private key
318         if (*optKeyPairAlgorithm == "RSA" &&
319             *optKeyBitLength != RSA_KEY_BIT_LENGTH)
320         {
321             messages::propertyValueNotInList(asyncResp->res,
322                                              std::to_string(*optKeyBitLength),
323                                              "KeyBitLength");
324             return;
325         }
326 
327         // validate KeyUsage supporting only 1 type based on URL
328         if (boost::starts_with(
329                 certURI,
330                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
331         {
332             if (optKeyUsage->size() == 0)
333             {
334                 optKeyUsage->push_back("ServerAuthentication");
335             }
336             else if (optKeyUsage->size() == 1)
337             {
338                 if ((*optKeyUsage)[0] != "ServerAuthentication")
339                 {
340                     messages::propertyValueNotInList(
341                         asyncResp->res, (*optKeyUsage)[0], "KeyUsage");
342                     return;
343                 }
344             }
345             else
346             {
347                 messages::actionParameterNotSupported(
348                     asyncResp->res, "KeyUsage", "GenerateCSR");
349                 return;
350             }
351         }
352 
353         // Only allow one CSR matcher at a time so setting retry time-out and
354         // timer expiry to 10 seconds for now.
355         static const int TIME_OUT = 10;
356         if (csrMatcher)
357         {
358             res.addHeader("Retry-After", std::to_string(TIME_OUT));
359             messages::serviceTemporarilyUnavailable(asyncResp->res,
360                                                     std::to_string(TIME_OUT));
361             return;
362         }
363 
364         // Make this static so it survives outside this method
365         static boost::asio::steady_timer timeout(*req.ioService);
366         timeout.expires_after(std::chrono::seconds(TIME_OUT));
367         timeout.async_wait([asyncResp](const boost::system::error_code &ec) {
368             csrMatcher = nullptr;
369             if (ec)
370             {
371                 // operation_aborted is expected if timer is canceled before
372                 // completion.
373                 if (ec != boost::asio::error::operation_aborted)
374                 {
375                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
376                 }
377                 return;
378             }
379             BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
380             messages::internalError(asyncResp->res);
381         });
382 
383         // create a matcher to wait on CSR object
384         BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
385         std::string match("type='signal',"
386                           "interface='org.freedesktop.DBus.ObjectManager',"
387                           "path='" +
388                           objectPath +
389                           "',"
390                           "member='InterfacesAdded'");
391         csrMatcher = std::make_unique<sdbusplus::bus::match::match>(
392             *crow::connections::systemBus, match,
393             [asyncResp, service, objectPath,
394              certURI](sdbusplus::message::message &m) {
395                 boost::system::error_code ec;
396                 timeout.cancel(ec);
397                 if (ec)
398                 {
399                     BMCWEB_LOG_ERROR << "error canceling timer " << ec;
400                     csrMatcher = nullptr;
401                 }
402                 if (m.is_method_error())
403                 {
404                     BMCWEB_LOG_ERROR << "Dbus method error!!!";
405                     messages::internalError(asyncResp->res);
406                     return;
407                 }
408                 std::vector<std::pair<
409                     std::string, std::vector<std::pair<
410                                      std::string, std::variant<std::string>>>>>
411                     interfacesProperties;
412                 sdbusplus::message::object_path csrObjectPath;
413                 m.read(csrObjectPath, interfacesProperties);
414                 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
415                 for (auto &interface : interfacesProperties)
416                 {
417                     if (interface.first == "xyz.openbmc_project.Certs.CSR")
418                     {
419                         getCSR(asyncResp, certURI, service, objectPath,
420                                csrObjectPath.str);
421                         break;
422                     }
423                 }
424             });
425         crow::connections::systemBus->async_method_call(
426             [asyncResp](const boost::system::error_code ec,
427                         const std::string &path) {
428                 if (ec)
429                 {
430                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
431                     messages::internalError(asyncResp->res);
432                     return;
433                 }
434             },
435             service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
436             "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
437             commonName, *optContactPerson, country, *optEmail, *optGivenName,
438             *optInitials, *optKeyBitLength, *optKeyCurveId,
439             *optKeyPairAlgorithm, *optKeyUsage, organization,
440             organizationalUnit, state, *optSurname, *optUnstructuredName);
441     }
442 }; // CertificateActionGenerateCSR
443 
444 /**
445  * @brief Parse and update Certficate Issue/Subject property
446  *
447  * @param[in] asyncResp Shared pointer to the response message
448  * @param[in] str  Issuer/Subject value in key=value pairs
449  * @param[in] type Issuer/Subject
450  * @return None
451  */
452 static void updateCertIssuerOrSubject(nlohmann::json &out,
453                                       const std::string_view value)
454 {
455     // example: O=openbmc-project.xyz,CN=localhost
456     std::string_view::iterator i = value.begin();
457     while (i != value.end())
458     {
459         std::string_view::iterator tokenBegin = i;
460         while (i != value.end() && *i != '=')
461         {
462             i++;
463         }
464         if (i == value.end())
465         {
466             break;
467         }
468         const std::string_view key(tokenBegin, i - tokenBegin);
469         i++;
470         tokenBegin = i;
471         while (i != value.end() && *i != ',')
472         {
473             i++;
474         }
475         const std::string_view val(tokenBegin, i - tokenBegin);
476         if (key == "L")
477         {
478             out["City"] = val;
479         }
480         else if (key == "CN")
481         {
482             out["CommonName"] = val;
483         }
484         else if (key == "C")
485         {
486             out["Country"] = val;
487         }
488         else if (key == "O")
489         {
490             out["Organization"] = val;
491         }
492         else if (key == "OU")
493         {
494             out["OrganizationalUnit"] = val;
495         }
496         else if (key == "ST")
497         {
498             out["State"] = val;
499         }
500         // skip comma character
501         if (i != value.end())
502         {
503             i++;
504         }
505     }
506 }
507 
508 /**
509  * @brief Retrieve the certificates properties and append to the response
510  * message
511  *
512  * @param[in] asyncResp Shared pointer to the response message
513  * @param[in] objectPath  Path of the D-Bus service object
514  * @param[in] certId  Id of the certificate
515  * @param[in] certURL  URL of the certificate object
516  * @param[in] name  name of the certificate
517  * @return None
518  */
519 static void getCertificateProperties(
520     const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
521     const std::string &service, long certId, const std::string &certURL,
522     const std::string &name)
523 {
524     using PropertyType =
525         std::variant<std::string, uint64_t, std::vector<std::string>>;
526     using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
527     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
528                      << " certId=" << certId << " certURl=" << certURL;
529     crow::connections::systemBus->async_method_call(
530         [asyncResp, certURL, certId, name](const boost::system::error_code ec,
531                                            const PropertiesMap &properties) {
532             if (ec)
533             {
534                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
535                 messages::resourceNotFound(asyncResp->res, name,
536                                            std::to_string(certId));
537                 return;
538             }
539             asyncResp->res.jsonValue = {
540                 {"@odata.id", certURL},
541                 {"@odata.type", "#Certificate.v1_0_0.Certificate"},
542                 {"@odata.context",
543                  "/redfish/v1/$metadata#Certificate.Certificate"},
544                 {"Id", std::to_string(certId)},
545                 {"Name", name},
546                 {"Description", name}};
547             for (const auto &property : properties)
548             {
549                 if (property.first == "CertificateString")
550                 {
551                     asyncResp->res.jsonValue["CertificateString"] = "";
552                     const std::string *value =
553                         std::get_if<std::string>(&property.second);
554                     if (value)
555                     {
556                         asyncResp->res.jsonValue["CertificateString"] = *value;
557                     }
558                 }
559                 else if (property.first == "KeyUsage")
560                 {
561                     nlohmann::json &keyUsage =
562                         asyncResp->res.jsonValue["KeyUsage"];
563                     keyUsage = nlohmann::json::array();
564                     const std::vector<std::string> *value =
565                         std::get_if<std::vector<std::string>>(&property.second);
566                     if (value)
567                     {
568                         for (const std::string &usage : *value)
569                         {
570                             keyUsage.push_back(usage);
571                         }
572                     }
573                 }
574                 else if (property.first == "Issuer")
575                 {
576                     const std::string *value =
577                         std::get_if<std::string>(&property.second);
578                     if (value)
579                     {
580                         updateCertIssuerOrSubject(
581                             asyncResp->res.jsonValue["Issuer"], *value);
582                     }
583                 }
584                 else if (property.first == "Subject")
585                 {
586                     const std::string *value =
587                         std::get_if<std::string>(&property.second);
588                     if (value)
589                     {
590                         updateCertIssuerOrSubject(
591                             asyncResp->res.jsonValue["Subject"], *value);
592                     }
593                 }
594                 else if (property.first == "ValidNotAfter")
595                 {
596                     const uint64_t *value =
597                         std::get_if<uint64_t>(&property.second);
598                     if (value)
599                     {
600                         std::time_t time = static_cast<std::time_t>(*value);
601                         asyncResp->res.jsonValue["ValidNotAfter"] =
602                             crow::utility::getDateTime(time);
603                     }
604                 }
605                 else if (property.first == "ValidNotBefore")
606                 {
607                     const uint64_t *value =
608                         std::get_if<uint64_t>(&property.second);
609                     if (value)
610                     {
611                         std::time_t time = static_cast<std::time_t>(*value);
612                         asyncResp->res.jsonValue["ValidNotBefore"] =
613                             crow::utility::getDateTime(time);
614                     }
615                 }
616             }
617             asyncResp->res.addHeader("Location", certURL);
618         },
619         service, objectPath, certs::dbusPropIntf, "GetAll",
620         certs::certPropIntf);
621 }
622 
623 using GetObjectType =
624     std::vector<std::pair<std::string, std::vector<std::string>>>;
625 
626 /**
627  * Action to replace an existing certificate
628  */
629 class CertificateActionsReplaceCertificate : public Node
630 {
631   public:
632     CertificateActionsReplaceCertificate(CrowApp &app) :
633         Node(app, "/redfish/v1/CertificateService/Actions/"
634                   "CertificateService.ReplaceCertificate/")
635     {
636         entityPrivileges = {
637             {boost::beast::http::verb::get, {{"Login"}}},
638             {boost::beast::http::verb::head, {{"Login"}}},
639             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
640             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
641             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
642             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
643     }
644 
645   private:
646     void doPost(crow::Response &res, const crow::Request &req,
647                 const std::vector<std::string> &params) override
648     {
649         std::string certificate;
650         nlohmann::json certificateUri;
651         std::optional<std::string> certificateType = "PEM";
652         auto asyncResp = std::make_shared<AsyncResp>(res);
653         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
654                                  certificate, "CertificateUri", certificateUri,
655                                  "CertificateType", certificateType))
656         {
657             BMCWEB_LOG_ERROR << "Required parameters are missing";
658             messages::internalError(asyncResp->res);
659             return;
660         }
661 
662         if (!certificateType)
663         {
664             // should never happen, but it never hurts to be paranoid.
665             return;
666         }
667         if (certificateType != "PEM")
668         {
669             messages::actionParameterNotSupported(
670                 asyncResp->res, "CertificateType", "ReplaceCertificate");
671             return;
672         }
673 
674         std::string certURI;
675         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
676                                           "@odata.id", certURI))
677         {
678             messages::actionParameterMissing(
679                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
680             return;
681         }
682 
683         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
684         long id = getIDFromURL(certURI);
685         if (id < 0)
686         {
687             messages::actionParameterValueFormatError(asyncResp->res, certURI,
688                                                       "CertificateUri",
689                                                       "ReplaceCertificate");
690             return;
691         }
692         std::string objectPath;
693         std::string name;
694         std::string service;
695         if (boost::starts_with(
696                 certURI,
697                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
698         {
699             objectPath =
700                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
701             name = "HTTPS certificate";
702             service = certs::httpsServiceName;
703         }
704         else if (boost::starts_with(
705                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
706         {
707             objectPath =
708                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
709             name = "LDAP certificate";
710             service = certs::ldapServiceName;
711         }
712         else
713         {
714             messages::actionParameterNotSupported(
715                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
716             return;
717         }
718 
719         std::shared_ptr<CertificateFile> certFile =
720             std::make_shared<CertificateFile>(certificate);
721         crow::connections::systemBus->async_method_call(
722             [asyncResp, certFile, objectPath, service, certURI, id,
723              name](const boost::system::error_code ec) {
724                 if (ec)
725                 {
726                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
727                     messages::resourceNotFound(asyncResp->res, name,
728                                                std::to_string(id));
729                     return;
730                 }
731                 getCertificateProperties(asyncResp, objectPath, service, id,
732                                          certURI, name);
733                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
734                                  << certFile->getCertFilePath();
735             },
736             service, objectPath, certs::certReplaceIntf, "Replace",
737             certFile->getCertFilePath());
738     }
739 }; // CertificateActionsReplaceCertificate
740 
741 /**
742  * Certificate resource describes a certificate used to prove the identity
743  * of a component, account or service.
744  */
745 class HTTPSCertificate : public Node
746 {
747   public:
748     template <typename CrowApp>
749     HTTPSCertificate(CrowApp &app) :
750         Node(app,
751              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
752              "<str>/",
753              std::string())
754     {
755         entityPrivileges = {
756             {boost::beast::http::verb::get, {{"Login"}}},
757             {boost::beast::http::verb::head, {{"Login"}}},
758             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
759             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
760             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
761             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
762     }
763 
764     void doGet(crow::Response &res, const crow::Request &req,
765                const std::vector<std::string> &params) override
766     {
767         auto asyncResp = std::make_shared<AsyncResp>(res);
768         if (params.size() != 1)
769         {
770             messages::internalError(asyncResp->res);
771             return;
772         }
773         long id = getIDFromURL(req.url);
774 
775         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
776         std::string certURL =
777             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
778             std::to_string(id);
779         std::string objectPath = certs::httpsObjectPath;
780         objectPath += "/";
781         objectPath += std::to_string(id);
782         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
783                                  id, certURL, "HTTPS Certificate");
784     }
785 
786 }; // namespace redfish
787 
788 /**
789  * Collection of HTTPS certificates
790  */
791 class HTTPSCertificateCollection : public Node
792 {
793   public:
794     template <typename CrowApp>
795     HTTPSCertificateCollection(CrowApp &app) :
796         Node(app,
797              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
798     {
799         entityPrivileges = {
800             {boost::beast::http::verb::get, {{"Login"}}},
801             {boost::beast::http::verb::head, {{"Login"}}},
802             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
803             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
804             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
805             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
806     }
807     void doGet(crow::Response &res, const crow::Request &req,
808                const std::vector<std::string> &params) override
809     {
810         res.jsonValue = {
811             {"@odata.id",
812              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
813             {"@odata.type", "#CertificateCollection.CertificateCollection"},
814             {"@odata.context",
815              "/redfish/v1/"
816              "$metadata#CertificateCollection.CertificateCollection"},
817             {"Name", "HTTPS Certificates Collection"},
818             {"Description", "A Collection of HTTPS certificate instances"}};
819         auto asyncResp = std::make_shared<AsyncResp>(res);
820         crow::connections::systemBus->async_method_call(
821             [asyncResp](const boost::system::error_code ec,
822                         const ManagedObjectType &certs) {
823                 if (ec)
824                 {
825                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
826                     messages::internalError(asyncResp->res);
827                     return;
828                 }
829                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
830                 members = nlohmann::json::array();
831                 for (const auto &cert : certs)
832                 {
833                     long id = getIDFromURL(cert.first.str);
834                     if (id >= 0)
835                     {
836                         members.push_back(
837                             {{"@odata.id",
838                               "/redfish/v1/Managers/bmc/"
839                               "NetworkProtocol/HTTPS/Certificates/" +
840                                   std::to_string(id)}});
841                     }
842                 }
843                 asyncResp->res.jsonValue["Members@odata.count"] =
844                     members.size();
845             },
846             certs::httpsServiceName, certs::httpsObjectPath,
847             certs::dbusObjManagerIntf, "GetManagedObjects");
848     }
849 
850     void doPost(crow::Response &res, const crow::Request &req,
851                 const std::vector<std::string> &params) override
852     {
853         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
854         auto asyncResp = std::make_shared<AsyncResp>(res);
855         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
856                                     {"Description", "HTTPS Certificate"}};
857 
858         std::shared_ptr<CertificateFile> certFile =
859             std::make_shared<CertificateFile>(req.body);
860 
861         crow::connections::systemBus->async_method_call(
862             [asyncResp, certFile](const boost::system::error_code ec) {
863                 if (ec)
864                 {
865                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
866                     messages::internalError(asyncResp->res);
867                     return;
868                 }
869                 // TODO: Issue#84 supporting only 1 certificate
870                 long certId = 1;
871                 std::string certURL =
872                     "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
873                     "Certificates/" +
874                     std::to_string(certId);
875                 std::string objectPath = std::string(certs::httpsObjectPath) +
876                                          "/" + std::to_string(certId);
877                 getCertificateProperties(asyncResp, objectPath,
878                                          certs::httpsServiceName, certId,
879                                          certURL, "HTTPS Certificate");
880                 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
881                                  << certFile->getCertFilePath();
882             },
883             certs::httpsServiceName, certs::httpsObjectPath,
884             certs::certInstallIntf, "Install", certFile->getCertFilePath());
885     }
886 }; // HTTPSCertificateCollection
887 
888 /**
889  * The certificate location schema defines a resource that an administrator
890  * can use in order to locate all certificates installed on a given service.
891  */
892 class CertificateLocations : public Node
893 {
894   public:
895     template <typename CrowApp>
896     CertificateLocations(CrowApp &app) :
897         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
898     {
899         entityPrivileges = {
900             {boost::beast::http::verb::get, {{"Login"}}},
901             {boost::beast::http::verb::head, {{"Login"}}},
902             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
903             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
904             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
905             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
906     }
907 
908   private:
909     void doGet(crow::Response &res, const crow::Request &req,
910                const std::vector<std::string> &params) override
911     {
912         res.jsonValue = {
913             {"@odata.id",
914              "/redfish/v1/CertificateService/CertificateLocations"},
915             {"@odata.type",
916              "#CertificateLocations.v1_0_0.CertificateLocations"},
917             {"@odata.context",
918              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
919             {"Name", "Certificate Locations"},
920             {"Id", "CertificateLocations"},
921             {"Description",
922              "Defines a resource that an administrator can use in order to "
923              "locate all certificates installed on a given service"}};
924         auto asyncResp = std::make_shared<AsyncResp>(res);
925         nlohmann::json &links =
926             asyncResp->res.jsonValue["Links"]["Certificates"];
927         links = nlohmann::json::array();
928         getCertificateLocations(
929             asyncResp,
930             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
931             certs::httpsObjectPath, certs::httpsServiceName);
932         getCertificateLocations(asyncResp,
933                                 "/redfish/v1/AccountService/LDAP/Certificates/",
934                                 certs::ldapObjectPath, certs::ldapServiceName);
935     }
936     /**
937      * @brief Retrieve the certificates installed list and append to the
938      * response
939      *
940      * @param[in] asyncResp Shared pointer to the response message
941      * @param[in] certURL  Path of the certificate object
942      * @param[in] path  Path of the D-Bus service object
943      * @return None
944      */
945     void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
946                                  const std::string &certURL,
947                                  const std::string &path,
948                                  const std::string &service)
949     {
950         BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
951                          << " Path=" << path << " service= " << service;
952         crow::connections::systemBus->async_method_call(
953             [asyncResp, certURL](const boost::system::error_code ec,
954                                  const ManagedObjectType &certs) {
955                 if (ec)
956                 {
957                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
958                     messages::internalError(asyncResp->res);
959                     return;
960                 }
961                 nlohmann::json &links =
962                     asyncResp->res.jsonValue["Links"]["Certificates"];
963                 for (auto &cert : certs)
964                 {
965                     long id = getIDFromURL(cert.first.str);
966                     if (id >= 0)
967                     {
968                         links.push_back(
969                             {{"@odata.id", certURL + std::to_string(id)}});
970                     }
971                 }
972                 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
973                     links.size();
974             },
975             service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
976     }
977 }; // CertificateLocations
978 
979 /**
980  * Collection of LDAP certificates
981  */
982 class LDAPCertificateCollection : public Node
983 {
984   public:
985     template <typename CrowApp>
986     LDAPCertificateCollection(CrowApp &app) :
987         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/")
988     {
989         entityPrivileges = {
990             {boost::beast::http::verb::get, {{"Login"}}},
991             {boost::beast::http::verb::head, {{"Login"}}},
992             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
993             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
994             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
995             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
996     }
997     void doGet(crow::Response &res, const crow::Request &req,
998                const std::vector<std::string> &params) override
999     {
1000         res.jsonValue = {
1001             {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"},
1002             {"@odata.type", "#CertificateCollection.CertificateCollection"},
1003             {"@odata.context",
1004              "/redfish/v1/"
1005              "$metadata#CertificateCollection.CertificateCollection"},
1006             {"Name", "LDAP Certificates Collection"},
1007             {"Description", "A Collection of LDAP certificate instances"}};
1008         auto asyncResp = std::make_shared<AsyncResp>(res);
1009         crow::connections::systemBus->async_method_call(
1010             [asyncResp](const boost::system::error_code ec,
1011                         const ManagedObjectType &certs) {
1012                 if (ec)
1013                 {
1014                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1015                     messages::internalError(asyncResp->res);
1016                     return;
1017                 }
1018                 nlohmann::json &members = asyncResp->res.jsonValue["Members"];
1019                 members = nlohmann::json::array();
1020                 for (const auto &cert : certs)
1021                 {
1022                     long id = getIDFromURL(cert.first.str);
1023                     if (id >= 0)
1024                     {
1025                         members.push_back(
1026                             {{"@odata.id", "/redfish/v1/AccountService/"
1027                                            "LDAP/Certificates/" +
1028                                                std::to_string(id)}});
1029                     }
1030                 }
1031                 asyncResp->res.jsonValue["Members@odata.count"] =
1032                     members.size();
1033             },
1034             certs::ldapServiceName, certs::ldapObjectPath,
1035             certs::dbusObjManagerIntf, "GetManagedObjects");
1036     }
1037 
1038     void doPost(crow::Response &res, const crow::Request &req,
1039                 const std::vector<std::string> &params) override
1040     {
1041         std::shared_ptr<CertificateFile> certFile =
1042             std::make_shared<CertificateFile>(req.body);
1043         auto asyncResp = std::make_shared<AsyncResp>(res);
1044         crow::connections::systemBus->async_method_call(
1045             [asyncResp, certFile](const boost::system::error_code ec) {
1046                 if (ec)
1047                 {
1048                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1049                     messages::internalError(asyncResp->res);
1050                     return;
1051                 }
1052                 //// TODO: Issue#84 supporting only 1 certificate
1053                 long certId = 1;
1054                 std::string certURL =
1055                     "/redfish/v1/AccountService/LDAP/Certificates/" +
1056                     std::to_string(certId);
1057                 std::string objectPath = std::string(certs::ldapObjectPath) +
1058                                          "/" + std::to_string(certId);
1059                 getCertificateProperties(asyncResp, objectPath,
1060                                          certs::ldapServiceName, certId,
1061                                          certURL, "LDAP Certificate");
1062                 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1063                                  << certFile->getCertFilePath();
1064             },
1065             certs::ldapServiceName, certs::ldapObjectPath,
1066             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1067     }
1068 }; // LDAPCertificateCollection
1069 
1070 /**
1071  * Certificate resource describes a certificate used to prove the identity
1072  * of a component, account or service.
1073  */
1074 class LDAPCertificate : public Node
1075 {
1076   public:
1077     template <typename CrowApp>
1078     LDAPCertificate(CrowApp &app) :
1079         Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/",
1080              std::string())
1081     {
1082         entityPrivileges = {
1083             {boost::beast::http::verb::get, {{"Login"}}},
1084             {boost::beast::http::verb::head, {{"Login"}}},
1085             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1086             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1087             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1088             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1089     }
1090 
1091     void doGet(crow::Response &res, const crow::Request &req,
1092                const std::vector<std::string> &params) override
1093     {
1094         auto asyncResp = std::make_shared<AsyncResp>(res);
1095         long id = getIDFromURL(req.url);
1096         if (id < 0)
1097         {
1098             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1099             messages::internalError(asyncResp->res);
1100             return;
1101         }
1102         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1103         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1104                               std::to_string(id);
1105         std::string objectPath = certs::ldapObjectPath;
1106         objectPath += "/";
1107         objectPath += std::to_string(id);
1108         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1109                                  id, certURL, "LDAP Certificate");
1110     }
1111 }; // LDAPCertificate
1112 } // namespace redfish
1113