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