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