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