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