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