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 crow::connections::systemBus->async_method_call( 427 [asyncResp, 428 id{objectPath.filename()}](const boost::system::error_code& ec) { 429 if (ec) 430 { 431 messages::resourceNotFound(asyncResp->res, "Certificate", id); 432 return; 433 } 434 BMCWEB_LOG_INFO("Certificate deleted"); 435 asyncResp->res.result(boost::beast::http::status::no_content); 436 }, 437 service, objectPath, certs::objDeleteIntf, "Delete"); 438 } 439 handleCertificateServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)440 inline void handleCertificateServiceGet( 441 App& app, const crow::Request& req, 442 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 443 { 444 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 445 { 446 return; 447 } 448 449 if (req.session == nullptr) 450 { 451 messages::internalError(asyncResp->res); 452 return; 453 } 454 455 asyncResp->res.jsonValue["@odata.type"] = 456 "#CertificateService.v1_0_0.CertificateService"; 457 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/CertificateService"; 458 asyncResp->res.jsonValue["Id"] = "CertificateService"; 459 asyncResp->res.jsonValue["Name"] = "Certificate Service"; 460 asyncResp->res.jsonValue["Description"] = 461 "Actions available to manage certificates"; 462 // /redfish/v1/CertificateService/CertificateLocations is something 463 // only ConfigureManager can access then only display when the user 464 // has permissions ConfigureManager 465 Privileges effectiveUserPrivileges = 466 redfish::getUserPrivileges(*req.session); 467 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 468 effectiveUserPrivileges)) 469 { 470 asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] = 471 "/redfish/v1/CertificateService/CertificateLocations"; 472 } 473 nlohmann::json& actions = asyncResp->res.jsonValue["Actions"]; 474 nlohmann::json& replace = actions["#CertificateService.ReplaceCertificate"]; 475 replace["target"] = 476 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"; 477 nlohmann::json::array_t allowed; 478 allowed.emplace_back("PEM"); 479 replace["CertificateType@Redfish.AllowableValues"] = std::move(allowed); 480 actions["#CertificateService.GenerateCSR"]["target"] = 481 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"; 482 } 483 handleCertificateLocationsGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)484 inline void handleCertificateLocationsGet( 485 App& app, const crow::Request& req, 486 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 487 { 488 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 489 { 490 return; 491 } 492 asyncResp->res.jsonValue["@odata.id"] = 493 "/redfish/v1/CertificateService/CertificateLocations"; 494 asyncResp->res.jsonValue["@odata.type"] = 495 "#CertificateLocations.v1_0_0.CertificateLocations"; 496 asyncResp->res.jsonValue["Name"] = "Certificate Locations"; 497 asyncResp->res.jsonValue["Id"] = "CertificateLocations"; 498 asyncResp->res.jsonValue["Description"] = 499 "Defines a resource that an administrator can use in order to " 500 "locate all certificates installed on a given service"; 501 502 getCertificateList(asyncResp, certs::baseObjectPath, 503 "/Links/Certificates"_json_pointer, 504 "/Links/Certificates@odata.count"_json_pointer); 505 } 506 handleError(const std::string_view dbusErrorName,const std::string & id,const std::string & certificate,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)507 inline void handleError(const std::string_view dbusErrorName, 508 const std::string& id, const std::string& certificate, 509 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 510 { 511 if (dbusErrorName == "org.freedesktop.DBus.Error.UnknownObject") 512 { 513 messages::resourceNotFound(asyncResp->res, "Certificate", id); 514 } 515 else if (dbusErrorName == 516 "xyz.openbmc_project.Certs.Error.InvalidCertificate") 517 { 518 messages::propertyValueIncorrect(asyncResp->res, "Certificate", 519 certificate); 520 } 521 else 522 { 523 messages::internalError(asyncResp->res); 524 } 525 } 526 handleReplaceCertificateAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)527 inline void handleReplaceCertificateAction( 528 App& app, const crow::Request& req, 529 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 530 { 531 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 532 { 533 return; 534 } 535 std::string certificate; 536 std::string certURI; 537 std::optional<std::string> certificateType = "PEM"; 538 539 if (!json_util::readJsonAction( // 540 req, asyncResp->res, // 541 "CertificateString", certificate, // 542 "CertificateType", certificateType, // 543 "CertificateUri/@odata.id", certURI // 544 )) 545 { 546 BMCWEB_LOG_ERROR("Required parameters are missing"); 547 return; 548 } 549 550 if (!certificateType) 551 { 552 // should never happen, but it never hurts to be paranoid. 553 return; 554 } 555 if (certificateType != "PEM") 556 { 557 messages::actionParameterNotSupported(asyncResp->res, "CertificateType", 558 "ReplaceCertificate"); 559 return; 560 } 561 562 BMCWEB_LOG_INFO("Certificate URI to replace: {}", certURI); 563 564 boost::system::result<boost::urls::url> parsedUrl = 565 boost::urls::parse_relative_ref(certURI); 566 if (!parsedUrl) 567 { 568 messages::actionParameterValueFormatError( 569 asyncResp->res, certURI, "CertificateUri", "ReplaceCertificate"); 570 return; 571 } 572 573 std::string id; 574 sdbusplus::message::object_path objectPath; 575 std::string name; 576 std::string service; 577 if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", "Managers", 578 "bmc", "NetworkProtocol", "HTTPS", 579 "Certificates", std::ref(id))) 580 { 581 objectPath = sdbusplus::message::object_path(certs::httpsObjectPath) / 582 id; 583 name = "HTTPS certificate"; 584 service = certs::httpsServiceName; 585 } 586 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 587 "AccountService", "LDAP", 588 "Certificates", std::ref(id))) 589 { 590 objectPath = sdbusplus::message::object_path(certs::ldapObjectPath) / 591 id; 592 name = "LDAP certificate"; 593 service = certs::ldapServiceName; 594 } 595 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 596 "Managers", "bmc", "Truststore", 597 "Certificates", std::ref(id))) 598 { 599 objectPath = 600 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 601 name = "TrustStore certificate"; 602 service = certs::authorityServiceName; 603 } 604 else 605 { 606 messages::actionParameterNotSupported(asyncResp->res, "CertificateUri", 607 "ReplaceCertificate"); 608 return; 609 } 610 611 std::shared_ptr<CertificateFile> certFile = 612 std::make_shared<CertificateFile>(certificate); 613 crow::connections::systemBus->async_method_call( 614 [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, name, 615 certificate](const boost::system::error_code& ec, 616 sdbusplus::message_t& m) { 617 if (ec) 618 { 619 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 620 const sd_bus_error* dbusError = m.get_error(); 621 if ((dbusError != nullptr) && (dbusError->name != nullptr)) 622 { 623 handleError(dbusError->name, id, certificate, asyncResp); 624 } 625 else 626 { 627 messages::internalError(asyncResp->res); 628 } 629 return; 630 } 631 getCertificateProperties(asyncResp, objectPath, service, id, url, 632 name); 633 BMCWEB_LOG_DEBUG("HTTPS certificate install file={}", 634 certFile->getCertFilePath()); 635 }, 636 service, objectPath, certs::certReplaceIntf, "Replace", 637 certFile->getCertFilePath()); 638 } 639 640 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 641 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher; 642 /** 643 * @brief Read data from CSR D-bus object and set to response 644 * 645 * @param[in] asyncResp Shared pointer to the response message 646 * @param[in] certURI Link to certificate collection URI 647 * @param[in] service D-Bus service name 648 * @param[in] certObjPath certificate D-Bus object path 649 * @param[in] csrObjPath CSR D-Bus object path 650 * @return None 651 */ getCSR(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & certURI,const std::string & service,const std::string & certObjPath,const std::string & csrObjPath)652 inline void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 653 const std::string& certURI, const std::string& service, 654 const std::string& certObjPath, 655 const std::string& csrObjPath) 656 { 657 BMCWEB_LOG_DEBUG("getCSR CertObjectPath{} CSRObjectPath={} service={}", 658 certObjPath, csrObjPath, service); 659 crow::connections::systemBus->async_method_call( 660 [asyncResp, 661 certURI](const boost::system::error_code& ec, const std::string& csr) { 662 if (ec) 663 { 664 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 665 messages::internalError(asyncResp->res); 666 return; 667 } 668 if (csr.empty()) 669 { 670 BMCWEB_LOG_ERROR("CSR read is empty"); 671 messages::internalError(asyncResp->res); 672 return; 673 } 674 asyncResp->res.jsonValue["CSRString"] = csr; 675 asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] = 676 certURI; 677 }, 678 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 679 } 680 handleGenerateCSRAction(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)681 inline void handleGenerateCSRAction( 682 App& app, const crow::Request& req, 683 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 684 { 685 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 686 { 687 return; 688 } 689 static const int rsaKeyBitLength = 2048; 690 691 // Required parameters 692 std::string city; 693 std::string commonName; 694 std::string country; 695 std::string organization; 696 std::string organizationalUnit; 697 std::string state; 698 std::string certURI; 699 700 // Optional parameters 701 std::optional<std::vector<std::string>> optAlternativeNames = 702 std::vector<std::string>(); 703 std::optional<std::string> optContactPerson = ""; 704 std::optional<std::string> optChallengePassword = ""; 705 std::optional<std::string> optEmail = ""; 706 std::optional<std::string> optGivenName = ""; 707 std::optional<std::string> optInitials = ""; 708 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength; 709 std::optional<std::string> optKeyCurveId = "secp384r1"; 710 std::optional<std::string> optKeyPairAlgorithm = "EC"; 711 std::optional<std::vector<std::string>> optKeyUsage = 712 std::vector<std::string>(); 713 std::optional<std::string> optSurname = ""; 714 std::optional<std::string> optUnstructuredName = ""; 715 if (!json_util::readJsonAction( // 716 req, asyncResp->res, // 717 "AlternativeNames", optAlternativeNames, // 718 "CertificateCollection/@odata.id", certURI, // 719 "ChallengePassword", optChallengePassword, // 720 "City", city, // 721 "CommonName", commonName, // 722 "ContactPerson", optContactPerson, // 723 "Country", country, // 724 "Email", optEmail, // 725 "GivenName", optGivenName, // 726 "Initials", optInitials, // 727 "KeyBitLength", optKeyBitLength, // 728 "KeyCurveId", optKeyCurveId, // 729 "KeyPairAlgorithm", optKeyPairAlgorithm, // 730 "KeyUsage", optKeyUsage, // 731 "Organization", organization, // 732 "OrganizationalUnit", organizationalUnit, // 733 "State", state, // 734 "Surname", optSurname, // 735 "UnstructuredName", optUnstructuredName // 736 )) 737 { 738 return; 739 } 740 741 // bmcweb has no way to store or decode a private key challenge 742 // password, which will likely cause bmcweb to crash on startup 743 // if this is not set on a post so not allowing the user to set 744 // value 745 if (!optChallengePassword->empty()) 746 { 747 messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR", 748 "ChallengePassword"); 749 return; 750 } 751 752 std::string objectPath; 753 std::string service; 754 if (certURI.starts_with(std::format( 755 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates", 756 BMCWEB_REDFISH_MANAGER_URI_NAME))) 757 { 758 objectPath = certs::httpsObjectPath; 759 service = certs::httpsServiceName; 760 } 761 else if (certURI.starts_with( 762 "/redfish/v1/AccountService/LDAP/Certificates")) 763 { 764 objectPath = certs::ldapObjectPath; 765 service = certs::ldapServiceName; 766 } 767 else 768 { 769 messages::actionParameterNotSupported( 770 asyncResp->res, "CertificateCollection", "GenerateCSR"); 771 return; 772 } 773 774 // supporting only EC and RSA algorithm 775 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 776 { 777 messages::actionParameterNotSupported( 778 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 779 return; 780 } 781 782 // supporting only 2048 key bit length for RSA algorithm due to 783 // time consumed in generating private key 784 if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength) 785 { 786 messages::propertyValueNotInList(asyncResp->res, *optKeyBitLength, 787 "KeyBitLength"); 788 return; 789 } 790 791 // validate KeyUsage supporting only 1 type based on URL 792 if (certURI.starts_with(std::format( 793 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates", 794 BMCWEB_REDFISH_MANAGER_URI_NAME))) 795 { 796 if (optKeyUsage->empty()) 797 { 798 optKeyUsage->emplace_back("ServerAuthentication"); 799 } 800 else if (optKeyUsage->size() == 1) 801 { 802 if ((*optKeyUsage)[0] != "ServerAuthentication") 803 { 804 messages::propertyValueNotInList(asyncResp->res, 805 (*optKeyUsage)[0], "KeyUsage"); 806 return; 807 } 808 } 809 else 810 { 811 messages::actionParameterNotSupported(asyncResp->res, "KeyUsage", 812 "GenerateCSR"); 813 return; 814 } 815 } 816 else if (certURI.starts_with( 817 "/redfish/v1/AccountService/LDAP/Certificates")) 818 { 819 if (optKeyUsage->empty()) 820 { 821 optKeyUsage->emplace_back("ClientAuthentication"); 822 } 823 else if (optKeyUsage->size() == 1) 824 { 825 if ((*optKeyUsage)[0] != "ClientAuthentication") 826 { 827 messages::propertyValueNotInList(asyncResp->res, 828 (*optKeyUsage)[0], "KeyUsage"); 829 return; 830 } 831 } 832 else 833 { 834 messages::actionParameterNotSupported(asyncResp->res, "KeyUsage", 835 "GenerateCSR"); 836 return; 837 } 838 } 839 840 // Only allow one CSR matcher at a time so setting retry 841 // time-out and timer expiry to 10 seconds for now. 842 static const int timeOut = 10; 843 if (csrMatcher) 844 { 845 messages::serviceTemporarilyUnavailable(asyncResp->res, 846 std::to_string(timeOut)); 847 return; 848 } 849 850 // Make this static so it survives outside this method 851 static boost::asio::steady_timer timeout(getIoContext()); 852 timeout.expires_after(std::chrono::seconds(timeOut)); 853 timeout.async_wait([asyncResp](const boost::system::error_code& ec) { 854 csrMatcher = nullptr; 855 if (ec) 856 { 857 // operation_aborted is expected if timer is canceled 858 // before completion. 859 if (ec != boost::asio::error::operation_aborted) 860 { 861 BMCWEB_LOG_ERROR("Async_wait failed {}", ec); 862 } 863 return; 864 } 865 BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR"); 866 messages::internalError(asyncResp->res); 867 }); 868 869 // create a matcher to wait on CSR object 870 BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath); 871 std::string match("type='signal'," 872 "interface='org.freedesktop.DBus.ObjectManager'," 873 "path='" + 874 objectPath + 875 "'," 876 "member='InterfacesAdded'"); 877 csrMatcher = std::make_unique<sdbusplus::bus::match_t>( 878 *crow::connections::systemBus, match, 879 [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) { 880 timeout.cancel(); 881 if (m.is_method_error()) 882 { 883 BMCWEB_LOG_ERROR("Dbus method error!!!"); 884 messages::internalError(asyncResp->res); 885 return; 886 } 887 888 dbus::utility::DBusInterfacesMap interfacesProperties; 889 890 sdbusplus::message::object_path csrObjectPath; 891 m.read(csrObjectPath, interfacesProperties); 892 BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str); 893 for (const auto& interface : interfacesProperties) 894 { 895 if (interface.first == "xyz.openbmc_project.Certs.CSR") 896 { 897 getCSR(asyncResp, certURI, service, objectPath, 898 csrObjectPath.str); 899 break; 900 } 901 } 902 }); 903 crow::connections::systemBus->async_method_call( 904 [asyncResp](const boost::system::error_code& ec, const std::string&) { 905 if (ec) 906 { 907 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message()); 908 messages::internalError(asyncResp->res); 909 return; 910 } 911 }, 912 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 913 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 914 commonName, *optContactPerson, country, *optEmail, *optGivenName, 915 *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm, 916 *optKeyUsage, organization, organizationalUnit, state, *optSurname, 917 *optUnstructuredName); 918 } 919 requestRoutesCertificateService(App & app)920 inline void requestRoutesCertificateService(App& app) 921 { 922 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/") 923 .privileges(redfish::privileges::getCertificateService) 924 .methods(boost::beast::http::verb::get)( 925 std::bind_front(handleCertificateServiceGet, std::ref(app))); 926 927 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 928 .privileges(redfish::privileges::getCertificateLocations) 929 .methods(boost::beast::http::verb::get)( 930 std::bind_front(handleCertificateLocationsGet, std::ref(app))); 931 932 BMCWEB_ROUTE( 933 app, 934 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 935 .privileges(redfish::privileges::postCertificateService) 936 .methods(boost::beast::http::verb::post)( 937 std::bind_front(handleReplaceCertificateAction, std::ref(app))); 938 939 BMCWEB_ROUTE( 940 app, 941 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 942 .privileges(redfish::privileges::postCertificateService) 943 .methods(boost::beast::http::verb::post)( 944 std::bind_front(handleGenerateCSRAction, std::ref(app))); 945 } // requestRoutesCertificateService 946 handleHTTPSCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)947 inline void handleHTTPSCertificateCollectionGet( 948 App& app, const crow::Request& req, 949 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 950 const std::string& managerId) 951 { 952 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 953 { 954 return; 955 } 956 957 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 958 { 959 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 960 return; 961 } 962 963 asyncResp->res.jsonValue["@odata.id"] = boost::urls::format( 964 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates", 965 BMCWEB_REDFISH_MANAGER_URI_NAME); 966 asyncResp->res.jsonValue["@odata.type"] = 967 "#CertificateCollection.CertificateCollection"; 968 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection"; 969 asyncResp->res.jsonValue["Description"] = 970 "A Collection of HTTPS certificate instances"; 971 972 getCertificateList(asyncResp, certs::httpsObjectPath, 973 "/Members"_json_pointer, 974 "/Members@odata.count"_json_pointer); 975 } 976 handleHTTPSCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)977 inline void handleHTTPSCertificateCollectionPost( 978 App& app, const crow::Request& req, 979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 980 const std::string& managerId) 981 { 982 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 983 { 984 return; 985 } 986 987 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 988 { 989 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 990 return; 991 } 992 993 BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost"); 994 995 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate"; 996 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate"; 997 998 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 999 1000 if (certHttpBody.empty()) 1001 { 1002 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 1003 messages::unrecognizedRequestBody(asyncResp->res); 1004 return; 1005 } 1006 1007 std::shared_ptr<CertificateFile> certFile = 1008 std::make_shared<CertificateFile>(certHttpBody); 1009 1010 crow::connections::systemBus->async_method_call( 1011 [asyncResp, certFile](const boost::system::error_code& ec, 1012 const std::string& objectPath) { 1013 if (ec) 1014 { 1015 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 1016 messages::internalError(asyncResp->res); 1017 return; 1018 } 1019 1020 sdbusplus::message::object_path path(objectPath); 1021 std::string certId = path.filename(); 1022 const boost::urls::url certURL = boost::urls::format( 1023 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}", 1024 BMCWEB_REDFISH_MANAGER_URI_NAME, certId); 1025 getCertificateProperties(asyncResp, objectPath, 1026 certs::httpsServiceName, certId, certURL, 1027 "HTTPS Certificate"); 1028 BMCWEB_LOG_DEBUG("HTTPS certificate install file={}", 1029 certFile->getCertFilePath()); 1030 }, 1031 certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf, 1032 "Install", certFile->getCertFilePath()); 1033 } 1034 handleHTTPSCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1035 inline void handleHTTPSCertificateGet( 1036 App& app, const crow::Request& req, 1037 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1038 const std::string& managerId, const std::string& certId) 1039 { 1040 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1041 { 1042 return; 1043 } 1044 1045 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 1046 { 1047 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 1048 return; 1049 } 1050 1051 BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", certId); 1052 const boost::urls::url certURL = boost::urls::format( 1053 "/redfish/v1/Managers/{}/NetworkProtocol/HTTPS/Certificates/{}", 1054 BMCWEB_REDFISH_MANAGER_URI_NAME, certId); 1055 std::string objPath = 1056 sdbusplus::message::object_path(certs::httpsObjectPath) / certId; 1057 getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, 1058 certId, certURL, "HTTPS Certificate"); 1059 } 1060 requestRoutesHTTPSCertificate(App & app)1061 inline void requestRoutesHTTPSCertificate(App& app) 1062 { 1063 BMCWEB_ROUTE( 1064 app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/") 1065 .privileges(redfish::privileges::getCertificateCollection) 1066 .methods(boost::beast::http::verb::get)(std::bind_front( 1067 handleHTTPSCertificateCollectionGet, std::ref(app))); 1068 1069 BMCWEB_ROUTE( 1070 app, "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/") 1071 .privileges(redfish::privileges::postCertificateCollection) 1072 .methods(boost::beast::http::verb::post)(std::bind_front( 1073 handleHTTPSCertificateCollectionPost, std::ref(app))); 1074 1075 BMCWEB_ROUTE( 1076 app, 1077 "/redfish/v1/Managers/<str>/NetworkProtocol/HTTPS/Certificates/<str>/") 1078 .privileges(redfish::privileges::getCertificate) 1079 .methods(boost::beast::http::verb::get)( 1080 std::bind_front(handleHTTPSCertificateGet, std::ref(app))); 1081 } 1082 handleLDAPCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1083 inline void handleLDAPCertificateCollectionGet( 1084 App& app, const crow::Request& req, 1085 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1086 { 1087 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1088 { 1089 return; 1090 } 1091 1092 asyncResp->res.jsonValue["@odata.id"] = 1093 "/redfish/v1/AccountService/LDAP/Certificates"; 1094 asyncResp->res.jsonValue["@odata.type"] = 1095 "#CertificateCollection.CertificateCollection"; 1096 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection"; 1097 asyncResp->res.jsonValue["Description"] = 1098 "A Collection of LDAP certificate instances"; 1099 1100 getCertificateList(asyncResp, certs::ldapObjectPath, 1101 "/Members"_json_pointer, 1102 "/Members@odata.count"_json_pointer); 1103 } 1104 handleLDAPCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)1105 inline void handleLDAPCertificateCollectionPost( 1106 App& app, const crow::Request& req, 1107 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1108 { 1109 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1110 { 1111 return; 1112 } 1113 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 1114 1115 if (certHttpBody.empty()) 1116 { 1117 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 1118 messages::unrecognizedRequestBody(asyncResp->res); 1119 return; 1120 } 1121 1122 std::shared_ptr<CertificateFile> certFile = 1123 std::make_shared<CertificateFile>(certHttpBody); 1124 1125 crow::connections::systemBus->async_method_call( 1126 [asyncResp, certFile](const boost::system::error_code& ec, 1127 const std::string& objectPath) { 1128 if (ec) 1129 { 1130 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 1131 messages::internalError(asyncResp->res); 1132 return; 1133 } 1134 1135 sdbusplus::message::object_path path(objectPath); 1136 std::string certId = path.filename(); 1137 const boost::urls::url certURL = boost::urls::format( 1138 "/redfish/v1/AccountService/LDAP/Certificates/{}", certId); 1139 getCertificateProperties(asyncResp, objectPath, 1140 certs::ldapServiceName, certId, certURL, 1141 "LDAP Certificate"); 1142 BMCWEB_LOG_DEBUG("LDAP certificate install file={}", 1143 certFile->getCertFilePath()); 1144 }, 1145 certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf, 1146 "Install", certFile->getCertFilePath()); 1147 } 1148 handleLDAPCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1149 inline void handleLDAPCertificateGet( 1150 App& app, const crow::Request& req, 1151 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1152 { 1153 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1154 { 1155 return; 1156 } 1157 1158 BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id); 1159 const boost::urls::url certURL = boost::urls::format( 1160 "/redfish/v1/AccountService/LDAP/Certificates/{}", id); 1161 std::string objPath = 1162 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1163 getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id, 1164 certURL, "LDAP Certificate"); 1165 } 1166 handleLDAPCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & id)1167 inline void handleLDAPCertificateDelete( 1168 App& app, const crow::Request& req, 1169 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1170 { 1171 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1172 { 1173 return; 1174 } 1175 1176 BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id); 1177 std::string objPath = 1178 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1179 1180 deleteCertificate(asyncResp, certs::ldapServiceName, objPath); 1181 } 1182 requestRoutesLDAPCertificate(App & app)1183 inline void requestRoutesLDAPCertificate(App& app) 1184 { 1185 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1186 .privileges(redfish::privileges::getCertificateCollection) 1187 .methods(boost::beast::http::verb::get)( 1188 std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app))); 1189 1190 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1191 .privileges(redfish::privileges::postCertificateCollection) 1192 .methods(boost::beast::http::verb::post)(std::bind_front( 1193 handleLDAPCertificateCollectionPost, std::ref(app))); 1194 1195 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1196 .privileges(redfish::privileges::getCertificate) 1197 .methods(boost::beast::http::verb::get)( 1198 std::bind_front(handleLDAPCertificateGet, std::ref(app))); 1199 1200 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1201 .privileges(redfish::privileges::deleteCertificate) 1202 .methods(boost::beast::http::verb::delete_)( 1203 std::bind_front(handleLDAPCertificateDelete, std::ref(app))); 1204 } // requestRoutesLDAPCertificate 1205 handleTrustStoreCertificateCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1206 inline void handleTrustStoreCertificateCollectionGet( 1207 App& app, const crow::Request& req, 1208 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1209 const std::string& managerId) 1210 { 1211 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1212 { 1213 return; 1214 } 1215 1216 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 1217 { 1218 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 1219 return; 1220 } 1221 1222 asyncResp->res.jsonValue["@odata.id"] = 1223 boost::urls::format("/redfish/v1/Managers/{}/Truststore/Certificates/", 1224 BMCWEB_REDFISH_MANAGER_URI_NAME); 1225 asyncResp->res.jsonValue["@odata.type"] = 1226 "#CertificateCollection.CertificateCollection"; 1227 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1228 asyncResp->res.jsonValue["Description"] = 1229 "A Collection of TrustStore certificate instances"; 1230 1231 getCertificateList(asyncResp, certs::authorityObjectPath, 1232 "/Members"_json_pointer, 1233 "/Members@odata.count"_json_pointer); 1234 } 1235 handleTrustStoreCertificateCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId)1236 inline void handleTrustStoreCertificateCollectionPost( 1237 App& app, const crow::Request& req, 1238 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1239 const std::string& managerId) 1240 { 1241 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1242 { 1243 return; 1244 } 1245 1246 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 1247 { 1248 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 1249 return; 1250 } 1251 1252 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 1253 1254 if (certHttpBody.empty()) 1255 { 1256 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 1257 messages::unrecognizedRequestBody(asyncResp->res); 1258 return; 1259 } 1260 1261 std::shared_ptr<CertificateFile> certFile = 1262 std::make_shared<CertificateFile>(certHttpBody); 1263 crow::connections::systemBus->async_method_call( 1264 [asyncResp, certFile](const boost::system::error_code& ec, 1265 const std::string& objectPath) { 1266 if (ec) 1267 { 1268 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 1269 messages::internalError(asyncResp->res); 1270 return; 1271 } 1272 1273 sdbusplus::message::object_path path(objectPath); 1274 std::string certId = path.filename(); 1275 const boost::urls::url certURL = boost::urls::format( 1276 "/redfish/v1/Managers/{}/Truststore/Certificates/{}", 1277 BMCWEB_REDFISH_MANAGER_URI_NAME, certId); 1278 getCertificateProperties(asyncResp, objectPath, 1279 certs::authorityServiceName, certId, 1280 certURL, "TrustStore Certificate"); 1281 BMCWEB_LOG_DEBUG("TrustStore certificate install file={}", 1282 certFile->getCertFilePath()); 1283 }, 1284 certs::authorityServiceName, certs::authorityObjectPath, 1285 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1286 } 1287 handleTrustStoreCertificateGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1288 inline void handleTrustStoreCertificateGet( 1289 App& app, const crow::Request& req, 1290 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1291 const std::string& managerId, const std::string& certId) 1292 { 1293 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1294 { 1295 return; 1296 } 1297 1298 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 1299 { 1300 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 1301 return; 1302 } 1303 1304 BMCWEB_LOG_DEBUG("Truststore Certificate ID={}", certId); 1305 const boost::urls::url certURL = boost::urls::format( 1306 "/redfish/v1/Managers/{}/Truststore/Certificates/{}", 1307 BMCWEB_REDFISH_MANAGER_URI_NAME, certId); 1308 std::string objPath = 1309 sdbusplus::message::object_path(certs::authorityObjectPath) / certId; 1310 getCertificateProperties(asyncResp, objPath, certs::authorityServiceName, 1311 certId, certURL, "TrustStore Certificate"); 1312 } 1313 handleTrustStoreCertificateDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & managerId,const std::string & certId)1314 inline void handleTrustStoreCertificateDelete( 1315 App& app, const crow::Request& req, 1316 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1317 const std::string& managerId, const std::string& certId) 1318 { 1319 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1320 { 1321 return; 1322 } 1323 1324 if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME) 1325 { 1326 messages::resourceNotFound(asyncResp->res, "Manager", managerId); 1327 return; 1328 } 1329 1330 BMCWEB_LOG_DEBUG("Delete TrustStore Certificate ID={}", certId); 1331 std::string objPath = 1332 sdbusplus::message::object_path(certs::authorityObjectPath) / certId; 1333 1334 deleteCertificate(asyncResp, certs::authorityServiceName, objPath); 1335 } 1336 requestRoutesTrustStoreCertificate(App & app)1337 inline void requestRoutesTrustStoreCertificate(App& app) 1338 { 1339 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/") 1340 .privileges(redfish::privileges::getCertificate) 1341 .methods(boost::beast::http::verb::get)(std::bind_front( 1342 handleTrustStoreCertificateCollectionGet, std::ref(app))); 1343 1344 BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/Truststore/Certificates/") 1345 .privileges(redfish::privileges::postCertificateCollection) 1346 .methods(boost::beast::http::verb::post)(std::bind_front( 1347 handleTrustStoreCertificateCollectionPost, std::ref(app))); 1348 1349 BMCWEB_ROUTE(app, 1350 "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/") 1351 .privileges(redfish::privileges::getCertificate) 1352 .methods(boost::beast::http::verb::get)( 1353 std::bind_front(handleTrustStoreCertificateGet, std::ref(app))); 1354 1355 BMCWEB_ROUTE(app, 1356 "/redfish/v1/Managers/<str>/Truststore/Certificates/<str>/") 1357 .privileges(redfish::privileges::deleteCertificate) 1358 .methods(boost::beast::http::verb::delete_)( 1359 std::bind_front(handleTrustStoreCertificateDelete, std::ref(app))); 1360 } // requestRoutesTrustStoreCertificate 1361 } // namespace redfish 1362