xref: /openbmc/bmcweb/features/redfish/lib/certificate_service.hpp (revision 5968caee7ccf978755a9a63d225965101a0c0378)
1 /*
2 // Copyright (c) 2018 IBM Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include "node.hpp"
19 
20 #include <variant>
21 namespace redfish
22 {
23 namespace certs
24 {
25 constexpr char const *httpsObjectPath =
26     "/xyz/openbmc_project/certs/server/https";
27 constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install";
28 constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
29 constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate";
30 constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties";
31 constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
32 constexpr char const *mapperBusName = "xyz.openbmc_project.ObjectMapper";
33 constexpr char const *mapperObjectPath = "/xyz/openbmc_project/object_mapper";
34 constexpr char const *mapperIntf = "xyz.openbmc_project.ObjectMapper";
35 } // namespace certs
36 
37 /**
38  * The Certificate schema defines a Certificate Service which represents the
39  * actions available to manage certificates and links to where certificates
40  * are installed.
41  */
42 class CertificateService : public Node
43 {
44   public:
45     CertificateService(CrowApp &app) :
46         Node(app, "/redfish/v1/CertificateService/")
47     {
48         // TODO: Issue#61 No entries are available for Certificate
49         // sevice at https://www.dmtf.org/standards/redfish
50         // "redfish standard registries". Need to modify after DMTF
51         // publish Privilege details for certificate service
52         entityPrivileges = {
53             {boost::beast::http::verb::get, {{"Login"}}},
54             {boost::beast::http::verb::head, {{"Login"}}},
55             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
56             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
57             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
58             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
59     }
60 
61   private:
62     void doGet(crow::Response &res, const crow::Request &req,
63                const std::vector<std::string> &params) override
64     {
65         res.jsonValue = {
66             {"@odata.type", "#CertificateService.v1_0_0.CertificateService"},
67             {"@odata.id", "/redfish/v1/CertificateService"},
68             {"@odata.context",
69              "/redfish/v1/$metadata#CertificateService.CertificateService"},
70             {"Id", "CertificateService"},
71             {"Name", "Certificate Service"},
72             {"Description", "Actions available to manage certificates"}};
73         res.jsonValue["CertificateLocations"] = {
74             {"@odata.id",
75              "/redfish/v1/CertificateService/CertificateLocations"}};
76         res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = {
77             {"target", "/redfish/v1/CertificateService/Actions/"
78                        "CertificateService.ReplaceCertificate"},
79             {"CertificateType@Redfish.AllowableValues", {"PEM"}}};
80         res.end();
81     }
82 }; // CertificateService
83 /**
84  * @brief Find the ID specified in the URL
85  * Finds the numbers specified after the last "/" in the URL and returns.
86  * @param[in] path URL
87  * @return -1 on failure and number on success
88  */
89 long getIDFromURL(const std::string_view url)
90 {
91     std::size_t found = url.rfind("/");
92     if (found == std::string::npos)
93     {
94         return -1;
95     }
96     if ((found + 1) < url.length())
97     {
98         char *endPtr;
99         std::string_view str = url.substr(found + 1);
100         long value = std::strtol(str.data(), &endPtr, 10);
101         if (endPtr != &str.back())
102         {
103             return -1;
104         }
105         return value;
106     }
107     return -1;
108 }
109 
110 /**
111  * Class to create a temporary certificate file for uploading to system
112  */
113 class CertificateFile
114 {
115   public:
116     CertificateFile() = delete;
117     CertificateFile(const CertificateFile &) = delete;
118     CertificateFile &operator=(const CertificateFile &) = delete;
119     CertificateFile(CertificateFile &&) = delete;
120     CertificateFile &operator=(CertificateFile &&) = delete;
121     CertificateFile(const std::string &certString)
122     {
123         char dirTemplate[] = "/tmp/Certs.XXXXXX";
124         char *tempDirectory = mkdtemp(dirTemplate);
125         if (tempDirectory)
126         {
127             certDirectory = tempDirectory;
128             certificateFile = certDirectory / "cert.pem";
129             std::ofstream out(certificateFile, std::ofstream::out |
130                                                    std::ofstream::binary |
131                                                    std::ofstream::trunc);
132             out << certString;
133             out.close();
134             BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile;
135         }
136     }
137     ~CertificateFile()
138     {
139         if (std::filesystem::exists(certDirectory))
140         {
141             BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile;
142             try
143             {
144                 std::filesystem::remove_all(certDirectory);
145             }
146             catch (const std::filesystem::filesystem_error &e)
147             {
148                 BMCWEB_LOG_ERROR << "Failed to remove temp directory"
149                                  << certDirectory;
150             }
151         }
152     }
153     std::string getCertFilePath()
154     {
155         return certificateFile;
156     }
157 
158   private:
159     std::filesystem::path certificateFile;
160     std::filesystem::path certDirectory;
161 };
162 
163 /**
164  * @brief Parse and update Certficate Issue/Subject property
165  *
166  * @param[in] asyncResp Shared pointer to the response message
167  * @param[in] str  Issuer/Subject value in key=value pairs
168  * @param[in] type Issuer/Subject
169  * @return None
170  */
171 static void updateCertIssuerOrSubject(nlohmann::json &out,
172                                       const std::string_view value)
173 {
174     // example: O=openbmc-project.xyz,CN=localhost
175     std::string_view::iterator i = value.begin();
176     while (i != value.end())
177     {
178         std::string_view::iterator tokenBegin = i;
179         while (i != value.end() && *i != '=')
180         {
181             i++;
182         }
183         if (i == value.end())
184         {
185             break;
186         }
187         const std::string_view key(tokenBegin, i - tokenBegin);
188         i++;
189         tokenBegin = i;
190         while (i != value.end() && *i != ',')
191         {
192             i++;
193         }
194         const std::string_view val(tokenBegin, i - tokenBegin);
195         if (key == "L")
196         {
197             out["City"] = val;
198         }
199         else if (key == "CN")
200         {
201             out["CommonName"] = val;
202         }
203         else if (key == "C")
204         {
205             out["Country"] = val;
206         }
207         else if (key == "O")
208         {
209             out["Organization"] = val;
210         }
211         else if (key == "OU")
212         {
213             out["OrganizationalUnit"] = val;
214         }
215         else if (key == "ST")
216         {
217             out["State"] = val;
218         }
219         // skip comma character
220         if (i != value.end())
221         {
222             i++;
223         }
224     }
225 }
226 
227 /**
228  * @brief Retrieve the certificates properties and append to the response
229  * message
230  *
231  * @param[in] asyncResp Shared pointer to the response message
232  * @param[in] objectPath  Path of the D-Bus service object
233  * @param[in] certId  Id of the certificate
234  * @param[in] certURL  URL of the certificate object
235  * @param[in] name  name of the certificate
236  * @return None
237  */
238 static void getCertificateProperties(
239     const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath,
240     long certId, const std::string &certURL, const std::string &name)
241 {
242     using PropertyType =
243         std::variant<std::string, uint64_t, std::vector<std::string>>;
244     using PropertiesMap = boost::container::flat_map<std::string, PropertyType>;
245     BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
246                      << " certId=" << certId << " certURl=" << certURL;
247     crow::connections::systemBus->async_method_call(
248         [asyncResp, objectPath, certURL, certId,
249          name](const boost::system::error_code ec, const GetObjectType &resp) {
250             if (ec)
251             {
252                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
253                 messages::internalError(asyncResp->res);
254                 return;
255             }
256             if (resp.size() > 1 || resp.empty())
257             {
258                 BMCWEB_LOG_ERROR << "Invalid number of objects found "
259                                  << resp.size();
260                 messages::internalError(asyncResp->res);
261                 return;
262             }
263             const std::string &service = resp.begin()->first;
264             crow::connections::systemBus->async_method_call(
265                 [asyncResp, certURL, certId,
266                  name](const boost::system::error_code ec,
267                        const PropertiesMap &properties) {
268                     if (ec)
269                     {
270                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
271                         messages::internalError(asyncResp->res);
272                         return;
273                     }
274                     asyncResp->res.jsonValue = {
275                         {"@odata.id", certURL},
276                         {"@odata.type", "#Certificate.v1_0_0.Certificate"},
277                         {"@odata.context",
278                          "/redfish/v1/$metadata#Certificate.Certificate"},
279                         {"Id", std::to_string(certId)},
280                         {"Name", name},
281                         {"Description", name}};
282                     for (const auto &property : properties)
283                     {
284                         if (property.first == "CertificateString")
285                         {
286                             asyncResp->res.jsonValue["CertificateString"] = "";
287                             const std::string *value =
288                                 std::get_if<std::string>(&property.second);
289                             if (value)
290                             {
291                                 asyncResp->res.jsonValue["CertificateString"] =
292                                     *value;
293                             }
294                         }
295                         else if (property.first == "KeyUsage")
296                         {
297                             nlohmann::json &keyUsage =
298                                 asyncResp->res.jsonValue["KeyUsage"];
299                             keyUsage = nlohmann::json::array();
300                             const std::vector<std::string> *value =
301                                 std::get_if<std::vector<std::string>>(
302                                     &property.second);
303                             if (value)
304                             {
305                                 for (const std::string &usage : *value)
306                                 {
307                                     keyUsage.push_back(usage);
308                                 }
309                             }
310                         }
311                         else if (property.first == "Issuer")
312                         {
313                             const std::string *value =
314                                 std::get_if<std::string>(&property.second);
315                             if (value)
316                             {
317                                 updateCertIssuerOrSubject(
318                                     asyncResp->res.jsonValue["Issuer"], *value);
319                             }
320                         }
321                         else if (property.first == "Subject")
322                         {
323                             const std::string *value =
324                                 std::get_if<std::string>(&property.second);
325                             if (value)
326                             {
327                                 updateCertIssuerOrSubject(
328                                     asyncResp->res.jsonValue["Subject"],
329                                     *value);
330                             }
331                         }
332                         else if (property.first == "ValidNotAfter")
333                         {
334                             const uint64_t *value =
335                                 std::get_if<uint64_t>(&property.second);
336                             if (value)
337                             {
338                                 std::time_t time =
339                                     static_cast<std::time_t>(*value);
340                                 asyncResp->res.jsonValue["ValidNotAfter"] =
341                                     crow::utility::getDateTime(time);
342                             }
343                         }
344                         else if (property.first == "ValidNotBefore")
345                         {
346                             const uint64_t *value =
347                                 std::get_if<uint64_t>(&property.second);
348                             if (value)
349                             {
350                                 std::time_t time =
351                                     static_cast<std::time_t>(*value);
352                                 asyncResp->res.jsonValue["ValidNotBefore"] =
353                                     crow::utility::getDateTime(time);
354                             }
355                         }
356                     }
357                     asyncResp->res.addHeader("Location", certURL);
358                 },
359                 service, objectPath, certs::dbusPropIntf, "GetAll",
360                 certs::certPropIntf);
361         },
362         certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
363         "GetObject", objectPath,
364         std::array<const char *, 1>{certs::certPropIntf});
365 }
366 
367 using GetObjectType =
368     std::vector<std::pair<std::string, std::vector<std::string>>>;
369 
370 /**
371  * Action to replace an existing certificate
372  */
373 class CertificateActionsReplaceCertificate : public Node
374 {
375   public:
376     CertificateActionsReplaceCertificate(CrowApp &app) :
377         Node(app, "/redfish/v1/CertificateService/Actions/"
378                   "CertificateService.ReplaceCertificate/")
379     {
380         entityPrivileges = {
381             {boost::beast::http::verb::get, {{"Login"}}},
382             {boost::beast::http::verb::head, {{"Login"}}},
383             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
384             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
385             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
386             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
387     }
388 
389   private:
390     void doPost(crow::Response &res, const crow::Request &req,
391                 const std::vector<std::string> &params) override
392     {
393         std::string certificate;
394         nlohmann::json certificateUri;
395         std::optional<std::string> certificateType = "PEM";
396         auto asyncResp = std::make_shared<AsyncResp>(res);
397         if (!json_util::readJson(req, asyncResp->res, "CertificateString",
398                                  certificate, "CertificateUri", certificateUri,
399                                  "CertificateType", certificateType))
400         {
401             BMCWEB_LOG_ERROR << "Required parameters are missing";
402             messages::internalError(asyncResp->res);
403             return;
404         }
405 
406         if (!certificateType)
407         {
408             // should never happen, but it never hurts to be paranoid.
409             return;
410         }
411         if (certificateType != "PEM")
412         {
413             messages::actionParameterNotSupported(
414                 asyncResp->res, "CertificateType", "ReplaceCertificate");
415             return;
416         }
417 
418         std::string certURI;
419         if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
420                                           "@odata.id", certURI))
421         {
422             messages::actionParameterMissing(
423                 asyncResp->res, "ReplaceCertificate", "CertificateUri");
424             return;
425         }
426 
427         if (!boost::starts_with(
428                 certURI,
429                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
430         {
431             BMCWEB_LOG_ERROR << "Unsupported certificate URI" << certURI;
432             messages::actionParameterValueFormatError(asyncResp->res, certURI,
433                                                       "CertificateUri",
434                                                       "ReplaceCertificate");
435             return;
436         }
437 
438         BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
439         long id = getIDFromURL(certURI);
440         if (id < 0)
441         {
442             messages::actionParameterValueFormatError(asyncResp->res, certURI,
443                                                       "CertificateUri",
444                                                       "ReplaceCertificate");
445             return;
446         }
447         std::string objectPath;
448         std::string name;
449         if (boost::starts_with(
450                 certURI,
451                 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
452         {
453             objectPath =
454                 std::string(certs::httpsObjectPath) + "/" + std::to_string(id);
455             name = "HTTPS certificate";
456         }
457         else
458         {
459             messages::actionParameterNotSupported(
460                 asyncResp->res, "CertificateUri", "ReplaceCertificate");
461             return;
462         }
463 
464         std::shared_ptr<CertificateFile> certFile =
465             std::make_shared<CertificateFile>(certificate);
466 
467         crow::connections::systemBus->async_method_call(
468             [asyncResp, objectPath, certFile, id, certURI, name](
469                 const boost::system::error_code ec, const GetObjectType &resp) {
470                 if (ec)
471                 {
472                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
473                     messages::internalError(asyncResp->res);
474                     return;
475                 }
476                 if (resp.size() > 1 || resp.empty())
477                 {
478                     BMCWEB_LOG_ERROR << "Invalid number of objects found "
479                                      << resp.size();
480                     messages::internalError(asyncResp->res);
481                     return;
482                 }
483                 const std::string &service = resp.begin()->first;
484                 crow::connections::systemBus->async_method_call(
485                     [asyncResp, certFile, objectPath, certURI, id,
486                      name](const boost::system::error_code ec) {
487                         if (ec)
488                         {
489                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
490                             messages::internalError(asyncResp->res);
491                             return;
492                         }
493                         getCertificateProperties(asyncResp, objectPath, id,
494                                                  certURI, name);
495                         BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
496                                          << certFile->getCertFilePath();
497                     },
498                     service, objectPath, certs::certReplaceIntf, "Replace",
499                     certFile->getCertFilePath());
500             },
501             certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
502             "GetObject", objectPath,
503             std::array<std::string, 1>({certs::certReplaceIntf}));
504     }
505 }; // CertificateActionsReplaceCertificate
506 
507 /**
508  * Certificate resource describes a certificate used to prove the identity
509  * of a component, account or service.
510  */
511 class HTTPSCertificate : public Node
512 {
513   public:
514     template <typename CrowApp>
515     HTTPSCertificate(CrowApp &app) :
516         Node(app,
517              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"
518              "<str>/",
519              std::string())
520     {
521         entityPrivileges = {
522             {boost::beast::http::verb::get, {{"Login"}}},
523             {boost::beast::http::verb::head, {{"Login"}}},
524             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
525             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
526             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
527             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
528     }
529 
530     void doGet(crow::Response &res, const crow::Request &req,
531                const std::vector<std::string> &params) override
532     {
533         auto asyncResp = std::make_shared<AsyncResp>(res);
534         if (params.size() != 1)
535         {
536             messages::internalError(asyncResp->res);
537             return;
538         }
539         long id = getIDFromURL(req.url);
540 
541         BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id);
542         std::string certURL =
543             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
544             std::to_string(id);
545         std::string objectPath = certs::httpsObjectPath;
546         objectPath += "/";
547         objectPath += std::to_string(id);
548         getCertificateProperties(asyncResp, objectPath, id, certURL,
549                                  "HTTPS Certificate");
550     }
551 
552 }; // namespace redfish
553 
554 /**
555  * Collection of HTTPS certificates
556  */
557 class HTTPSCertificateCollection : public Node
558 {
559   public:
560     template <typename CrowApp>
561     HTTPSCertificateCollection(CrowApp &app) :
562         Node(app,
563              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
564     {
565         entityPrivileges = {
566             {boost::beast::http::verb::get, {{"Login"}}},
567             {boost::beast::http::verb::head, {{"Login"}}},
568             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
569             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
570             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
571             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
572     }
573     void doGet(crow::Response &res, const crow::Request &req,
574                const std::vector<std::string> &params) override
575     {
576         res.jsonValue = {
577             {"@odata.id",
578              "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"},
579             {"@odata.type", "#CertificateCollection.CertificateCollection"},
580             {"@odata.context",
581              "/redfish/v1/"
582              "$metadata#CertificateCollection.CertificateCollection"},
583             {"Name", "HTTPS Certificates Collection"},
584             {"Description", "A Collection of HTTPS certificate instances"}};
585         auto asyncResp = std::make_shared<AsyncResp>(res);
586         crow::connections::systemBus->async_method_call(
587             [asyncResp](const boost::system::error_code ec,
588                         const GetObjectType &resp) {
589                 if (ec)
590                 {
591                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
592                     messages::internalError(asyncResp->res);
593                     return;
594                 }
595                 if (resp.size() > 1 || resp.empty())
596                 {
597                     BMCWEB_LOG_ERROR << "Invalid number of objects found "
598                                      << resp.size();
599                     messages::internalError(asyncResp->res);
600                     return;
601                 }
602                 const std::string &service = resp.begin()->first;
603                 crow::connections::systemBus->async_method_call(
604                     [asyncResp](const boost::system::error_code ec,
605                                 const ManagedObjectType &certs) {
606                         if (ec)
607                         {
608                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
609                             messages::internalError(asyncResp->res);
610                             return;
611                         }
612                         nlohmann::json &members =
613                             asyncResp->res.jsonValue["Members"];
614                         members = nlohmann::json::array();
615                         for (const auto &cert : certs)
616                         {
617                             long id = getIDFromURL(cert.first.str);
618                             if (id != -1)
619                             {
620                                 members.push_back(
621                                     {{"@odata.id",
622                                       "/redfish/v1/Managers/bmc/"
623                                       "NetworkProtocol/HTTPS/Certificates/" +
624                                           std::to_string(id)}});
625                             }
626                         }
627                         asyncResp->res.jsonValue["Members@odata.count"] =
628                             members.size();
629                     },
630                     service, certs::httpsObjectPath, certs::dbusObjManagerIntf,
631                     "GetManagedObjects");
632             },
633             certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
634             "GetObject", certs::httpsObjectPath,
635             std::array<const char *, 1>{certs::certInstallIntf});
636     }
637 
638     void doPost(crow::Response &res, const crow::Request &req,
639                 const std::vector<std::string> &params) override
640     {
641         BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
642         auto asyncResp = std::make_shared<AsyncResp>(res);
643         asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"},
644                                     {"Description", "HTTPS Certificate"}};
645 
646         std::shared_ptr<CertificateFile> certFile =
647             std::make_shared<CertificateFile>(req.body);
648 
649         crow::connections::systemBus->async_method_call(
650             [asyncResp, certFile](const boost::system::error_code ec,
651                                   const GetObjectType &resp) {
652                 if (ec)
653                 {
654                     BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
655                     messages::internalError(asyncResp->res);
656                     return;
657                 }
658                 if (resp.size() > 1 || resp.empty())
659                 {
660                     BMCWEB_LOG_ERROR << "Invalid number of objects found "
661                                      << resp.size();
662                     messages::internalError(asyncResp->res);
663                     return;
664                 }
665                 const std::string &service = resp.begin()->first;
666                 crow::connections::systemBus->async_method_call(
667                     [asyncResp, certFile](const boost::system::error_code ec) {
668                         if (ec)
669                         {
670                             BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
671                             messages::internalError(asyncResp->res);
672                             return;
673                         }
674                         // TODO: Issue#84 supporting only 1 certificate
675                         long certId = 1;
676                         std::string certURL =
677                             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/"
678                             "Certificates/" +
679                             std::to_string(certId);
680                         std::string objectPath =
681                             std::string(certs::httpsObjectPath) + "/" +
682                             std::to_string(certId);
683                         getCertificateProperties(asyncResp, objectPath, certId,
684                                                  certURL, "HTTPS Certificate");
685                         BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
686                                          << certFile->getCertFilePath();
687                     },
688                     service, certs::httpsObjectPath, certs::certInstallIntf,
689                     "Install", certFile->getCertFilePath());
690             },
691             certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
692             "GetObject", certs::httpsObjectPath,
693             std::array<const char *, 1>{certs::certInstallIntf});
694     }
695 }; // HTTPSCertificateCollection
696 
697 /**
698  * @brief Retrieve the certificates installed list and append to the response
699  *
700  * @param[in] asyncResp Shared pointer to the response message
701  * @param[in] certURL  Path of the certificate object
702  * @param[in] path  Path of the D-Bus service object
703  * @return None
704  */
705 static void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp,
706                                     const std::string &certURL,
707                                     const std::string &path)
708 {
709     BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
710                      << " Path=" << path;
711     crow::connections::systemBus->async_method_call(
712         [asyncResp, path, certURL](const boost::system::error_code ec,
713                                    const GetObjectType &resp) {
714             if (ec)
715             {
716                 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
717                 messages::internalError(asyncResp->res);
718                 return;
719             }
720             if (resp.size() > 1 || resp.empty())
721             {
722                 BMCWEB_LOG_ERROR << "Invalid number of objects found "
723                                  << resp.size();
724                 messages::internalError(asyncResp->res);
725                 return;
726             }
727             const std::string &service = resp.begin()->first;
728             crow::connections::systemBus->async_method_call(
729                 [asyncResp, certURL](const boost::system::error_code ec,
730                                      const ManagedObjectType &certs) {
731                     if (ec)
732                     {
733                         BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
734                         messages::internalError(asyncResp->res);
735                         return;
736                     }
737                     nlohmann::json &links =
738                         asyncResp->res.jsonValue["Links"]["Certificates"];
739                     for (auto &cert : certs)
740                     {
741                         long id = getIDFromURL(cert.first.str);
742                         if (id != -1)
743                         {
744                             links.push_back(
745                                 {{"@odata.id", certURL + std::to_string(id)}});
746                         }
747                     }
748                     asyncResp->res
749                         .jsonValue["Links"]["Certificates@odata.count"] =
750                         links.size();
751                 },
752                 service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
753         },
754         certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf,
755         "GetObject", path, std::array<std::string, 0>());
756 }
757 
758 /**
759  * The certificate location schema defines a resource that an administrator
760  * can use in order to locate all certificates installed on a given service.
761  */
762 class CertificateLocations : public Node
763 {
764   public:
765     template <typename CrowApp>
766     CertificateLocations(CrowApp &app) :
767         Node(app, "/redfish/v1/CertificateService/CertificateLocations/")
768     {
769         entityPrivileges = {
770             {boost::beast::http::verb::get, {{"Login"}}},
771             {boost::beast::http::verb::head, {{"Login"}}},
772             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
773             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
774             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
775             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
776     }
777 
778   private:
779     void doGet(crow::Response &res, const crow::Request &req,
780                const std::vector<std::string> &params) override
781     {
782         res.jsonValue = {
783             {"@odata.id",
784              "/redfish/v1/CertificateService/CertificateLocations"},
785             {"@odata.type",
786              "#CertificateLocations.v1_0_0.CertificateLocations"},
787             {"@odata.context",
788              "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"},
789             {"Name", "Certificate Locations"},
790             {"Id", "CertificateLocations"},
791             {"Description",
792              "Defines a resource that an administrator can use in order to "
793              "locate all certificates installed on a given service"}};
794         auto asyncResp = std::make_shared<AsyncResp>(res);
795         nlohmann::json &links =
796             asyncResp->res.jsonValue["Links"]["Certificates"];
797         links = nlohmann::json::array();
798         getCertificateLocations(
799             asyncResp,
800             "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
801             certs::httpsObjectPath);
802     }
803 }; // CertificateLocations
804 } // namespace redfish
805