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