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