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