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