xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 7a1dbc4803bf78bfc0c574e6676b3c5def4cdae3)
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 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher;
573 /**
574  * @brief Read data from CSR D-bus object and set to response
575  *
576  * @param[in] asyncResp Shared pointer to the response message
577  * @param[in] certURI Link to certifiate collection URI
578  * @param[in] service D-Bus service name
579  * @param[in] certObjPath certificate D-Bus object path
580  * @param[in] csrObjPath CSR D-Bus object path
581  * @return None
582  */
583 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
584                    const std::string& certURI, const std::string& service,
585                    const std::string& certObjPath,
586                    const std::string& csrObjPath)
587 {
588     BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath
589                      << " CSRObjectPath=" << csrObjPath
590                      << " service=" << service;
591     crow::connections::systemBus->async_method_call(
592         [asyncResp, certURI](const boost::system::error_code ec,
593                              const std::string& csr) {
594         if (ec)
595         {
596             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
597             messages::internalError(asyncResp->res);
598             return;
599         }
600         if (csr.empty())
601         {
602             BMCWEB_LOG_ERROR << "CSR read is empty";
603             messages::internalError(asyncResp->res);
604             return;
605         }
606         asyncResp->res.jsonValue["CSRString"] = csr;
607         asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
608             certURI;
609         },
610         service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
611 }
612 
613 inline void
614     handleGenerateCSRAction(App& app, const crow::Request& req,
615                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
616 {
617     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
618     {
619         return;
620     }
621     static const int rsaKeyBitLength = 2048;
622 
623     // Required parameters
624     std::string city;
625     std::string commonName;
626     std::string country;
627     std::string organization;
628     std::string organizationalUnit;
629     std::string state;
630     nlohmann::json certificateCollection;
631 
632     // Optional parameters
633     std::optional<std::vector<std::string>> optAlternativeNames =
634         std::vector<std::string>();
635     std::optional<std::string> optContactPerson = "";
636     std::optional<std::string> optChallengePassword = "";
637     std::optional<std::string> optEmail = "";
638     std::optional<std::string> optGivenName = "";
639     std::optional<std::string> optInitials = "";
640     std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
641     std::optional<std::string> optKeyCurveId = "secp384r1";
642     std::optional<std::string> optKeyPairAlgorithm = "EC";
643     std::optional<std::vector<std::string>> optKeyUsage =
644         std::vector<std::string>();
645     std::optional<std::string> optSurname = "";
646     std::optional<std::string> optUnstructuredName = "";
647     if (!json_util::readJsonAction(
648             req, asyncResp->res, "City", city, "CommonName", commonName,
649             "ContactPerson", optContactPerson, "Country", country,
650             "Organization", organization, "OrganizationalUnit",
651             organizationalUnit, "State", state, "CertificateCollection",
652             certificateCollection, "AlternativeNames", optAlternativeNames,
653             "ChallengePassword", optChallengePassword, "Email", optEmail,
654             "GivenName", optGivenName, "Initials", optInitials, "KeyBitLength",
655             optKeyBitLength, "KeyCurveId", optKeyCurveId, "KeyPairAlgorithm",
656             optKeyPairAlgorithm, "KeyUsage", optKeyUsage, "Surname", optSurname,
657             "UnstructuredName", optUnstructuredName))
658     {
659         return;
660     }
661 
662     // bmcweb has no way to store or decode a private key challenge
663     // password, which will likely cause bmcweb to crash on startup
664     // if this is not set on a post so not allowing the user to set
665     // value
666     if (!optChallengePassword->empty())
667     {
668         messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR",
669                                               "ChallengePassword");
670         return;
671     }
672 
673     std::string certURI;
674     if (!redfish::json_util::readJson(certificateCollection, asyncResp->res,
675                                       "@odata.id", certURI))
676     {
677         return;
678     }
679 
680     std::string objectPath;
681     std::string service;
682     if (certURI.starts_with(
683             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
684     {
685         objectPath = certs::httpsObjectPath;
686         service = certs::httpsServiceName;
687     }
688     else if (certURI.starts_with(
689                  "/redfish/v1/AccountService/LDAP/Certificates"))
690     {
691         objectPath = certs::ldapObjectPath;
692         service = certs::ldapServiceName;
693     }
694     else
695     {
696         messages::actionParameterNotSupported(
697             asyncResp->res, "CertificateCollection", "GenerateCSR");
698         return;
699     }
700 
701     // supporting only EC and RSA algorithm
702     if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
703     {
704         messages::actionParameterNotSupported(
705             asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
706         return;
707     }
708 
709     // supporting only 2048 key bit length for RSA algorithm due to
710     // time consumed in generating private key
711     if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength)
712     {
713         messages::propertyValueNotInList(
714             asyncResp->res, std::to_string(*optKeyBitLength), "KeyBitLength");
715         return;
716     }
717 
718     // validate KeyUsage supporting only 1 type based on URL
719     if (certURI.starts_with(
720             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
721     {
722         if (optKeyUsage->empty())
723         {
724             optKeyUsage->push_back("ServerAuthentication");
725         }
726         else if (optKeyUsage->size() == 1)
727         {
728             if ((*optKeyUsage)[0] != "ServerAuthentication")
729             {
730                 messages::propertyValueNotInList(asyncResp->res,
731                                                  (*optKeyUsage)[0], "KeyUsage");
732                 return;
733             }
734         }
735         else
736         {
737             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
738                                                   "GenerateCSR");
739             return;
740         }
741     }
742     else if (certURI.starts_with(
743                  "/redfish/v1/AccountService/LDAP/Certificates"))
744     {
745         if (optKeyUsage->empty())
746         {
747             optKeyUsage->push_back("ClientAuthentication");
748         }
749         else if (optKeyUsage->size() == 1)
750         {
751             if ((*optKeyUsage)[0] != "ClientAuthentication")
752             {
753                 messages::propertyValueNotInList(asyncResp->res,
754                                                  (*optKeyUsage)[0], "KeyUsage");
755                 return;
756             }
757         }
758         else
759         {
760             messages::actionParameterNotSupported(asyncResp->res, "KeyUsage",
761                                                   "GenerateCSR");
762             return;
763         }
764     }
765 
766     // Only allow one CSR matcher at a time so setting retry
767     // time-out and timer expiry to 10 seconds for now.
768     static const int timeOut = 10;
769     if (csrMatcher)
770     {
771         messages::serviceTemporarilyUnavailable(asyncResp->res,
772                                                 std::to_string(timeOut));
773         return;
774     }
775 
776     // Make this static so it survives outside this method
777     static boost::asio::steady_timer timeout(*req.ioService);
778     timeout.expires_after(std::chrono::seconds(timeOut));
779     timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
780         csrMatcher = nullptr;
781         if (ec)
782         {
783             // operation_aborted is expected if timer is canceled
784             // before completion.
785             if (ec != boost::asio::error::operation_aborted)
786             {
787                 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
788             }
789             return;
790         }
791         BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
792         messages::internalError(asyncResp->res);
793     });
794 
795     // create a matcher to wait on CSR object
796     BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
797     std::string match("type='signal',"
798                       "interface='org.freedesktop.DBus.ObjectManager',"
799                       "path='" +
800                       objectPath +
801                       "',"
802                       "member='InterfacesAdded'");
803     csrMatcher = std::make_unique<sdbusplus::bus::match_t>(
804         *crow::connections::systemBus, match,
805         [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) {
806         timeout.cancel();
807         if (m.is_method_error())
808         {
809             BMCWEB_LOG_ERROR << "Dbus method error!!!";
810             messages::internalError(asyncResp->res);
811             return;
812         }
813 
814         dbus::utility::DBusInteracesMap interfacesProperties;
815 
816         sdbusplus::message::object_path csrObjectPath;
817         m.read(csrObjectPath, interfacesProperties);
818         BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
819         for (const auto& interface : interfacesProperties)
820         {
821             if (interface.first == "xyz.openbmc_project.Certs.CSR")
822             {
823                 getCSR(asyncResp, certURI, service, objectPath,
824                        csrObjectPath.str);
825                 break;
826             }
827         }
828         });
829     crow::connections::systemBus->async_method_call(
830         [asyncResp](const boost::system::error_code ec, const std::string&) {
831         if (ec)
832         {
833             BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
834             messages::internalError(asyncResp->res);
835             return;
836         }
837         },
838         service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
839         "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
840         commonName, *optContactPerson, country, *optEmail, *optGivenName,
841         *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm,
842         *optKeyUsage, organization, organizationalUnit, state, *optSurname,
843         *optUnstructuredName);
844 }
845 
846 inline void requestRoutesCertificateService(App& app)
847 {
848     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
849         .privileges(redfish::privileges::getCertificateService)
850         .methods(boost::beast::http::verb::get)(
851             std::bind_front(handleCertificateServiceGet, std::ref(app)));
852 
853     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
854         .privileges(redfish::privileges::getCertificateLocations)
855         .methods(boost::beast::http::verb::get)(
856             std::bind_front(handleCertificateLocationsGet, std::ref(app)));
857 
858     BMCWEB_ROUTE(
859         app,
860         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
861         .privileges(redfish::privileges::postCertificateService)
862         .methods(boost::beast::http::verb::post)(
863             std::bind_front(handleReplaceCertificateAction, std::ref(app)));
864 
865     BMCWEB_ROUTE(
866         app,
867         "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
868         .privileges(redfish::privileges::postCertificateService)
869         .methods(boost::beast::http::verb::post)(
870             std::bind_front(handleGenerateCSRAction, std::ref(app)));
871 } // requestRoutesCertificateService
872 
873 inline void handleHTTPSCertificateCollectionGet(
874     App& app, const crow::Request& req,
875     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
876 {
877     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
878     {
879         return;
880     }
881 
882     asyncResp->res.jsonValue["@odata.id"] =
883         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
884     asyncResp->res.jsonValue["@odata.type"] =
885         "#CertificateCollection.CertificateCollection";
886     asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
887     asyncResp->res.jsonValue["Description"] =
888         "A Collection of HTTPS certificate instances";
889 
890     getCertificateList(asyncResp, certs::httpsObjectPath,
891                        "/Members"_json_pointer,
892                        "/Members@odata.count"_json_pointer);
893 }
894 
895 inline void handleHTTPSCertificateCollectionPost(
896     App& app, const crow::Request& req,
897     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
898 {
899     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
900     {
901         return;
902     }
903     BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
904 
905     asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
906     asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
907 
908     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
909 
910     if (certFileBody.empty())
911     {
912         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
913         messages::unrecognizedRequestBody(asyncResp->res);
914         return;
915     }
916 
917     std::shared_ptr<CertificateFile> certFile =
918         std::make_shared<CertificateFile>(certFileBody);
919 
920     crow::connections::systemBus->async_method_call(
921         [asyncResp, certFile](const boost::system::error_code ec,
922                               const std::string& objectPath) {
923         if (ec)
924         {
925             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
926             messages::internalError(asyncResp->res);
927             return;
928         }
929 
930         sdbusplus::message::object_path path(objectPath);
931         std::string certId = path.filename();
932         const boost::urls::url certURL = crow::utility::urlFromPieces(
933             "redfish", "v1", "Managers", "bmc", "NetworkProtocol", "HTTPS",
934             "Certificates", certId);
935         getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName,
936                                  certId, certURL, "HTTPS Certificate");
937         BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
938                          << certFile->getCertFilePath();
939         },
940         certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf,
941         "Install", certFile->getCertFilePath());
942 }
943 
944 inline void handleHTTPSCertificateGet(
945     App& app, const crow::Request& req,
946     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
947 {
948     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
949     {
950         return;
951     }
952 
953     BMCWEB_LOG_DEBUG << "HTTPS Certificate ID=" << id;
954     const boost::urls::url certURL = crow::utility::urlFromPieces(
955         "redfish", "v1", "Managers", "bmc", "NetworkProtocol", "HTTPS",
956         "Certificates", id);
957     std::string objPath =
958         sdbusplus::message::object_path(certs::httpsObjectPath) / id;
959     getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, id,
960                              certURL, "HTTPS Certificate");
961 }
962 
963 inline void requestRoutesHTTPSCertificate(App& app)
964 {
965     BMCWEB_ROUTE(app,
966                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
967         .privileges(redfish::privileges::getCertificateCollection)
968         .methods(boost::beast::http::verb::get)(std::bind_front(
969             handleHTTPSCertificateCollectionGet, std::ref(app)));
970 
971     BMCWEB_ROUTE(app,
972                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
973         .privileges(redfish::privileges::postCertificateCollection)
974         .methods(boost::beast::http::verb::post)(std::bind_front(
975             handleHTTPSCertificateCollectionPost, std::ref(app)));
976 
977     BMCWEB_ROUTE(
978         app,
979         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
980         .privileges(redfish::privileges::getCertificate)
981         .methods(boost::beast::http::verb::get)(
982             std::bind_front(handleHTTPSCertificateGet, std::ref(app)));
983 }
984 
985 inline void handleLDAPCertificateCollectionGet(
986     App& app, const crow::Request& req,
987     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
988 {
989     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
990     {
991         return;
992     }
993 
994     asyncResp->res.jsonValue["@odata.id"] =
995         "/redfish/v1/AccountService/LDAP/Certificates";
996     asyncResp->res.jsonValue["@odata.type"] =
997         "#CertificateCollection.CertificateCollection";
998     asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
999     asyncResp->res.jsonValue["Description"] =
1000         "A Collection of LDAP certificate instances";
1001 
1002     getCertificateList(asyncResp, certs::ldapObjectPath,
1003                        "/Members"_json_pointer,
1004                        "/Members@odata.count"_json_pointer);
1005 }
1006 
1007 inline void handleLDAPCertificateCollectionPost(
1008     App& app, const crow::Request& req,
1009     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1010 {
1011     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1012     {
1013         return;
1014     }
1015     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1016 
1017     if (certFileBody.empty())
1018     {
1019         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1020         messages::unrecognizedRequestBody(asyncResp->res);
1021         return;
1022     }
1023 
1024     std::shared_ptr<CertificateFile> certFile =
1025         std::make_shared<CertificateFile>(certFileBody);
1026 
1027     crow::connections::systemBus->async_method_call(
1028         [asyncResp, certFile](const boost::system::error_code ec,
1029                               const std::string& objectPath) {
1030         if (ec)
1031         {
1032             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1033             messages::internalError(asyncResp->res);
1034             return;
1035         }
1036 
1037         sdbusplus::message::object_path path(objectPath);
1038         std::string certId = path.filename();
1039         const boost::urls::url certURL = crow::utility::urlFromPieces(
1040             "redfish", "v1", "AccountService", "LDAP", "Certificates", certId);
1041         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1042                                  certId, certURL, "LDAP Certificate");
1043         BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1044                          << certFile->getCertFilePath();
1045         },
1046         certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf,
1047         "Install", certFile->getCertFilePath());
1048 }
1049 
1050 inline void handleLDAPCertificateGet(
1051     App& app, const crow::Request& req,
1052     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1053 {
1054     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1055     {
1056         return;
1057     }
1058 
1059     BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id;
1060     const boost::urls::url certURL = crow::utility::urlFromPieces(
1061         "redfish", "v1", "AccountService", "LDAP", "Certificates", id);
1062     std::string objPath =
1063         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1064     getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1065                              certURL, "LDAP Certificate");
1066 }
1067 
1068 inline void handleLDAPCertificateDelete(
1069     App& app, const crow::Request& req,
1070     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1071 {
1072     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1073     {
1074         return;
1075     }
1076 
1077     BMCWEB_LOG_DEBUG << "Delete LDAP Certificate ID=" << id;
1078     std::string objPath =
1079         sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1080 
1081     deleteCertificate(asyncResp, certs::ldapServiceName, objPath);
1082 }
1083 
1084 inline void requestRoutesLDAPCertificate(App& app)
1085 {
1086     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1087         .privileges(redfish::privileges::getCertificateCollection)
1088         .methods(boost::beast::http::verb::get)(
1089             std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app)));
1090 
1091     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1092         .privileges(redfish::privileges::postCertificateCollection)
1093         .methods(boost::beast::http::verb::post)(std::bind_front(
1094             handleLDAPCertificateCollectionPost, std::ref(app)));
1095 
1096     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1097         .privileges(redfish::privileges::getCertificate)
1098         .methods(boost::beast::http::verb::get)(
1099             std::bind_front(handleLDAPCertificateGet, std::ref(app)));
1100 
1101     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1102         .privileges(redfish::privileges::deleteCertificate)
1103         .methods(boost::beast::http::verb::delete_)(
1104             std::bind_front(handleLDAPCertificateDelete, std::ref(app)));
1105 } // requestRoutesLDAPCertificate
1106 
1107 inline void handleTrustStoreCertificateCollectionGet(
1108     App& app, const crow::Request& req,
1109     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1110 {
1111     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1112     {
1113         return;
1114     }
1115 
1116     asyncResp->res.jsonValue["@odata.id"] =
1117         "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1118     asyncResp->res.jsonValue["@odata.type"] =
1119         "#CertificateCollection.CertificateCollection";
1120     asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1121     asyncResp->res.jsonValue["Description"] =
1122         "A Collection of TrustStore certificate instances";
1123 
1124     getCertificateList(asyncResp, certs::authorityObjectPath,
1125                        "/Members"_json_pointer,
1126                        "/Members@odata.count"_json_pointer);
1127 }
1128 
1129 inline void handleTrustStoreCertificateCollectionPost(
1130     App& app, const crow::Request& req,
1131     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1132 {
1133     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1134     {
1135         return;
1136     }
1137     std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1138 
1139     if (certFileBody.empty())
1140     {
1141         BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1142         messages::unrecognizedRequestBody(asyncResp->res);
1143         return;
1144     }
1145 
1146     std::shared_ptr<CertificateFile> certFile =
1147         std::make_shared<CertificateFile>(certFileBody);
1148     crow::connections::systemBus->async_method_call(
1149         [asyncResp, certFile](const boost::system::error_code ec,
1150                               const std::string& objectPath) {
1151         if (ec)
1152         {
1153             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1154             messages::internalError(asyncResp->res);
1155             return;
1156         }
1157 
1158         sdbusplus::message::object_path path(objectPath);
1159         std::string certId = path.filename();
1160         const boost::urls::url certURL =
1161             crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc",
1162                                          "Truststore", "Certificates", certId);
1163         getCertificateProperties(asyncResp, objectPath,
1164                                  certs::authorityServiceName, certId, certURL,
1165                                  "TrustStore Certificate");
1166         BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1167                          << certFile->getCertFilePath();
1168         },
1169         certs::authorityServiceName, certs::authorityObjectPath,
1170         certs::certInstallIntf, "Install", certFile->getCertFilePath());
1171 }
1172 
1173 inline void handleTrustStoreCertificateGet(
1174     App& app, const crow::Request& req,
1175     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1176 {
1177     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1178     {
1179         return;
1180     }
1181 
1182     BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id;
1183     const boost::urls::url certURL = crow::utility::urlFromPieces(
1184         "redfish", "v1", "Managers", "bmc", "Truststore", "Certificates", id);
1185     std::string objPath =
1186         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1187     getCertificateProperties(asyncResp, objPath, certs::authorityServiceName,
1188                              id, certURL, "TrustStore Certificate");
1189 }
1190 
1191 inline void handleTrustStoreCertificateDelete(
1192     App& app, const crow::Request& req,
1193     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id)
1194 {
1195     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1196     {
1197         return;
1198     }
1199 
1200     BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id;
1201     std::string objPath =
1202         sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1203 
1204     deleteCertificate(asyncResp, certs::authorityServiceName, objPath);
1205 }
1206 
1207 inline void requestRoutesTrustStoreCertificate(App& app)
1208 {
1209     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1210         .privileges(redfish::privileges::getCertificate)
1211         .methods(boost::beast::http::verb::get)(std::bind_front(
1212             handleTrustStoreCertificateCollectionGet, std::ref(app)));
1213 
1214     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1215         .privileges(redfish::privileges::postCertificateCollection)
1216         .methods(boost::beast::http::verb::post)(std::bind_front(
1217             handleTrustStoreCertificateCollectionPost, std::ref(app)));
1218 
1219     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1220         .privileges(redfish::privileges::getCertificate)
1221         .methods(boost::beast::http::verb::get)(
1222             std::bind_front(handleTrustStoreCertificateGet, std::ref(app)));
1223 
1224     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1225         .privileges(redfish::privileges::deleteCertificate)
1226         .methods(boost::beast::http::verb::delete_)(
1227             std::bind_front(handleTrustStoreCertificateDelete, std::ref(app)));
1228 } // requestRoutesTrustStoreCertificate
1229 } // namespace redfish
1230