xref: /openbmc/bmcweb/redfish-core/lib/certificate_service.hpp (revision 5a60c56f08c8c65a611e559b4c1ef6886933501a)
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.Ldap";
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/ldap";
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=" << objectPath
298                      << " certId=" << certId << " certURl=" << 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     asyncResp->res.jsonValue["@odata.type"] =
408         "#CertificateService.v1_0_0.CertificateService";
409     asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/CertificateService";
410     asyncResp->res.jsonValue["Id"] = "CertificateService";
411     asyncResp->res.jsonValue["Name"] = "Certificate Service";
412     asyncResp->res.jsonValue["Description"] =
413         "Actions available to manage certificates";
414     // /redfish/v1/CertificateService/CertificateLocations is something
415     // only ConfigureManager can access then only display when the user
416     // has permissions ConfigureManager
417     Privileges effectiveUserPrivileges =
418         redfish::getUserPrivileges(req.userRole);
419     if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
420                                          effectiveUserPrivileges))
421     {
422         asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] =
423             "/redfish/v1/CertificateService/CertificateLocations";
424     }
425     nlohmann::json& actions = asyncResp->res.jsonValue["Actions"];
426     nlohmann::json& replace = actions["#CertificateService.ReplaceCertificate"];
427     replace["target"] =
428         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate";
429     nlohmann::json::array_t allowed;
430     allowed.emplace_back("PEM");
431     replace["CertificateType@Redfish.AllowableValues"] = std::move(allowed);
432     actions["#CertificateService.GenerateCSR"]["target"] =
433         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR";
434 }
435 
436 inline void handleCertificateLocationsGet(
437     App& app, const crow::Request& req,
438     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
439 {
440     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
441     {
442         return;
443     }
444     asyncResp->res.jsonValue["@odata.id"] =
445         "/redfish/v1/CertificateService/CertificateLocations";
446     asyncResp->res.jsonValue["@odata.type"] =
447         "#CertificateLocations.v1_0_0.CertificateLocations";
448     asyncResp->res.jsonValue["Name"] = "Certificate Locations";
449     asyncResp->res.jsonValue["Id"] = "CertificateLocations";
450     asyncResp->res.jsonValue["Description"] =
451         "Defines a resource that an administrator can use in order to "
452         "locate all certificates installed on a given service";
453 
454     getCertificateList(asyncResp, certs::baseObjectPath,
455                        "/Links/Certificates"_json_pointer,
456                        "/Links/Certificates@odata.count"_json_pointer);
457 }
458 
459 inline void handleReplaceCertificateAction(
460     App& app, const crow::Request& req,
461     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
462 {
463     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
464     {
465         return;
466     }
467     std::string certificate;
468     nlohmann::json certificateUri;
469     std::optional<std::string> certificateType = "PEM";
470 
471     if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString",
472                                    certificate, "CertificateUri",
473                                    certificateUri, "CertificateType",
474                                    certificateType))
475     {
476         BMCWEB_LOG_ERROR << "Required parameters are missing";
477         return;
478     }
479 
480     if (!certificateType)
481     {
482         // should never happen, but it never hurts to be paranoid.
483         return;
484     }
485     if (certificateType != "PEM")
486     {
487         messages::actionParameterNotSupported(asyncResp->res, "CertificateType",
488                                               "ReplaceCertificate");
489         return;
490     }
491 
492     std::string certURI;
493     if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
494                                       "@odata.id", certURI))
495     {
496         messages::actionParameterMissing(asyncResp->res, "ReplaceCertificate",
497                                          "CertificateUri");
498         return;
499     }
500     BMCWEB_LOG_INFO << "Certificate URI to replace: " << certURI;
501 
502     boost::urls::result<boost::urls::url_view> parsedUrl =
503         boost::urls::parse_relative_ref(certURI);
504     if (!parsedUrl)
505     {
506         messages::actionParameterValueFormatError(
507             asyncResp->res, certURI, "CertificateUri", "ReplaceCertificate");
508         return;
509     }
510 
511     std::string id;
512     sdbusplus::message::object_path objectPath;
513     std::string name;
514     std::string service;
515     if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", "Managers",
516                                        "bmc", "NetworkProtocol", "HTTPS",
517                                        "Certificates", std::ref(id)))
518     {
519         objectPath = sdbusplus::message::object_path(certs::httpsObjectPath) /
520                      id;
521         name = "HTTPS certificate";
522         service = certs::httpsServiceName;
523     }
524     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
525                                             "AccountService", "LDAP",
526                                             "Certificates", std::ref(id)))
527     {
528         objectPath = sdbusplus::message::object_path(certs::ldapObjectPath) /
529                      id;
530         name = "LDAP certificate";
531         service = certs::ldapServiceName;
532     }
533     else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
534                                             "Managers", "bmc", "Truststore",
535                                             "Certificates", std::ref(id)))
536     {
537         objectPath =
538             sdbusplus::message::object_path(certs::authorityObjectPath) / id;
539         name = "TrustStore certificate";
540         service = certs::authorityServiceName;
541     }
542     else
543     {
544         messages::actionParameterNotSupported(asyncResp->res, "CertificateUri",
545                                               "ReplaceCertificate");
546         return;
547     }
548 
549     std::shared_ptr<CertificateFile> certFile =
550         std::make_shared<CertificateFile>(certificate);
551     crow::connections::systemBus->async_method_call(
552         [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id,
553          name](const boost::system::error_code& ec) {
554         if (ec)
555         {
556             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
557             if (ec.value() ==
558                 boost::system::linux_error::bad_request_descriptor)
559             {
560                 messages::resourceNotFound(asyncResp->res, "Certificate", id);
561                 return;
562             }
563             messages::internalError(asyncResp->res);
564             return;
565         }
566         getCertificateProperties(asyncResp, objectPath, service, id, url, name);
567         BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
568                          << certFile->getCertFilePath();
569         },
570         service, objectPath, certs::certReplaceIntf, "Replace",
571         certFile->getCertFilePath());
572 }
573 
574 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
575 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher;
576 /**
577  * @brief Read data from CSR D-bus object and set to response
578  *
579  * @param[in] asyncResp Shared pointer to the response message
580  * @param[in] certURI Link to certifiate collection URI
581  * @param[in] service D-Bus service name
582  * @param[in] certObjPath certificate D-Bus object path
583  * @param[in] csrObjPath CSR D-Bus object path
584  * @return None
585  */
586 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
587                    const std::string& certURI, const std::string& service,
588                    const std::string& certObjPath,
589                    const std::string& csrObjPath)
590 {
591     BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath
592                      << " CSRObjectPath=" << csrObjPath
593                      << " service=" << service;
594     crow::connections::systemBus->async_method_call(
595         [asyncResp, certURI](const boost::system::error_code& ec,
596                              const std::string& csr) {
597         if (ec)
598         {
599             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
600             messages::internalError(asyncResp->res);
601             return;
602         }
603         if (csr.empty())
604         {
605             BMCWEB_LOG_ERROR << "CSR read is empty";
606             messages::internalError(asyncResp->res);
607             return;
608         }
609         asyncResp->res.jsonValue["CSRString"] = csr;
610         asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
611             certURI;
612         },
613         service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
614 }
615 
616 inline void
617     handleGenerateCSRAction(App& app, const crow::Request& req,
618                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
619 {
620     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
621     {
622         return;
623     }
624     static const int rsaKeyBitLength = 2048;
625 
626     // Required parameters
627     std::string city;
628     std::string commonName;
629     std::string country;
630     std::string organization;
631     std::string organizationalUnit;
632     std::string state;
633     nlohmann::json certificateCollection;
634 
635     // Optional parameters
636     std::optional<std::vector<std::string>> optAlternativeNames =
637         std::vector<std::string>();
638     std::optional<std::string> optContactPerson = "";
639     std::optional<std::string> optChallengePassword = "";
640     std::optional<std::string> optEmail = "";
641     std::optional<std::string> optGivenName = "";
642     std::optional<std::string> optInitials = "";
643     std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
644     std::optional<std::string> optKeyCurveId = "secp384r1";
645     std::optional<std::string> optKeyPairAlgorithm = "EC";
646     std::optional<std::vector<std::string>> optKeyUsage =
647         std::vector<std::string>();
648     std::optional<std::string> optSurname = "";
649     std::optional<std::string> optUnstructuredName = "";
650     if (!json_util::readJsonAction(
651             req, asyncResp->res, "City", city, "CommonName", commonName,
652             "ContactPerson", optContactPerson, "Country", country,
653             "Organization", organization, "OrganizationalUnit",
654             organizationalUnit, "State", state, "CertificateCollection",
655             certificateCollection, "AlternativeNames", optAlternativeNames,
656             "ChallengePassword", optChallengePassword, "Email", optEmail,
657             "GivenName", optGivenName, "Initials", optInitials, "KeyBitLength",
658             optKeyBitLength, "KeyCurveId", optKeyCurveId, "KeyPairAlgorithm",
659             optKeyPairAlgorithm, "KeyUsage", optKeyUsage, "Surname", optSurname,
660             "UnstructuredName", optUnstructuredName))
661     {
662         return;
663     }
664 
665     // bmcweb has no way to store or decode a private key challenge
666     // password, which will likely cause bmcweb to crash on startup
667     // if this is not set on a post so not allowing the user to set
668     // value
669     if (!optChallengePassword->empty())
670     {
671         messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR",
672                                               "ChallengePassword");
673         return;
674     }
675 
676     std::string certURI;
677     if (!redfish::json_util::readJson(certificateCollection, asyncResp->res,
678                                       "@odata.id", certURI))
679     {
680         return;
681     }
682 
683     std::string objectPath;
684     std::string service;
685     if (certURI.starts_with(
686             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
687     {
688         objectPath = certs::httpsObjectPath;
689         service = certs::httpsServiceName;
690     }
691     else if (certURI.starts_with(
692                  "/redfish/v1/AccountService/LDAP/Certificates"))
693     {
694         objectPath = certs::ldapObjectPath;
695         service = certs::ldapServiceName;
696     }
697     else
698     {
699         messages::actionParameterNotSupported(
700             asyncResp->res, "CertificateCollection", "GenerateCSR");
701         return;
702     }
703 
704     // supporting only EC and RSA algorithm
705     if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
706     {
707         messages::actionParameterNotSupported(
708             asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
709         return;
710     }
711 
712     // supporting only 2048 key bit length for RSA algorithm due to
713     // time consumed in generating private key
714     if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength)
715     {
716         messages::propertyValueNotInList(
717             asyncResp->res, std::to_string(*optKeyBitLength), "KeyBitLength");
718         return;
719     }
720 
721     // validate KeyUsage supporting only 1 type based on URL
722     if (certURI.starts_with(
723             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
724     {
725         if (optKeyUsage->empty())
726         {
727             optKeyUsage->emplace_back("ServerAuthentication");
728         }
729         else if (optKeyUsage->size() == 1)
730         {
731             if ((*optKeyUsage)[0] != "ServerAuthentication")
732             {
733                 messages::propertyValueNotInList(asyncResp->res,
734                                                  (*optKeyUsage)[0], "KeyUsage");
735                 return;
736             }
737         }
738         else
739         {
740             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
741                                                   "GenerateCSR");
742             return;
743         }
744     }
745     else if (certURI.starts_with(
746                  "/redfish/v1/AccountService/LDAP/Certificates"))
747     {
748         if (optKeyUsage->empty())
749         {
750             optKeyUsage->emplace_back("ClientAuthentication");
751         }
752         else if (optKeyUsage->size() == 1)
753         {
754             if ((*optKeyUsage)[0] != "ClientAuthentication")
755             {
756                 messages::propertyValueNotInList(asyncResp->res,
757                                                  (*optKeyUsage)[0], "KeyUsage");
758                 return;
759             }
760         }
761         else
762         {
763             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
764                                                   "GenerateCSR");
765             return;
766         }
767     }
768 
769     // Only allow one CSR matcher at a time so setting retry
770     // time-out and timer expiry to 10 seconds for now.
771     static const int timeOut = 10;
772     if (csrMatcher)
773     {
774         messages::serviceTemporarilyUnavailable(asyncResp->res,
775                                                 std::to_string(timeOut));
776         return;
777     }
778 
779     // Make this static so it survives outside this method
780     static boost::asio::steady_timer timeout(*req.ioService);
781     timeout.expires_after(std::chrono::seconds(timeOut));
782     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
783         csrMatcher = nullptr;
784         if (ec)
785         {
786             // operation_aborted is expected if timer is canceled
787             // before completion.
788             if (ec != boost::asio::error::operation_aborted)
789             {
790                 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
791             }
792             return;
793         }
794         BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
795         messages::internalError(asyncResp->res);
796     });
797 
798     // create a matcher to wait on CSR object
799     BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
800     std::string match("type='signal',"
801                       "interface='org.freedesktop.DBus.ObjectManager',"
802                       "path='" +
803                       objectPath +
804                       "',"
805                       "member='InterfacesAdded'");
806     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
807         *crow::connections::systemBus, match,
808         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
809         timeout.cancel();
810         if (m.is_method_error())
811         {
812             BMCWEB_LOG_ERROR << "Dbus method error!!!";
813             messages::internalError(asyncResp->res);
814             return;
815         }
816 
817         dbus::utility::DBusInteracesMap interfacesProperties;
818 
819         sdbusplus::message::object_path csrObjectPath;
820         m.read(csrObjectPath, interfacesProperties);
821         BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
822         for (const auto& interface : interfacesProperties)
823         {
824             if (interface.first == "xyz.openbmc_project.Certs.CSR")
825             {
826                 getCSR(asyncResp, certURI, service, objectPath,
827                        csrObjectPath.str);
828                 break;
829             }
830         }
831         });
832     crow::connections::systemBus->async_method_call(
833         [asyncResp](const boost::system::error_code& ec, const std::string&) {
834         if (ec)
835         {
836             BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
837             messages::internalError(asyncResp->res);
838             return;
839         }
840         },
841         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
842         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
843         commonName, *optContactPerson, country, *optEmail, *optGivenName,
844         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
845         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
846         *optUnstructuredName);
847 }
848 
849 inline void requestRoutesCertificateService(App& app)
850 {
851     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
852         .privileges(redfish::privileges::getCertificateService)
853         .methods(boost::beast::http::verb::get)(
854             std::bind_front(handleCertificateServiceGet, std::ref(app)));
855 
856     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
857         .privileges(redfish::privileges::getCertificateLocations)
858         .methods(boost::beast::http::verb::get)(
859             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
860 
861     BMCWEB_ROUTE(
862         app,
863         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
864         .privileges(redfish::privileges::postCertificateService)
865         .methods(boost::beast::http::verb::post)(
866             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
867 
868     BMCWEB_ROUTE(
869         app,
870         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
871         .privileges(redfish::privileges::postCertificateService)
872         .methods(boost::beast::http::verb::post)(
873             std::bind_front(handleGenerateCSRAction, std::ref(app)));
874 } // requestRoutesCertificateService
875 
876 inline void handleHTTPSCertificateCollectionGet(
877     App& app, const crow::Request& req,
878     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
879 {
880     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
881     {
882         return;
883     }
884 
885     asyncResp->res.jsonValue["@odata.id"] =
886         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
887     asyncResp->res.jsonValue["@odata.type"] =
888         "#CertificateCollection.CertificateCollection";
889     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
890     asyncResp->res.jsonValue["Description"] =
891         "A Collection of HTTPS certificate instances";
892 
893     getCertificateList(asyncResp, certs::httpsObjectPath,
894                        "/Members"_json_pointer,
895                        "/Members@odata.count"_json_pointer);
896 }
897 
898 inline void handleHTTPSCertificateCollectionPost(
899     App& app, const crow::Request& req,
900     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
901 {
902     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
903     {
904         return;
905     }
906     BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
907 
908     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
909     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
910 
911     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
912 
913     if (certFileBody.empty())
914     {
915         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
916         messages::unrecognizedRequestBody(asyncResp->res);
917         return;
918     }
919 
920     std::shared_ptr<CertificateFile> certFile =
921         std::make_shared<CertificateFile>(certFileBody);
922 
923     crow::connections::systemBus->async_method_call(
924         [asyncResp, certFile](const boost::system::error_code& ec,
925                               const std::string& objectPath) {
926         if (ec)
927         {
928             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
929             messages::internalError(asyncResp->res);
930             return;
931         }
932 
933         sdbusplus::message::object_path path(objectPath);
934         std::string certId = path.filename();
935         const boost::urls::url certURL = boost::urls::format(
936             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}",
937             certId);
938         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
939                                  certId, certURL, "HTTPS Certificate");
940         BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
941                          << certFile->getCertFilePath();
942         },
943         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
944         "Install", certFile->getCertFilePath());
945 }
946 
947 inline void handleHTTPSCertificateGet(
948     App& app, const crow::Request& req,
949     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
950 {
951     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
952     {
953         return;
954     }
955 
956     BMCWEB_LOG_DEBUG << "HTTPS Certificate ID=" << id;
957     const boost::urls::url certURL = boost::urls::format(
958         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}", id);
959     std::string objPath =
960         sdbusplus::message::object_path(certs::httpsObjectPath) / id;
961     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, id,
962                              certURL, "HTTPS Certificate");
963 }
964 
965 inline void requestRoutesHTTPSCertificate(App& app)
966 {
967     BMCWEB_ROUTE(app,
968                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
969         .privileges(redfish::privileges::getCertificateCollection)
970         .methods(boost::beast::http::verb::get)(std::bind_front(
971             handleHTTPSCertificateCollectionGet, std::ref(app)));
972 
973     BMCWEB_ROUTE(app,
974                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
975         .privileges(redfish::privileges::postCertificateCollection)
976         .methods(boost::beast::http::verb::post)(std::bind_front(
977             handleHTTPSCertificateCollectionPost, std::ref(app)));
978 
979     BMCWEB_ROUTE(
980         app,
981         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
982         .privileges(redfish::privileges::getCertificate)
983         .methods(boost::beast::http::verb::get)(
984             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
985 }
986 
987 inline void handleLDAPCertificateCollectionGet(
988     App& app, const crow::Request& req,
989     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
990 {
991     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
992     {
993         return;
994     }
995 
996     asyncResp->res.jsonValue["@odata.id"] =
997         "/redfish/v1/AccountService/LDAP/Certificates";
998     asyncResp->res.jsonValue["@odata.type"] =
999         "#CertificateCollection.CertificateCollection";
1000     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1001     asyncResp->res.jsonValue["Description"] =
1002         "A Collection of LDAP certificate instances";
1003 
1004     getCertificateList(asyncResp, certs::ldapObjectPath,
1005                        "/Members"_json_pointer,
1006                        "/Members@odata.count"_json_pointer);
1007 }
1008 
1009 inline void handleLDAPCertificateCollectionPost(
1010     App& app, const crow::Request& req,
1011     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1012 {
1013     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1014     {
1015         return;
1016     }
1017     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1018 
1019     if (certFileBody.empty())
1020     {
1021         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1022         messages::unrecognizedRequestBody(asyncResp->res);
1023         return;
1024     }
1025 
1026     std::shared_ptr<CertificateFile> certFile =
1027         std::make_shared<CertificateFile>(certFileBody);
1028 
1029     crow::connections::systemBus->async_method_call(
1030         [asyncResp, certFile](const boost::system::error_code& ec,
1031                               const std::string& objectPath) {
1032         if (ec)
1033         {
1034             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1035             messages::internalError(asyncResp->res);
1036             return;
1037         }
1038 
1039         sdbusplus::message::object_path path(objectPath);
1040         std::string certId = path.filename();
1041         const boost::urls::url certURL = boost::urls::format(
1042             "/redfish/v1/AccountService/LDAP/Certificates/{}", certId);
1043         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1044                                  certId, certURL, "LDAP Certificate");
1045         BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1046                          << certFile->getCertFilePath();
1047         },
1048         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1049         "Install", certFile->getCertFilePath());
1050 }
1051 
1052 inline void handleLDAPCertificateGet(
1053     App& app, const crow::Request& req,
1054     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1055 {
1056     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1057     {
1058         return;
1059     }
1060 
1061     BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id;
1062     const boost::urls::url certURL = boost::urls::format(
1063         "/redfish/v1/AccountService/LDAP/Certificates/{}", id);
1064     std::string objPath =
1065         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1066     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1067                              certURL, "LDAP Certificate");
1068 }
1069 
1070 inline void handleLDAPCertificateDelete(
1071     App& app, const crow::Request& req,
1072     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1073 {
1074     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1075     {
1076         return;
1077     }
1078 
1079     BMCWEB_LOG_DEBUG << "Delete LDAP Certificate ID=" << id;
1080     std::string objPath =
1081         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1082 
1083     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1084 }
1085 
1086 inline void requestRoutesLDAPCertificate(App& app)
1087 {
1088     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1089         .privileges(redfish::privileges::getCertificateCollection)
1090         .methods(boost::beast::http::verb::get)(
1091             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1092 
1093     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1094         .privileges(redfish::privileges::postCertificateCollection)
1095         .methods(boost::beast::http::verb::post)(std::bind_front(
1096             handleLDAPCertificateCollectionPost, std::ref(app)));
1097 
1098     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1099         .privileges(redfish::privileges::getCertificate)
1100         .methods(boost::beast::http::verb::get)(
1101             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1102 
1103     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1104         .privileges(redfish::privileges::deleteCertificate)
1105         .methods(boost::beast::http::verb::delete_)(
1106             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1107 } // requestRoutesLDAPCertificate
1108 
1109 inline void handleTrustStoreCertificateCollectionGet(
1110     App& app, const crow::Request& req,
1111     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1112 {
1113     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1114     {
1115         return;
1116     }
1117 
1118     asyncResp->res.jsonValue["@odata.id"] =
1119         "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1120     asyncResp->res.jsonValue["@odata.type"] =
1121         "#CertificateCollection.CertificateCollection";
1122     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1123     asyncResp->res.jsonValue["Description"] =
1124         "A Collection of TrustStore certificate instances";
1125 
1126     getCertificateList(asyncResp, certs::authorityObjectPath,
1127                        "/Members"_json_pointer,
1128                        "/Members@odata.count"_json_pointer);
1129 }
1130 
1131 inline void handleTrustStoreCertificateCollectionPost(
1132     App& app, const crow::Request& req,
1133     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1134 {
1135     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1136     {
1137         return;
1138     }
1139     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1140 
1141     if (certFileBody.empty())
1142     {
1143         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1144         messages::unrecognizedRequestBody(asyncResp->res);
1145         return;
1146     }
1147 
1148     std::shared_ptr<CertificateFile> certFile =
1149         std::make_shared<CertificateFile>(certFileBody);
1150     crow::connections::systemBus->async_method_call(
1151         [asyncResp, certFile](const boost::system::error_code& ec,
1152                               const std::string& objectPath) {
1153         if (ec)
1154         {
1155             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1156             messages::internalError(asyncResp->res);
1157             return;
1158         }
1159 
1160         sdbusplus::message::object_path path(objectPath);
1161         std::string certId = path.filename();
1162         const boost::urls::url certURL = boost::urls::format(
1163             "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", certId);
1164         getCertificateProperties(asyncResp, objectPath,
1165                                  certs::authorityServiceName, certId, certURL,
1166                                  "TrustStore Certificate");
1167         BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1168                          << certFile->getCertFilePath();
1169         },
1170         certs::authorityServiceName, certs::authorityObjectPath,
1171         certs::certInstallIntf, "Install", certFile->getCertFilePath());
1172 }
1173 
1174 inline void handleTrustStoreCertificateGet(
1175     App& app, const crow::Request& req,
1176     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1177 {
1178     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1179     {
1180         return;
1181     }
1182 
1183     BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id;
1184     const boost::urls::url certURL = boost::urls::format(
1185         "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", id);
1186     std::string objPath =
1187         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1188     getCertificateProperties(asyncResp, objPath, certs::authorityServiceName,
1189                              id, certURL, "TrustStore Certificate");
1190 }
1191 
1192 inline void handleTrustStoreCertificateDelete(
1193     App& app, const crow::Request& req,
1194     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1195 {
1196     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1197     {
1198         return;
1199     }
1200 
1201     BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id;
1202     std::string objPath =
1203         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1204 
1205     deleteCertificate(asyncResp, certs::authorityServiceName, objPath);
1206 }
1207 
1208 inline void requestRoutesTrustStoreCertificate(App& app)
1209 {
1210     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1211         .privileges(redfish::privileges::getCertificate)
1212         .methods(boost::beast::http::verb::get)(std::bind_front(
1213             handleTrustStoreCertificateCollectionGet, std::ref(app)));
1214 
1215     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1216         .privileges(redfish::privileges::postCertificateCollection)
1217         .methods(boost::beast::http::verb::post)(std::bind_front(
1218             handleTrustStoreCertificateCollectionPost, std::ref(app)));
1219 
1220     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1221         .privileges(redfish::privileges::getCertificate)
1222         .methods(boost::beast::http::verb::get)(
1223             std::bind_front(handleTrustStoreCertificateGet, std::ref(app)));
1224 
1225     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1226         .privileges(redfish::privileges::deleteCertificate)
1227         .methods(boost::beast::http::verb::delete_)(
1228             std::bind_front(handleTrustStoreCertificateDelete, std::ref(app)));
1229 } // requestRoutesTrustStoreCertificate
1230 } // namespace redfish
1231