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