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