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