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