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