xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision d3f92ce7a9fbff1e39b6018f753c919639afc1f7)
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     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::match> 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 (boost::starts_with(
322                 certURI,
323                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
324         {
325             objectPath = certs::httpsObjectPath;
326             service = certs::httpsServiceName;
327         }
328         else if (boost::starts_with(
329                      certURI, "/redfish/v1/AccountService/LDAP/Certificates"))
330         {
331             objectPath = certs::ldapObjectPath;
332             service = certs::ldapServiceName;
333         }
334         else
335         {
336             messages::actionParameterNotSupported(
337                 asyncResp->res, "CertificateCollection", "GenerateCSR");
338             return;
339         }
340 
341         // supporting only EC and RSA algorithm
342         if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
343         {
344             messages::actionParameterNotSupported(
345                 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
346             return;
347         }
348 
349         // supporting only 2048 key bit length for RSA algorithm due to
350         // time consumed in generating private key
351         if (*optKeyPairAlgorithm == "RSA" &&
352             *optKeyBitLength != rsaKeyBitLength)
353         {
354             messages::propertyValueNotInList(asyncResp->res,
355                                              std::to_string(*optKeyBitLength),
356                                              "KeyBitLength");
357             return;
358         }
359 
360         // validate KeyUsage supporting only 1 type based on URL
361         if (boost::starts_with(
362                 certURI,
363                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
364         {
365             if (optKeyUsage->empty())
366             {
367                 optKeyUsage->push_back("ServerAuthentication");
368             }
369             else if (optKeyUsage->size() == 1)
370             {
371                 if ((*optKeyUsage)[0] != "ServerAuthentication")
372                 {
373                     messages::propertyValueNotInList(
374                         asyncResp->res, (*optKeyUsage)[0], "KeyUsage");
375                     return;
376                 }
377             }
378             else
379             {
380                 messages::actionParameterNotSupported(
381                     asyncResp->res, "KeyUsage", "GenerateCSR");
382                 return;
383             }
384         }
385         else if (boost::starts_with(
386                      certURI, "/redfish/v1/AccountService/LDAP/Certificates"))
387         {
388             if (optKeyUsage->empty())
389             {
390                 optKeyUsage->push_back("ClientAuthentication");
391             }
392             else if (optKeyUsage->size() == 1)
393             {
394                 if ((*optKeyUsage)[0] != "ClientAuthentication")
395                 {
396                     messages::propertyValueNotInList(
397                         asyncResp->res, (*optKeyUsage)[0], "KeyUsage");
398                     return;
399                 }
400             }
401             else
402             {
403                 messages::actionParameterNotSupported(
404                     asyncResp->res, "KeyUsage", "GenerateCSR");
405                 return;
406             }
407         }
408 
409         // Only allow one CSR matcher at a time so setting retry
410         // time-out and timer expiry to 10 seconds for now.
411         static const int timeOut = 10;
412         if (csrMatcher)
413         {
414             messages::serviceTemporarilyUnavailable(asyncResp->res,
415                                                     std::to_string(timeOut));
416             return;
417         }
418 
419         // Make this static so it survives outside this method
420         static boost::asio::steady_timer timeout(*req.ioService);
421         timeout.expires_after(std::chrono::seconds(timeOut));
422         timeout.async_wait([asyncResp](const boost::system::error_code& ec) {
423             csrMatcher = nullptr;
424             if (ec)
425             {
426                 // operation_aborted is expected if timer is canceled
427                 // before completion.
428                 if (ec != boost::asio::error::operation_aborted)
429                 {
430                     BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
431                 }
432                 return;
433             }
434             BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
435             messages::internalError(asyncResp->res);
436         });
437 
438         // create a matcher to wait on CSR object
439         BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
440         std::string match("type='signal',"
441                           "interface='org.freedesktop.DBus.ObjectManager',"
442                           "path='" +
443                           objectPath +
444                           "',"
445                           "member='InterfacesAdded'");
446         csrMatcher = std::make_unique<sdbusplus::bus::match::match>(
447             *crow::connections::systemBus, match,
448             [asyncResp, service, objectPath,
449              certURI](sdbusplus::message::message& m) {
450             timeout.cancel();
451             if (m.is_method_error())
452             {
453                 BMCWEB_LOG_ERROR << "Dbus method error!!!";
454                 messages::internalError(asyncResp->res);
455                 return;
456             }
457 
458             dbus::utility::DBusInteracesMap interfacesProperties;
459 
460             sdbusplus::message::object_path csrObjectPath;
461             m.read(csrObjectPath, interfacesProperties);
462             BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
463             for (auto& interface : interfacesProperties)
464             {
465                 if (interface.first == "xyz.openbmc_project.Certs.CSR")
466                 {
467                     getCSR(asyncResp, certURI, service, objectPath,
468                            csrObjectPath.str);
469                     break;
470                 }
471             }
472             });
473         crow::connections::systemBus->async_method_call(
474             [asyncResp](const boost::system::error_code ec,
475                         const std::string&) {
476             if (ec)
477             {
478                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message();
479                 messages::internalError(asyncResp->res);
480                 return;
481             }
482             },
483             service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
484             "GenerateCSR", *optAlternativeNames, *optChallengePassword, city,
485             commonName, *optContactPerson, country, *optEmail, *optGivenName,
486             *optInitials, *optKeyBitLength, *optKeyCurveId,
487             *optKeyPairAlgorithm, *optKeyUsage, organization,
488             organizationalUnit, state, *optSurname, *optUnstructuredName);
489         });
490 } // requestRoutesCertificateActionGenerateCSR
491 
492 /**
493  * @brief Parse and update Certificate Issue/Subject property
494  *
495  * @param[in] asyncResp Shared pointer to the response message
496  * @param[in] str  Issuer/Subject value in key=value pairs
497  * @param[in] type Issuer/Subject
498  * @return None
499  */
500 static void updateCertIssuerOrSubject(nlohmann::json& out,
501                                       const std::string_view value)
502 {
503     // example: O=openbmc-project.xyz,CN=localhost
504     std::string_view::iterator i = value.begin();
505     while (i != value.end())
506     {
507         std::string_view::iterator tokenBegin = i;
508         while (i != value.end() && *i != '=')
509         {
510             ++i;
511         }
512         if (i == value.end())
513         {
514             break;
515         }
516         const std::string_view key(tokenBegin,
517                                    static_cast<size_t>(i - tokenBegin));
518         ++i;
519         tokenBegin = i;
520         while (i != value.end() && *i != ',')
521         {
522             ++i;
523         }
524         const std::string_view val(tokenBegin,
525                                    static_cast<size_t>(i - tokenBegin));
526         if (key == "L")
527         {
528             out["City"] = val;
529         }
530         else if (key == "CN")
531         {
532             out["CommonName"] = val;
533         }
534         else if (key == "C")
535         {
536             out["Country"] = val;
537         }
538         else if (key == "O")
539         {
540             out["Organization"] = val;
541         }
542         else if (key == "OU")
543         {
544             out["OrganizationalUnit"] = val;
545         }
546         else if (key == "ST")
547         {
548             out["State"] = val;
549         }
550         // skip comma character
551         if (i != value.end())
552         {
553             ++i;
554         }
555     }
556 }
557 
558 /**
559  * @brief Retrieve the installed certificate list
560  *
561  * @param[in] asyncResp Shared pointer to the response message
562  * @param[in] basePath DBus object path to search
563  * @param[in] listPtr Json pointer to the list in asyncResp
564  * @param[in] countPtr Json pointer to the count in asyncResp
565  * @return None
566  */
567 static void
568     getCertificateList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
569                        const std::string& basePath,
570                        const nlohmann::json::json_pointer& listPtr,
571                        const nlohmann::json::json_pointer& countPtr)
572 {
573     crow::connections::systemBus->async_method_call(
574         [asyncResp, listPtr, countPtr](
575             const boost::system::error_code ec,
576             const dbus::utility::MapperGetSubTreePathsResponse& certPaths) {
577         if (ec)
578         {
579             BMCWEB_LOG_ERROR << "Certificate collection query failed: " << ec;
580             messages::internalError(asyncResp->res);
581             return;
582         }
583 
584         nlohmann::json& links = asyncResp->res.jsonValue[listPtr];
585         links = nlohmann::json::array();
586         for (const auto& certPath : certPaths)
587         {
588             sdbusplus::message::object_path objPath(certPath);
589             std::string certId = objPath.filename();
590             if (certId.empty())
591             {
592                 BMCWEB_LOG_ERROR << "Invalid certificate objPath " << certPath;
593                 continue;
594             }
595 
596             boost::urls::url certURL;
597             if (objPath.parent_path() == certs::httpsObjectPath)
598             {
599                 certURL = crow::utility::urlFromPieces(
600                     "redfish", "v1", "Managers", "bmc", "NetworkProtocol",
601                     "HTTPS", "Certificates", certId);
602             }
603             else if (objPath.parent_path() == certs::ldapObjectPath)
604             {
605                 certURL = crow::utility::urlFromPieces("redfish", "v1",
606                                                        "AccountService", "LDAP",
607                                                        "Certificates", certId);
608             }
609             else if (objPath.parent_path() == certs::authorityObjectPath)
610             {
611                 certURL = crow::utility::urlFromPieces(
612                     "redfish", "v1", "Managers", "bmc", "Truststore",
613                     "Certificates", certId);
614             }
615             else
616             {
617                 continue;
618             }
619 
620             nlohmann::json::object_t link;
621             link["@odata.id"] = certURL;
622             links.emplace_back(std::move(link));
623         }
624 
625         asyncResp->res.jsonValue[countPtr] = links.size();
626         },
627         "xyz.openbmc_project.ObjectMapper",
628         "/xyz/openbmc_project/object_mapper",
629         "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", basePath, 0,
630         std::array<const char*, 1>{certs::certPropIntf});
631 }
632 
633 /**
634  * @brief Retrieve the certificates properties and append to the response
635  * message
636  *
637  * @param[in] asyncResp Shared pointer to the response message
638  * @param[in] objectPath  Path of the D-Bus service object
639  * @param[in] certId  Id of the certificate
640  * @param[in] certURL  URL of the certificate object
641  * @param[in] name  name of the certificate
642  * @return None
643  */
644 static void getCertificateProperties(
645     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
646     const std::string& objectPath, const std::string& service, long certId,
647     const std::string& certURL, const std::string& name)
648 {
649     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
650                      << " certId=" << certId << " certURl=" << certURL;
651     crow::connections::systemBus->async_method_call(
652         [asyncResp, certURL, certId,
653          name](const boost::system::error_code ec,
654                const dbus::utility::DBusPropertiesMap& properties) {
655         if (ec)
656         {
657             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
658             messages::resourceNotFound(asyncResp->res, name,
659                                        std::to_string(certId));
660             return;
661         }
662         asyncResp->res.jsonValue["@odata.id"] = certURL;
663         asyncResp->res.jsonValue["@odata.type"] =
664             "#Certificate.v1_0_0.Certificate";
665         asyncResp->res.jsonValue["Id"] = std::to_string(certId);
666         asyncResp->res.jsonValue["Name"] = name;
667         asyncResp->res.jsonValue["Description"] = name;
668         for (const auto& property : properties)
669         {
670             if (property.first == "CertificateString")
671             {
672                 asyncResp->res.jsonValue["CertificateString"] = "";
673                 const std::string* value =
674                     std::get_if<std::string>(&property.second);
675                 if (value != nullptr)
676                 {
677                     asyncResp->res.jsonValue["CertificateString"] = *value;
678                 }
679             }
680             else if (property.first == "KeyUsage")
681             {
682                 nlohmann::json& keyUsage = asyncResp->res.jsonValue["KeyUsage"];
683                 keyUsage = nlohmann::json::array();
684                 const std::vector<std::string>* value =
685                     std::get_if<std::vector<std::string>>(&property.second);
686                 if (value != nullptr)
687                 {
688                     for (const std::string& usage : *value)
689                     {
690                         keyUsage.push_back(usage);
691                     }
692                 }
693             }
694             else if (property.first == "Issuer")
695             {
696                 const std::string* value =
697                     std::get_if<std::string>(&property.second);
698                 if (value != nullptr)
699                 {
700                     updateCertIssuerOrSubject(
701                         asyncResp->res.jsonValue["Issuer"], *value);
702                 }
703             }
704             else if (property.first == "Subject")
705             {
706                 const std::string* value =
707                     std::get_if<std::string>(&property.second);
708                 if (value != nullptr)
709                 {
710                     updateCertIssuerOrSubject(
711                         asyncResp->res.jsonValue["Subject"], *value);
712                 }
713             }
714             else if (property.first == "ValidNotAfter")
715             {
716                 const uint64_t* value = std::get_if<uint64_t>(&property.second);
717                 if (value != nullptr)
718                 {
719                     asyncResp->res.jsonValue["ValidNotAfter"] =
720                         crow::utility::getDateTimeUint(*value);
721                 }
722             }
723             else if (property.first == "ValidNotBefore")
724             {
725                 const uint64_t* value = std::get_if<uint64_t>(&property.second);
726                 if (value != nullptr)
727                 {
728                     asyncResp->res.jsonValue["ValidNotBefore"] =
729                         crow::utility::getDateTimeUint(*value);
730                 }
731             }
732         }
733         asyncResp->res.addHeader("Location", certURL);
734         },
735         service, objectPath, certs::dbusPropIntf, "GetAll",
736         certs::certPropIntf);
737 }
738 
739 /**
740  * Action to replace an existing certificate
741  */
742 inline void requestRoutesCertificateActionsReplaceCertificate(App& app)
743 {
744     BMCWEB_ROUTE(
745         app,
746         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
747         .privileges(redfish::privileges::postCertificateService)
748         .methods(boost::beast::http::verb::post)(
749             [&app](const crow::Request& req,
750                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
751         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
752         {
753             return;
754         }
755         std::string certificate;
756         nlohmann::json certificateUri;
757         std::optional<std::string> certificateType = "PEM";
758 
759         if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString",
760                                        certificate, "CertificateUri",
761                                        certificateUri, "CertificateType",
762                                        certificateType))
763         {
764             BMCWEB_LOG_ERROR << "Required parameters are missing";
765             messages::internalError(asyncResp->res);
766             return;
767         }
768 
769         if (!certificateType)
770         {
771             // should never happen, but it never hurts to be paranoid.
772             return;
773         }
774         if (certificateType != "PEM")
775         {
776             messages::actionParameterNotSupported(
777                 asyncResp->res, "CertificateType", "ReplaceCertificate");
778             return;
779         }
780 
781         std::string certURI;
782         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
783                                           "@odata.id", certURI))
784         {
785             messages::actionParameterMissing(
786                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
787             return;
788         }
789 
790         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
791         long id = getIDFromURL(certURI);
792         if (id < 0)
793         {
794             messages::actionParameterValueFormatError(asyncResp->res, certURI,
795                                                       "CertificateUri",
796                                                       "ReplaceCertificate");
797             return;
798         }
799         std::string objectPath;
800         std::string name;
801         std::string service;
802         if (boost::starts_with(
803                 certURI,
804                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
805         {
806             objectPath =
807                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
808             name = "HTTPS certificate";
809             service = certs::httpsServiceName;
810         }
811         else if (boost::starts_with(
812                      certURI, "/redfish/v1/AccountService/LDAP/Certificates/"))
813         {
814             objectPath =
815                 std::string(certs::ldapObjectPath) + "/" + std::to_string(id);
816             name = "LDAP certificate";
817             service = certs::ldapServiceName;
818         }
819         else if (boost::starts_with(
820                      certURI,
821                      "/redfish/v1/Managers/bmc/Truststore/Certificates/"))
822         {
823             objectPath = std::string(certs::authorityObjectPath) + "/" +
824                          std::to_string(id);
825             name = "TrustStore certificate";
826             service = certs::authorityServiceName;
827         }
828         else
829         {
830             messages::actionParameterNotSupported(
831                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
832             return;
833         }
834 
835         std::shared_ptr<CertificateFile> certFile =
836             std::make_shared<CertificateFile>(certificate);
837         crow::connections::systemBus->async_method_call(
838             [asyncResp, certFile, objectPath, service, certURI, id,
839              name](const boost::system::error_code ec) {
840             if (ec)
841             {
842                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
843                 if (ec.value() ==
844                     boost::system::linux_error::bad_request_descriptor)
845                 {
846                     messages::resourceNotFound(asyncResp->res, name,
847                                                std::to_string(id));
848                     return;
849                 }
850                 messages::internalError(asyncResp->res);
851                 return;
852             }
853             getCertificateProperties(asyncResp, objectPath, service, id,
854                                      certURI, name);
855             BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
856                              << certFile->getCertFilePath();
857             },
858             service, objectPath, certs::certReplaceIntf, "Replace",
859             certFile->getCertFilePath());
860         });
861 } // requestRoutesCertificateActionsReplaceCertificate
862 
863 /**
864  * Certificate resource describes a certificate used to prove the identity
865  * of a component, account or service.
866  */
867 
868 inline void requestRoutesHTTPSCertificate(App& app)
869 {
870     BMCWEB_ROUTE(
871         app,
872         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
873         .privileges(redfish::privileges::getCertificate)
874         .methods(
875             boost::beast::http::verb::
876                 get)([&app](const crow::Request& req,
877                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
878                             const std::string& param) -> void {
879             if (!redfish::setUpRedfishRoute(app, req, asyncResp))
880             {
881                 return;
882             }
883             if (param.empty())
884             {
885                 messages::internalError(asyncResp->res);
886                 return;
887             }
888             long id = getIDFromURL(req.url);
889 
890             BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID="
891                              << std::to_string(id);
892             std::string certURL =
893                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
894                 std::to_string(id);
895             std::string objectPath = certs::httpsObjectPath;
896             objectPath += "/";
897             objectPath += std::to_string(id);
898             getCertificateProperties(asyncResp, objectPath,
899                                      certs::httpsServiceName, id, certURL,
900                                      "HTTPS Certificate");
901         });
902 }
903 
904 /**
905  * Collection of HTTPS certificates
906  */
907 inline void requestRoutesHTTPSCertificateCollection(App& app)
908 {
909     BMCWEB_ROUTE(app,
910                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
911         .privileges(redfish::privileges::getCertificateCollection)
912         .methods(boost::beast::http::verb::get)(
913             [&app](const crow::Request& req,
914                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
915         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
916         {
917             return;
918         }
919 
920         asyncResp->res.jsonValue["@odata.id"] =
921             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
922         asyncResp->res.jsonValue["@odata.type"] =
923             "#CertificateCollection.CertificateCollection";
924         asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
925         asyncResp->res.jsonValue["Description"] =
926             "A Collection of HTTPS certificate instances";
927 
928         getCertificateList(asyncResp, certs::httpsObjectPath,
929                            "/Members"_json_pointer,
930                            "/Members@odata.count"_json_pointer);
931         });
932 
933     BMCWEB_ROUTE(app,
934                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
935         .privileges(redfish::privileges::postCertificateCollection)
936         .methods(boost::beast::http::verb::post)(
937             [&app](const crow::Request& req,
938                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
939         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
940         {
941             return;
942         }
943         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
944 
945         asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
946         asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
947 
948         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
949 
950         if (certFileBody.empty())
951         {
952             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
953             messages::unrecognizedRequestBody(asyncResp->res);
954             return;
955         }
956 
957         std::shared_ptr<CertificateFile> certFile =
958             std::make_shared<CertificateFile>(certFileBody);
959 
960         crow::connections::systemBus->async_method_call(
961             [asyncResp, certFile](const boost::system::error_code ec,
962                                   const std::string& objectPath) {
963             if (ec)
964             {
965                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
966                 messages::internalError(asyncResp->res);
967                 return;
968             }
969             long certId = getIDFromURL(objectPath);
970             if (certId < 0)
971             {
972                 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath;
973                 messages::internalError(asyncResp->res);
974                 return;
975             }
976             std::string certURL =
977                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
978                 std::to_string(certId);
979             getCertificateProperties(asyncResp, objectPath,
980                                      certs::httpsServiceName, certId, certURL,
981                                      "HTTPS Certificate");
982             BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
983                              << certFile->getCertFilePath();
984             },
985             certs::httpsServiceName, certs::httpsObjectPath,
986             certs::certInstallIntf, "Install", certFile->getCertFilePath());
987         });
988 } // requestRoutesHTTPSCertificateCollection
989 
990 /**
991  * The certificate location schema defines a resource that an administrator
992  * can use in order to locate all certificates installed on a given service.
993  */
994 inline void requestRoutesCertificateLocations(App& app)
995 {
996     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
997         .privileges(redfish::privileges::getCertificateLocations)
998         .methods(boost::beast::http::verb::get)(
999             [&app](const crow::Request& req,
1000                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1001         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1002         {
1003             return;
1004         }
1005         asyncResp->res.jsonValue["@odata.id"] =
1006             "/redfish/v1/CertificateService/CertificateLocations";
1007         asyncResp->res.jsonValue["@odata.type"] =
1008             "#CertificateLocations.v1_0_0.CertificateLocations";
1009         asyncResp->res.jsonValue["Name"] = "Certificate Locations";
1010         asyncResp->res.jsonValue["Id"] = "CertificateLocations";
1011         asyncResp->res.jsonValue["Description"] =
1012             "Defines a resource that an administrator can use in order to "
1013             "locate all certificates installed on a given service";
1014 
1015         getCertificateList(asyncResp, certs::baseObjectPath,
1016                            "/Links/Certificates"_json_pointer,
1017                            "/Links/Certificates@odata.count"_json_pointer);
1018         });
1019 }
1020 // requestRoutesCertificateLocations
1021 
1022 /**
1023  * Collection of LDAP certificates
1024  */
1025 inline void requestRoutesLDAPCertificateCollection(App& app)
1026 {
1027     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1028         .privileges(redfish::privileges::getCertificateCollection)
1029         .methods(boost::beast::http::verb::get)(
1030             [&app](const crow::Request& req,
1031                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1032         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1033         {
1034             return;
1035         }
1036 
1037         asyncResp->res.jsonValue["@odata.id"] =
1038             "/redfish/v1/AccountService/LDAP/Certificates";
1039         asyncResp->res.jsonValue["@odata.type"] =
1040             "#CertificateCollection.CertificateCollection";
1041         asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1042         asyncResp->res.jsonValue["Description"] =
1043             "A Collection of LDAP certificate instances";
1044 
1045         getCertificateList(asyncResp, certs::ldapObjectPath,
1046                            "/Members"_json_pointer,
1047                            "/Members@odata.count"_json_pointer);
1048         });
1049 
1050     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1051         .privileges(redfish::privileges::postCertificateCollection)
1052         .methods(boost::beast::http::verb::post)(
1053             [&app](const crow::Request& req,
1054                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1055         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1056         {
1057             return;
1058         }
1059         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1060 
1061         if (certFileBody.empty())
1062         {
1063             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1064             messages::unrecognizedRequestBody(asyncResp->res);
1065             return;
1066         }
1067 
1068         std::shared_ptr<CertificateFile> certFile =
1069             std::make_shared<CertificateFile>(certFileBody);
1070 
1071         crow::connections::systemBus->async_method_call(
1072             [asyncResp, certFile](const boost::system::error_code ec,
1073                                   const std::string& objectPath) {
1074             if (ec)
1075             {
1076                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1077                 messages::internalError(asyncResp->res);
1078                 return;
1079             }
1080             long certId = getIDFromURL(objectPath);
1081             if (certId < 0)
1082             {
1083                 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath;
1084                 messages::internalError(asyncResp->res);
1085                 return;
1086             }
1087             std::string certURL =
1088                 "/redfish/v1/AccountService/LDAP/Certificates/" +
1089                 std::to_string(certId);
1090             getCertificateProperties(asyncResp, objectPath,
1091                                      certs::ldapServiceName, certId, certURL,
1092                                      "LDAP Certificate");
1093             BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1094                              << certFile->getCertFilePath();
1095             },
1096             certs::ldapServiceName, certs::ldapObjectPath,
1097             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1098         });
1099 } // requestRoutesLDAPCertificateCollection
1100 
1101 /**
1102  * Certificate resource describes a certificate used to prove the identity
1103  * of a component, account or service.
1104  */
1105 inline void requestRoutesLDAPCertificate(App& app)
1106 {
1107     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1108         .privileges(redfish::privileges::getCertificate)
1109         .methods(boost::beast::http::verb::get)(
1110             [&app](const crow::Request& req,
1111                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1112                    const std::string&) {
1113         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1114         {
1115             return;
1116         }
1117         long id = getIDFromURL(req.url);
1118         if (id < 0)
1119         {
1120             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1121             messages::internalError(asyncResp->res);
1122             return;
1123         }
1124         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id);
1125         std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" +
1126                               std::to_string(id);
1127         std::string objectPath = certs::ldapObjectPath;
1128         objectPath += "/";
1129         objectPath += std::to_string(id);
1130         getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName,
1131                                  id, certURL, "LDAP Certificate");
1132         });
1133 } // requestRoutesLDAPCertificate
1134 /**
1135  * Collection of TrustStoreCertificate certificates
1136  */
1137 inline void requestRoutesTrustStoreCertificateCollection(App& app)
1138 {
1139     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1140         .privileges(redfish::privileges::getCertificate)
1141         .methods(boost::beast::http::verb::get)(
1142             [&app](const crow::Request& req,
1143                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1144         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1145         {
1146             return;
1147         }
1148 
1149         asyncResp->res.jsonValue["@odata.id"] =
1150             "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1151         asyncResp->res.jsonValue["@odata.type"] =
1152             "#CertificateCollection.CertificateCollection";
1153         asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1154         asyncResp->res.jsonValue["Description"] =
1155             "A Collection of TrustStore certificate instances";
1156 
1157         getCertificateList(asyncResp, certs::authorityObjectPath,
1158                            "/Members"_json_pointer,
1159                            "/Members@odata.count"_json_pointer);
1160         });
1161 
1162     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1163         .privileges(redfish::privileges::postCertificateCollection)
1164         .methods(boost::beast::http::verb::post)(
1165             [&app](const crow::Request& req,
1166                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1167         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1168         {
1169             return;
1170         }
1171         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1172 
1173         if (certFileBody.empty())
1174         {
1175             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1176             messages::unrecognizedRequestBody(asyncResp->res);
1177             return;
1178         }
1179 
1180         std::shared_ptr<CertificateFile> certFile =
1181             std::make_shared<CertificateFile>(certFileBody);
1182         crow::connections::systemBus->async_method_call(
1183             [asyncResp, certFile](const boost::system::error_code ec,
1184                                   const std::string& objectPath) {
1185             if (ec)
1186             {
1187                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1188                 messages::internalError(asyncResp->res);
1189                 return;
1190             }
1191             long certId = getIDFromURL(objectPath);
1192             if (certId < 0)
1193             {
1194                 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath;
1195                 messages::internalError(asyncResp->res);
1196                 return;
1197             }
1198             std::string certURL =
1199                 "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1200                 std::to_string(certId);
1201 
1202             getCertificateProperties(asyncResp, objectPath,
1203                                      certs::authorityServiceName, certId,
1204                                      certURL, "TrustStore Certificate");
1205             BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1206                              << certFile->getCertFilePath();
1207             },
1208             certs::authorityServiceName, certs::authorityObjectPath,
1209             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1210         });
1211 } // requestRoutesTrustStoreCertificateCollection
1212 
1213 /**
1214  * Certificate resource describes a certificate used to prove the identity
1215  * of a component, account or service.
1216  */
1217 inline void requestRoutesTrustStoreCertificate(App& app)
1218 {
1219     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1220         .privileges(redfish::privileges::getCertificate)
1221         .methods(boost::beast::http::verb::get)(
1222             [&app](const crow::Request& req,
1223                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1224                    const std::string&) {
1225         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1226         {
1227             return;
1228         }
1229         long id = getIDFromURL(req.url);
1230         if (id < 0)
1231         {
1232             BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1233             messages::internalError(asyncResp->res);
1234             return;
1235         }
1236         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1237                          << std::to_string(id);
1238         std::string certURL =
1239             "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1240             std::to_string(id);
1241         std::string objectPath = certs::authorityObjectPath;
1242         objectPath += "/";
1243         objectPath += std::to_string(id);
1244         getCertificateProperties(asyncResp, objectPath,
1245                                  certs::authorityServiceName, id, certURL,
1246                                  "TrustStore Certificate");
1247         });
1248 
1249     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1250         .privileges(redfish::privileges::deleteCertificate)
1251         .methods(boost::beast::http::verb::delete_)(
1252             [&app](const crow::Request& req,
1253                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1254                    const std::string& param) {
1255         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1256         {
1257             return;
1258         }
1259         if (param.empty())
1260         {
1261             messages::internalError(asyncResp->res);
1262             return;
1263         }
1264 
1265         long id = getIDFromURL(req.url);
1266         if (id < 0)
1267         {
1268             BMCWEB_LOG_ERROR << "Invalid url value: " << req.url;
1269             messages::resourceNotFound(asyncResp->res, "TrustStore Certificate",
1270                                        std::string(req.url));
1271             return;
1272         }
1273         BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID="
1274                          << std::to_string(id);
1275         std::string certPath = certs::authorityObjectPath;
1276         certPath += "/";
1277         certPath += std::to_string(id);
1278 
1279         crow::connections::systemBus->async_method_call(
1280             [asyncResp, id](const boost::system::error_code ec) {
1281             if (ec)
1282             {
1283                 messages::resourceNotFound(asyncResp->res,
1284                                            "TrustStore Certificate",
1285                                            std::to_string(id));
1286                 return;
1287             }
1288             BMCWEB_LOG_INFO << "Certificate deleted";
1289             asyncResp->res.result(boost::beast::http::status::no_content);
1290             },
1291             certs::authorityServiceName, certPath, certs::objDeleteIntf,
1292             "Delete");
1293         });
1294 } // requestRoutesTrustStoreCertificate
1295 } // namespace redfish
1296