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