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