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