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