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