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