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