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