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     // Make this static so it survives outside this method
770     static boost::asio::steady_timer timeout(*req.ioService);
771     timeout.expires_after(std::chrono::seconds(timeOut));
772     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
773         csrMatcher = nullptr;
774         if (ec)
775         {
776             // operation_aborted is expected if timer is canceled
777             // before completion.
778             if (ec != boost::asio::error::operation_aborted)
779             {
780                 BMCWEB_LOG_ERROR("Async_wait failed {}", ec);
781             }
782             return;
783         }
784         BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR");
785         messages::internalError(asyncResp->res);
786     });
787 
788     // create a matcher to wait on CSR object
789     BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath);
790     std::string match("type='signal',"
791                       "interface='org.freedesktop.DBus.ObjectManager',"
792                       "path='" +
793                       objectPath +
794                       "',"
795                       "member='InterfacesAdded'");
796     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
797         *crow::connections::systemBus, match,
798         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
799         timeout.cancel();
800         if (m.is_method_error())
801         {
802             BMCWEB_LOG_ERROR("Dbus method error!!!");
803             messages::internalError(asyncResp->res);
804             return;
805         }
806 
807         dbus::utility::DBusInterfacesMap interfacesProperties;
808 
809         sdbusplus::message::object_path csrObjectPath;
810         m.read(csrObjectPath, interfacesProperties);
811         BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str);
812         for (const auto& interface : interfacesProperties)
813         {
814             if (interface.first == "xyz.openbmc_project.Certs.CSR")
815             {
816                 getCSR(asyncResp, certURI, service, objectPath,
817                        csrObjectPath.str);
818                 break;
819             }
820         }
821     });
822     crow::connections::systemBus->async_method_call(
823         [asyncResp](const boost::system::error_code& ec, const std::string&) {
824         if (ec)
825         {
826             BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message());
827             messages::internalError(asyncResp->res);
828             return;
829         }
830     },
831         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
832         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
833         commonName, *optContactPerson, country, *optEmail, *optGivenName,
834         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
835         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
836         *optUnstructuredName);
837 }
838 
839 inline void requestRoutesCertificateService(App& app)
840 {
841     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
842         .privileges(redfish::privileges::getCertificateService)
843         .methods(boost::beast::http::verb::get)(
844             std::bind_front(handleCertificateServiceGet, std::ref(app)));
845 
846     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
847         .privileges(redfish::privileges::getCertificateLocations)
848         .methods(boost::beast::http::verb::get)(
849             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
850 
851     BMCWEB_ROUTE(
852         app,
853         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
854         .privileges(redfish::privileges::postCertificateService)
855         .methods(boost::beast::http::verb::post)(
856             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
857 
858     BMCWEB_ROUTE(
859         app,
860         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
861         .privileges(redfish::privileges::postCertificateService)
862         .methods(boost::beast::http::verb::post)(
863             std::bind_front(handleGenerateCSRAction, std::ref(app)));
864 } // requestRoutesCertificateService
865 
866 inline void handleHTTPSCertificateCollectionGet(
867     App& app, const crow::Request& req,
868     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
869 {
870     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
871     {
872         return;
873     }
874 
875     asyncResp->res.jsonValue["@odata.id"] =
876         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
877     asyncResp->res.jsonValue["@odata.type"] =
878         "#CertificateCollection.CertificateCollection";
879     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
880     asyncResp->res.jsonValue["Description"] =
881         "A Collection of HTTPS certificate instances";
882 
883     getCertificateList(asyncResp, certs::httpsObjectPath,
884                        "/Members"_json_pointer,
885                        "/Members@odata.count"_json_pointer);
886 }
887 
888 inline void handleHTTPSCertificateCollectionPost(
889     App& app, const crow::Request& req,
890     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
891 {
892     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
893     {
894         return;
895     }
896     BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost");
897 
898     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
899     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
900 
901     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
902 
903     if (certHttpBody.empty())
904     {
905         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
906         messages::unrecognizedRequestBody(asyncResp->res);
907         return;
908     }
909 
910     std::shared_ptr<CertificateFile> certFile =
911         std::make_shared<CertificateFile>(certHttpBody);
912 
913     crow::connections::systemBus->async_method_call(
914         [asyncResp, certFile](const boost::system::error_code& ec,
915                               const std::string& objectPath) {
916         if (ec)
917         {
918             BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
919             messages::internalError(asyncResp->res);
920             return;
921         }
922 
923         sdbusplus::message::object_path path(objectPath);
924         std::string certId = path.filename();
925         const boost::urls::url certURL = boost::urls::format(
926             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}",
927             certId);
928         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
929                                  certId, certURL, "HTTPS Certificate");
930         BMCWEB_LOG_DEBUG("HTTPS certificate install file={}",
931                          certFile->getCertFilePath());
932     },
933         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
934         "Install", certFile->getCertFilePath());
935 }
936 
937 inline void handleHTTPSCertificateGet(
938     App& app, const crow::Request& req,
939     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
940 {
941     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
942     {
943         return;
944     }
945 
946     BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", id);
947     const boost::urls::url certURL = boost::urls::format(
948         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}", id);
949     std::string objPath =
950         sdbusplus::message::object_path(certs::httpsObjectPath) / id;
951     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, id,
952                              certURL, "HTTPS Certificate");
953 }
954 
955 inline void requestRoutesHTTPSCertificate(App& app)
956 {
957     BMCWEB_ROUTE(app,
958                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
959         .privileges(redfish::privileges::getCertificateCollection)
960         .methods(boost::beast::http::verb::get)(std::bind_front(
961             handleHTTPSCertificateCollectionGet, std::ref(app)));
962 
963     BMCWEB_ROUTE(app,
964                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
965         .privileges(redfish::privileges::postCertificateCollection)
966         .methods(boost::beast::http::verb::post)(std::bind_front(
967             handleHTTPSCertificateCollectionPost, std::ref(app)));
968 
969     BMCWEB_ROUTE(
970         app,
971         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
972         .privileges(redfish::privileges::getCertificate)
973         .methods(boost::beast::http::verb::get)(
974             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
975 }
976 
977 inline void handleLDAPCertificateCollectionGet(
978     App& app, const crow::Request& req,
979     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
980 {
981     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
982     {
983         return;
984     }
985 
986     asyncResp->res.jsonValue["@odata.id"] =
987         "/redfish/v1/AccountService/LDAP/Certificates";
988     asyncResp->res.jsonValue["@odata.type"] =
989         "#CertificateCollection.CertificateCollection";
990     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
991     asyncResp->res.jsonValue["Description"] =
992         "A Collection of LDAP certificate instances";
993 
994     getCertificateList(asyncResp, certs::ldapObjectPath,
995                        "/Members"_json_pointer,
996                        "/Members@odata.count"_json_pointer);
997 }
998 
999 inline void handleLDAPCertificateCollectionPost(
1000     App& app, const crow::Request& req,
1001     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1002 {
1003     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1004     {
1005         return;
1006     }
1007     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1008 
1009     if (certHttpBody.empty())
1010     {
1011         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1012         messages::unrecognizedRequestBody(asyncResp->res);
1013         return;
1014     }
1015 
1016     std::shared_ptr<CertificateFile> certFile =
1017         std::make_shared<CertificateFile>(certHttpBody);
1018 
1019     crow::connections::systemBus->async_method_call(
1020         [asyncResp, certFile](const boost::system::error_code& ec,
1021                               const std::string& objectPath) {
1022         if (ec)
1023         {
1024             BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1025             messages::internalError(asyncResp->res);
1026             return;
1027         }
1028 
1029         sdbusplus::message::object_path path(objectPath);
1030         std::string certId = path.filename();
1031         const boost::urls::url certURL = boost::urls::format(
1032             "/redfish/v1/AccountService/LDAP/Certificates/{}", certId);
1033         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1034                                  certId, certURL, "LDAP Certificate");
1035         BMCWEB_LOG_DEBUG("LDAP certificate install file={}",
1036                          certFile->getCertFilePath());
1037     },
1038         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1039         "Install", certFile->getCertFilePath());
1040 }
1041 
1042 inline void handleLDAPCertificateGet(
1043     App& app, const crow::Request& req,
1044     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1045 {
1046     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1047     {
1048         return;
1049     }
1050 
1051     BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id);
1052     const boost::urls::url certURL = boost::urls::format(
1053         "/redfish/v1/AccountService/LDAP/Certificates/{}", id);
1054     std::string objPath =
1055         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1056     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1057                              certURL, "LDAP Certificate");
1058 }
1059 
1060 inline void handleLDAPCertificateDelete(
1061     App& app, const crow::Request& req,
1062     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1063 {
1064     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1065     {
1066         return;
1067     }
1068 
1069     BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id);
1070     std::string objPath =
1071         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1072 
1073     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1074 }
1075 
1076 inline void requestRoutesLDAPCertificate(App& app)
1077 {
1078     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1079         .privileges(redfish::privileges::getCertificateCollection)
1080         .methods(boost::beast::http::verb::get)(
1081             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1082 
1083     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1084         .privileges(redfish::privileges::postCertificateCollection)
1085         .methods(boost::beast::http::verb::post)(std::bind_front(
1086             handleLDAPCertificateCollectionPost, std::ref(app)));
1087 
1088     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1089         .privileges(redfish::privileges::getCertificate)
1090         .methods(boost::beast::http::verb::get)(
1091             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1092 
1093     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1094         .privileges(redfish::privileges::deleteCertificate)
1095         .methods(boost::beast::http::verb::delete_)(
1096             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1097 } // requestRoutesLDAPCertificate
1098 
1099 inline void handleTrustStoreCertificateCollectionGet(
1100     App& app, const crow::Request& req,
1101     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1102 {
1103     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1104     {
1105         return;
1106     }
1107 
1108     asyncResp->res.jsonValue["@odata.id"] =
1109         "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1110     asyncResp->res.jsonValue["@odata.type"] =
1111         "#CertificateCollection.CertificateCollection";
1112     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1113     asyncResp->res.jsonValue["Description"] =
1114         "A Collection of TrustStore certificate instances";
1115 
1116     getCertificateList(asyncResp, certs::authorityObjectPath,
1117                        "/Members"_json_pointer,
1118                        "/Members@odata.count"_json_pointer);
1119 }
1120 
1121 inline void handleTrustStoreCertificateCollectionPost(
1122     App& app, const crow::Request& req,
1123     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1124 {
1125     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1126     {
1127         return;
1128     }
1129     std::string certHttpBody = getCertificateFromReqBody(asyncResp, req);
1130 
1131     if (certHttpBody.empty())
1132     {
1133         BMCWEB_LOG_ERROR("Cannot get certificate from request body.");
1134         messages::unrecognizedRequestBody(asyncResp->res);
1135         return;
1136     }
1137 
1138     std::shared_ptr<CertificateFile> certFile =
1139         std::make_shared<CertificateFile>(certHttpBody);
1140     crow::connections::systemBus->async_method_call(
1141         [asyncResp, certFile](const boost::system::error_code& ec,
1142                               const std::string& objectPath) {
1143         if (ec)
1144         {
1145             BMCWEB_LOG_ERROR("DBUS response error: {}", ec);
1146             messages::internalError(asyncResp->res);
1147             return;
1148         }
1149 
1150         sdbusplus::message::object_path path(objectPath);
1151         std::string certId = path.filename();
1152         const boost::urls::url certURL = boost::urls::format(
1153             "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", certId);
1154         getCertificateProperties(asyncResp, objectPath,
1155                                  certs::authorityServiceName, certId, certURL,
1156                                  "TrustStore Certificate");
1157         BMCWEB_LOG_DEBUG("TrustStore certificate install file={}",
1158                          certFile->getCertFilePath());
1159     },
1160         certs::authorityServiceName, certs::authorityObjectPath,
1161         certs::certInstallIntf, "Install", certFile->getCertFilePath());
1162 }
1163 
1164 inline void handleTrustStoreCertificateGet(
1165     App& app, const crow::Request& req,
1166     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1167 {
1168     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1169     {
1170         return;
1171     }
1172 
1173     BMCWEB_LOG_DEBUG("Truststore Certificate ID={}", id);
1174     const boost::urls::url certURL = boost::urls::format(
1175         "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", id);
1176     std::string objPath =
1177         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1178     getCertificateProperties(asyncResp, objPath, certs::authorityServiceName,
1179                              id, certURL, "TrustStore Certificate");
1180 }
1181 
1182 inline void handleTrustStoreCertificateDelete(
1183     App& app, const crow::Request& req,
1184     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1185 {
1186     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1187     {
1188         return;
1189     }
1190 
1191     BMCWEB_LOG_DEBUG("Delete TrustStore Certificate ID={}", id);
1192     std::string objPath =
1193         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1194 
1195     deleteCertificate(asyncResp, certs::authorityServiceName, objPath);
1196 }
1197 
1198 inline void requestRoutesTrustStoreCertificate(App& app)
1199 {
1200     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1201         .privileges(redfish::privileges::getCertificate)
1202         .methods(boost::beast::http::verb::get)(std::bind_front(
1203             handleTrustStoreCertificateCollectionGet, std::ref(app)));
1204 
1205     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1206         .privileges(redfish::privileges::postCertificateCollection)
1207         .methods(boost::beast::http::verb::post)(std::bind_front(
1208             handleTrustStoreCertificateCollectionPost, std::ref(app)));
1209 
1210     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1211         .privileges(redfish::privileges::getCertificate)
1212         .methods(boost::beast::http::verb::get)(
1213             std::bind_front(handleTrustStoreCertificateGet, std::ref(app)));
1214 
1215     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1216         .privileges(redfish::privileges::deleteCertificate)
1217         .methods(boost::beast::http::verb::delete_)(
1218             std::bind_front(handleTrustStoreCertificateDelete, std::ref(app)));
1219 } // requestRoutesTrustStoreCertificate
1220 } // namespace redfish
1221