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