xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 75b63a2cfe2560b2d4267349882f19538c1fe2f2)
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 std::string& 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("Location", certURL);
705         },
706         service, objectPath, certs::dbusPropIntf, "GetAll",
707         certs::certPropIntf);
708 }
709 
710 /**
711  * Action to replace an existing certificate
712  */
713 inline void requestRoutesCertificateActionsReplaceCertificate(App& app)
714 {
715     BMCWEB_ROUTE(
716         app,
717         "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
718         .privileges(redfish::privileges::postCertificateService)
719         .methods(boost::beast::http::verb::post)(
720             [&app](const crow::Request& req,
721                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
722         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
723         {
724             return;
725         }
726         std::string certificate;
727         nlohmann::json certificateUri;
728         std::optional<std::string> certificateType = "PEM";
729 
730         if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString",
731                                        certificate, "CertificateUri",
732                                        certificateUri, "CertificateType",
733                                        certificateType))
734         {
735             BMCWEB_LOG_ERROR << "Required parameters are missing";
736             messages::internalError(asyncResp->res);
737             return;
738         }
739 
740         if (!certificateType)
741         {
742             // should never happen, but it never hurts to be paranoid.
743             return;
744         }
745         if (certificateType != "PEM")
746         {
747             messages::actionParameterNotSupported(
748                 asyncResp->res, "CertificateType", "ReplaceCertificate");
749             return;
750         }
751 
752         std::string certURI;
753         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
754                                           "@odata.id", certURI))
755         {
756             messages::actionParameterMissing(
757                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
758             return;
759         }
760         BMCWEB_LOG_INFO << "Certificate URI to replace: " << certURI;
761 
762         boost::urls::result<boost::urls::url_view> parsedUrl =
763             boost::urls::parse_relative_ref(certURI);
764         if (!parsedUrl)
765         {
766             messages::actionParameterValueFormatError(asyncResp->res, certURI,
767                                                       "CertificateUri",
768                                                       "ReplaceCertificate");
769             return;
770         }
771 
772         std::string id;
773         sdbusplus::message::object_path objectPath;
774         std::string name;
775         std::string service;
776         if (crow::utility::readUrlSegments(
777                 *parsedUrl, "redfish", "v1", "Managers", "bmc",
778                 "NetworkProtocol", "HTTPS", "Certificates", std::ref(id)))
779         {
780             objectPath =
781                 sdbusplus::message::object_path(certs::httpsObjectPath) / id;
782             name = "HTTPS certificate";
783             service = certs::httpsServiceName;
784         }
785         else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
786                                                 "AccountService", "LDAP",
787                                                 "Certificates", std::ref(id)))
788         {
789             objectPath =
790                 sdbusplus::message::object_path(certs::ldapObjectPath) / id;
791             name = "LDAP certificate";
792             service = certs::ldapServiceName;
793         }
794         else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1",
795                                                 "Managers", "bmc", "Truststore",
796                                                 "Certificates", std::ref(id)))
797         {
798             objectPath =
799                 sdbusplus::message::object_path(certs::authorityObjectPath) /
800                 id;
801             name = "TrustStore certificate";
802             service = certs::authorityServiceName;
803         }
804         else
805         {
806             messages::actionParameterNotSupported(
807                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
808             return;
809         }
810 
811         std::shared_ptr<CertificateFile> certFile =
812             std::make_shared<CertificateFile>(certificate);
813         crow::connections::systemBus->async_method_call(
814             [asyncResp, certFile, objectPath, service, certURI, id,
815              name](const boost::system::error_code ec) {
816             if (ec)
817             {
818                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
819                 if (ec.value() ==
820                     boost::system::linux_error::bad_request_descriptor)
821                 {
822                     messages::resourceNotFound(asyncResp->res, name, id);
823                     return;
824                 }
825                 messages::internalError(asyncResp->res);
826                 return;
827             }
828             getCertificateProperties(asyncResp, objectPath, service, id,
829                                      certURI, name);
830             BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
831                              << certFile->getCertFilePath();
832             },
833             service, objectPath, certs::certReplaceIntf, "Replace",
834             certFile->getCertFilePath());
835         });
836 } // requestRoutesCertificateActionsReplaceCertificate
837 
838 /**
839  * Certificate resource describes a certificate used to prove the identity
840  * of a component, account or service.
841  */
842 
843 inline void requestRoutesHTTPSCertificate(App& app)
844 {
845     BMCWEB_ROUTE(
846         app,
847         "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
848         .privileges(redfish::privileges::getCertificate)
849         .methods(
850             boost::beast::http::verb::
851                 get)([&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             std::string certURL =
861                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
862                 id;
863             std::string objPath =
864                 sdbusplus::message::object_path(certs::httpsObjectPath) / id;
865             getCertificateProperties(asyncResp, objPath,
866                                      certs::httpsServiceName, id, certURL,
867                                      "HTTPS Certificate");
868         });
869 }
870 
871 /**
872  * Collection of HTTPS certificates
873  */
874 inline void requestRoutesHTTPSCertificateCollection(App& app)
875 {
876     BMCWEB_ROUTE(app,
877                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
878         .privileges(redfish::privileges::getCertificateCollection)
879         .methods(boost::beast::http::verb::get)(
880             [&app](const crow::Request& req,
881                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
882         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
883         {
884             return;
885         }
886 
887         asyncResp->res.jsonValue["@odata.id"] =
888             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
889         asyncResp->res.jsonValue["@odata.type"] =
890             "#CertificateCollection.CertificateCollection";
891         asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
892         asyncResp->res.jsonValue["Description"] =
893             "A Collection of HTTPS certificate instances";
894 
895         getCertificateList(asyncResp, certs::httpsObjectPath,
896                            "/Members"_json_pointer,
897                            "/Members@odata.count"_json_pointer);
898         });
899 
900     BMCWEB_ROUTE(app,
901                  "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
902         .privileges(redfish::privileges::postCertificateCollection)
903         .methods(boost::beast::http::verb::post)(
904             [&app](const crow::Request& req,
905                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
906         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
907         {
908             return;
909         }
910         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
911 
912         asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
913         asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
914 
915         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
916 
917         if (certFileBody.empty())
918         {
919             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
920             messages::unrecognizedRequestBody(asyncResp->res);
921             return;
922         }
923 
924         std::shared_ptr<CertificateFile> certFile =
925             std::make_shared<CertificateFile>(certFileBody);
926 
927         crow::connections::systemBus->async_method_call(
928             [asyncResp, certFile](const boost::system::error_code ec,
929                                   const std::string& objectPath) {
930             if (ec)
931             {
932                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
933                 messages::internalError(asyncResp->res);
934                 return;
935             }
936 
937             sdbusplus::message::object_path path(objectPath);
938             std::string certId = path.filename();
939             std::string certURL =
940                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
941                 certId;
942             getCertificateProperties(asyncResp, objectPath,
943                                      certs::httpsServiceName, certId, certURL,
944                                      "HTTPS Certificate");
945             BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
946                              << certFile->getCertFilePath();
947             },
948             certs::httpsServiceName, certs::httpsObjectPath,
949             certs::certInstallIntf, "Install", certFile->getCertFilePath());
950         });
951 } // requestRoutesHTTPSCertificateCollection
952 
953 /**
954  * The certificate location schema defines a resource that an administrator
955  * can use in order to locate all certificates installed on a given service.
956  */
957 inline void requestRoutesCertificateLocations(App& app)
958 {
959     BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
960         .privileges(redfish::privileges::getCertificateLocations)
961         .methods(boost::beast::http::verb::get)(
962             [&app](const crow::Request& req,
963                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
964         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
965         {
966             return;
967         }
968         asyncResp->res.jsonValue["@odata.id"] =
969             "/redfish/v1/CertificateService/CertificateLocations";
970         asyncResp->res.jsonValue["@odata.type"] =
971             "#CertificateLocations.v1_0_0.CertificateLocations";
972         asyncResp->res.jsonValue["Name"] = "Certificate Locations";
973         asyncResp->res.jsonValue["Id"] = "CertificateLocations";
974         asyncResp->res.jsonValue["Description"] =
975             "Defines a resource that an administrator can use in order to "
976             "locate all certificates installed on a given service";
977 
978         getCertificateList(asyncResp, certs::baseObjectPath,
979                            "/Links/Certificates"_json_pointer,
980                            "/Links/Certificates@odata.count"_json_pointer);
981         });
982 }
983 // requestRoutesCertificateLocations
984 
985 /**
986  * Collection of LDAP certificates
987  */
988 inline void requestRoutesLDAPCertificateCollection(App& app)
989 {
990     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
991         .privileges(redfish::privileges::getCertificateCollection)
992         .methods(boost::beast::http::verb::get)(
993             [&app](const crow::Request& req,
994                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
995         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
996         {
997             return;
998         }
999 
1000         asyncResp->res.jsonValue["@odata.id"] =
1001             "/redfish/v1/AccountService/LDAP/Certificates";
1002         asyncResp->res.jsonValue["@odata.type"] =
1003             "#CertificateCollection.CertificateCollection";
1004         asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1005         asyncResp->res.jsonValue["Description"] =
1006             "A Collection of LDAP certificate instances";
1007 
1008         getCertificateList(asyncResp, certs::ldapObjectPath,
1009                            "/Members"_json_pointer,
1010                            "/Members@odata.count"_json_pointer);
1011         });
1012 
1013     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
1014         .privileges(redfish::privileges::postCertificateCollection)
1015         .methods(boost::beast::http::verb::post)(
1016             [&app](const crow::Request& req,
1017                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1018         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1019         {
1020             return;
1021         }
1022         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1023 
1024         if (certFileBody.empty())
1025         {
1026             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1027             messages::unrecognizedRequestBody(asyncResp->res);
1028             return;
1029         }
1030 
1031         std::shared_ptr<CertificateFile> certFile =
1032             std::make_shared<CertificateFile>(certFileBody);
1033 
1034         crow::connections::systemBus->async_method_call(
1035             [asyncResp, certFile](const boost::system::error_code ec,
1036                                   const std::string& objectPath) {
1037             if (ec)
1038             {
1039                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1040                 messages::internalError(asyncResp->res);
1041                 return;
1042             }
1043 
1044             sdbusplus::message::object_path path(objectPath);
1045             std::string certId = path.filename();
1046             std::string certURL =
1047                 "/redfish/v1/AccountService/LDAP/Certificates/" + certId;
1048             getCertificateProperties(asyncResp, objectPath,
1049                                      certs::ldapServiceName, certId, certURL,
1050                                      "LDAP Certificate");
1051             BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1052                              << certFile->getCertFilePath();
1053             },
1054             certs::ldapServiceName, certs::ldapObjectPath,
1055             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1056         });
1057 } // requestRoutesLDAPCertificateCollection
1058 
1059 /**
1060  * Certificate resource describes a certificate used to prove the identity
1061  * of a component, account or service.
1062  */
1063 inline void requestRoutesLDAPCertificate(App& app)
1064 {
1065     BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
1066         .privileges(redfish::privileges::getCertificate)
1067         .methods(boost::beast::http::verb::get)(
1068             [&app](const crow::Request& req,
1069                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1070                    const std::string& id) {
1071         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1072         {
1073             return;
1074         }
1075 
1076         BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id;
1077         std::string certURL =
1078             "/redfish/v1/AccountService/LDAP/Certificates/" + id;
1079         std::string objPath =
1080             sdbusplus::message::object_path(certs::ldapObjectPath) / id;
1081         getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id,
1082                                  certURL, "LDAP Certificate");
1083         });
1084 } // requestRoutesLDAPCertificate
1085 /**
1086  * Collection of TrustStoreCertificate certificates
1087  */
1088 inline void requestRoutesTrustStoreCertificateCollection(App& app)
1089 {
1090     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1091         .privileges(redfish::privileges::getCertificate)
1092         .methods(boost::beast::http::verb::get)(
1093             [&app](const crow::Request& req,
1094                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1095         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1096         {
1097             return;
1098         }
1099 
1100         asyncResp->res.jsonValue["@odata.id"] =
1101             "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1102         asyncResp->res.jsonValue["@odata.type"] =
1103             "#CertificateCollection.CertificateCollection";
1104         asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection";
1105         asyncResp->res.jsonValue["Description"] =
1106             "A Collection of TrustStore certificate instances";
1107 
1108         getCertificateList(asyncResp, certs::authorityObjectPath,
1109                            "/Members"_json_pointer,
1110                            "/Members@odata.count"_json_pointer);
1111         });
1112 
1113     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
1114         .privileges(redfish::privileges::postCertificateCollection)
1115         .methods(boost::beast::http::verb::post)(
1116             [&app](const crow::Request& req,
1117                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1118         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1119         {
1120             return;
1121         }
1122         std::string certFileBody = getCertificateFromReqBody(asyncResp, req);
1123 
1124         if (certFileBody.empty())
1125         {
1126             BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1127             messages::unrecognizedRequestBody(asyncResp->res);
1128             return;
1129         }
1130 
1131         std::shared_ptr<CertificateFile> certFile =
1132             std::make_shared<CertificateFile>(certFileBody);
1133         crow::connections::systemBus->async_method_call(
1134             [asyncResp, certFile](const boost::system::error_code ec,
1135                                   const std::string& objectPath) {
1136             if (ec)
1137             {
1138                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1139                 messages::internalError(asyncResp->res);
1140                 return;
1141             }
1142 
1143             sdbusplus::message::object_path path(objectPath);
1144             std::string certId = path.filename();
1145             std::string certURL =
1146                 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + certId;
1147             getCertificateProperties(asyncResp, objectPath,
1148                                      certs::authorityServiceName, certId,
1149                                      certURL, "TrustStore Certificate");
1150             BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1151                              << certFile->getCertFilePath();
1152             },
1153             certs::authorityServiceName, certs::authorityObjectPath,
1154             certs::certInstallIntf, "Install", certFile->getCertFilePath());
1155         });
1156 } // requestRoutesTrustStoreCertificateCollection
1157 
1158 /**
1159  * Certificate resource describes a certificate used to prove the identity
1160  * of a component, account or service.
1161  */
1162 inline void requestRoutesTrustStoreCertificate(App& app)
1163 {
1164     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1165         .privileges(redfish::privileges::getCertificate)
1166         .methods(boost::beast::http::verb::get)(
1167             [&app](const crow::Request& req,
1168                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1169                    const std::string& id) {
1170         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1171         {
1172             return;
1173         }
1174 
1175         BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id;
1176         std::string certURL =
1177             "/redfish/v1/Managers/bmc/Truststore/Certificates/" + id;
1178         std::string objPath =
1179             sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1180         getCertificateProperties(asyncResp, objPath,
1181                                  certs::authorityServiceName, id, certURL,
1182                                  "TrustStore Certificate");
1183         });
1184 
1185     BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
1186         .privileges(redfish::privileges::deleteCertificate)
1187         .methods(boost::beast::http::verb::delete_)(
1188             [&app](const crow::Request& req,
1189                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1190                    const std::string& id) {
1191         if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1192         {
1193             return;
1194         }
1195 
1196         BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id;
1197         std::string objPath =
1198             sdbusplus::message::object_path(certs::authorityObjectPath) / id;
1199 
1200         crow::connections::systemBus->async_method_call(
1201             [asyncResp, id](const boost::system::error_code ec) {
1202             if (ec)
1203             {
1204                 messages::resourceNotFound(asyncResp->res,
1205                                            "TrustStore Certificate", id);
1206                 return;
1207             }
1208             BMCWEB_LOG_INFO << "Certificate deleted";
1209             asyncResp->res.result(boost::beast::http::status::no_content);
1210             },
1211             certs::authorityServiceName, objPath, certs::objDeleteIntf,
1212             "Delete");
1213         });
1214 } // requestRoutesTrustStoreCertificate
1215 } // namespace redfish
1216