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