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