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