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 // Make this static so it survives outside this method 770 static boost::asio::steady_timer timeout(*req.ioService); 771 timeout.expires_after(std::chrono::seconds(timeOut)); 772 timeout.async_wait([asyncResp](const boost::system::error_code& ec) { 773 csrMatcher = nullptr; 774 if (ec) 775 { 776 // operation_aborted is expected if timer is canceled 777 // before completion. 778 if (ec != boost::asio::error::operation_aborted) 779 { 780 BMCWEB_LOG_ERROR("Async_wait failed {}", ec); 781 } 782 return; 783 } 784 BMCWEB_LOG_ERROR("Timed out waiting for Generating CSR"); 785 messages::internalError(asyncResp->res); 786 }); 787 788 // create a matcher to wait on CSR object 789 BMCWEB_LOG_DEBUG("create matcher with path {}", objectPath); 790 std::string match("type='signal'," 791 "interface='org.freedesktop.DBus.ObjectManager'," 792 "path='" + 793 objectPath + 794 "'," 795 "member='InterfacesAdded'"); 796 csrMatcher = std::make_unique<sdbusplus::bus::match_t>( 797 *crow::connections::systemBus, match, 798 [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) { 799 timeout.cancel(); 800 if (m.is_method_error()) 801 { 802 BMCWEB_LOG_ERROR("Dbus method error!!!"); 803 messages::internalError(asyncResp->res); 804 return; 805 } 806 807 dbus::utility::DBusInterfacesMap interfacesProperties; 808 809 sdbusplus::message::object_path csrObjectPath; 810 m.read(csrObjectPath, interfacesProperties); 811 BMCWEB_LOG_DEBUG("CSR object added{}", csrObjectPath.str); 812 for (const auto& interface : interfacesProperties) 813 { 814 if (interface.first == "xyz.openbmc_project.Certs.CSR") 815 { 816 getCSR(asyncResp, certURI, service, objectPath, 817 csrObjectPath.str); 818 break; 819 } 820 } 821 }); 822 crow::connections::systemBus->async_method_call( 823 [asyncResp](const boost::system::error_code& ec, const std::string&) { 824 if (ec) 825 { 826 BMCWEB_LOG_ERROR("DBUS response error: {}", ec.message()); 827 messages::internalError(asyncResp->res); 828 return; 829 } 830 }, 831 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 832 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 833 commonName, *optContactPerson, country, *optEmail, *optGivenName, 834 *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm, 835 *optKeyUsage, organization, organizationalUnit, state, *optSurname, 836 *optUnstructuredName); 837 } 838 839 inline void requestRoutesCertificateService(App& app) 840 { 841 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/") 842 .privileges(redfish::privileges::getCertificateService) 843 .methods(boost::beast::http::verb::get)( 844 std::bind_front(handleCertificateServiceGet, std::ref(app))); 845 846 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 847 .privileges(redfish::privileges::getCertificateLocations) 848 .methods(boost::beast::http::verb::get)( 849 std::bind_front(handleCertificateLocationsGet, std::ref(app))); 850 851 BMCWEB_ROUTE( 852 app, 853 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 854 .privileges(redfish::privileges::postCertificateService) 855 .methods(boost::beast::http::verb::post)( 856 std::bind_front(handleReplaceCertificateAction, std::ref(app))); 857 858 BMCWEB_ROUTE( 859 app, 860 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 861 .privileges(redfish::privileges::postCertificateService) 862 .methods(boost::beast::http::verb::post)( 863 std::bind_front(handleGenerateCSRAction, std::ref(app))); 864 } // requestRoutesCertificateService 865 866 inline void handleHTTPSCertificateCollectionGet( 867 App& app, const crow::Request& req, 868 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 869 { 870 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 871 { 872 return; 873 } 874 875 asyncResp->res.jsonValue["@odata.id"] = 876 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 877 asyncResp->res.jsonValue["@odata.type"] = 878 "#CertificateCollection.CertificateCollection"; 879 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection"; 880 asyncResp->res.jsonValue["Description"] = 881 "A Collection of HTTPS certificate instances"; 882 883 getCertificateList(asyncResp, certs::httpsObjectPath, 884 "/Members"_json_pointer, 885 "/Members@odata.count"_json_pointer); 886 } 887 888 inline void handleHTTPSCertificateCollectionPost( 889 App& app, const crow::Request& req, 890 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 891 { 892 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 893 { 894 return; 895 } 896 BMCWEB_LOG_DEBUG("HTTPSCertificateCollection::doPost"); 897 898 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate"; 899 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate"; 900 901 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 902 903 if (certHttpBody.empty()) 904 { 905 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 906 messages::unrecognizedRequestBody(asyncResp->res); 907 return; 908 } 909 910 std::shared_ptr<CertificateFile> certFile = 911 std::make_shared<CertificateFile>(certHttpBody); 912 913 crow::connections::systemBus->async_method_call( 914 [asyncResp, certFile](const boost::system::error_code& ec, 915 const std::string& objectPath) { 916 if (ec) 917 { 918 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 919 messages::internalError(asyncResp->res); 920 return; 921 } 922 923 sdbusplus::message::object_path path(objectPath); 924 std::string certId = path.filename(); 925 const boost::urls::url certURL = boost::urls::format( 926 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}", 927 certId); 928 getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName, 929 certId, certURL, "HTTPS Certificate"); 930 BMCWEB_LOG_DEBUG("HTTPS certificate install file={}", 931 certFile->getCertFilePath()); 932 }, 933 certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf, 934 "Install", certFile->getCertFilePath()); 935 } 936 937 inline void handleHTTPSCertificateGet( 938 App& app, const crow::Request& req, 939 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 940 { 941 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 942 { 943 return; 944 } 945 946 BMCWEB_LOG_DEBUG("HTTPS Certificate ID={}", id); 947 const boost::urls::url certURL = boost::urls::format( 948 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/{}", id); 949 std::string objPath = 950 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 951 getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, id, 952 certURL, "HTTPS Certificate"); 953 } 954 955 inline void requestRoutesHTTPSCertificate(App& app) 956 { 957 BMCWEB_ROUTE(app, 958 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 959 .privileges(redfish::privileges::getCertificateCollection) 960 .methods(boost::beast::http::verb::get)(std::bind_front( 961 handleHTTPSCertificateCollectionGet, std::ref(app))); 962 963 BMCWEB_ROUTE(app, 964 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 965 .privileges(redfish::privileges::postCertificateCollection) 966 .methods(boost::beast::http::verb::post)(std::bind_front( 967 handleHTTPSCertificateCollectionPost, std::ref(app))); 968 969 BMCWEB_ROUTE( 970 app, 971 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/") 972 .privileges(redfish::privileges::getCertificate) 973 .methods(boost::beast::http::verb::get)( 974 std::bind_front(handleHTTPSCertificateGet, std::ref(app))); 975 } 976 977 inline void handleLDAPCertificateCollectionGet( 978 App& app, const crow::Request& req, 979 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 980 { 981 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 982 { 983 return; 984 } 985 986 asyncResp->res.jsonValue["@odata.id"] = 987 "/redfish/v1/AccountService/LDAP/Certificates"; 988 asyncResp->res.jsonValue["@odata.type"] = 989 "#CertificateCollection.CertificateCollection"; 990 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection"; 991 asyncResp->res.jsonValue["Description"] = 992 "A Collection of LDAP certificate instances"; 993 994 getCertificateList(asyncResp, certs::ldapObjectPath, 995 "/Members"_json_pointer, 996 "/Members@odata.count"_json_pointer); 997 } 998 999 inline void handleLDAPCertificateCollectionPost( 1000 App& app, const crow::Request& req, 1001 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1002 { 1003 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1004 { 1005 return; 1006 } 1007 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 1008 1009 if (certHttpBody.empty()) 1010 { 1011 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 1012 messages::unrecognizedRequestBody(asyncResp->res); 1013 return; 1014 } 1015 1016 std::shared_ptr<CertificateFile> certFile = 1017 std::make_shared<CertificateFile>(certHttpBody); 1018 1019 crow::connections::systemBus->async_method_call( 1020 [asyncResp, certFile](const boost::system::error_code& ec, 1021 const std::string& objectPath) { 1022 if (ec) 1023 { 1024 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 1025 messages::internalError(asyncResp->res); 1026 return; 1027 } 1028 1029 sdbusplus::message::object_path path(objectPath); 1030 std::string certId = path.filename(); 1031 const boost::urls::url certURL = boost::urls::format( 1032 "/redfish/v1/AccountService/LDAP/Certificates/{}", certId); 1033 getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName, 1034 certId, certURL, "LDAP Certificate"); 1035 BMCWEB_LOG_DEBUG("LDAP certificate install file={}", 1036 certFile->getCertFilePath()); 1037 }, 1038 certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf, 1039 "Install", certFile->getCertFilePath()); 1040 } 1041 1042 inline void handleLDAPCertificateGet( 1043 App& app, const crow::Request& req, 1044 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1045 { 1046 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1047 { 1048 return; 1049 } 1050 1051 BMCWEB_LOG_DEBUG("LDAP Certificate ID={}", id); 1052 const boost::urls::url certURL = boost::urls::format( 1053 "/redfish/v1/AccountService/LDAP/Certificates/{}", id); 1054 std::string objPath = 1055 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1056 getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id, 1057 certURL, "LDAP Certificate"); 1058 } 1059 1060 inline void handleLDAPCertificateDelete( 1061 App& app, const crow::Request& req, 1062 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1063 { 1064 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1065 { 1066 return; 1067 } 1068 1069 BMCWEB_LOG_DEBUG("Delete LDAP Certificate ID={}", id); 1070 std::string objPath = 1071 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1072 1073 deleteCertificate(asyncResp, certs::ldapServiceName, objPath); 1074 } 1075 1076 inline void requestRoutesLDAPCertificate(App& app) 1077 { 1078 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1079 .privileges(redfish::privileges::getCertificateCollection) 1080 .methods(boost::beast::http::verb::get)( 1081 std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app))); 1082 1083 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1084 .privileges(redfish::privileges::postCertificateCollection) 1085 .methods(boost::beast::http::verb::post)(std::bind_front( 1086 handleLDAPCertificateCollectionPost, std::ref(app))); 1087 1088 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1089 .privileges(redfish::privileges::getCertificate) 1090 .methods(boost::beast::http::verb::get)( 1091 std::bind_front(handleLDAPCertificateGet, std::ref(app))); 1092 1093 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1094 .privileges(redfish::privileges::deleteCertificate) 1095 .methods(boost::beast::http::verb::delete_)( 1096 std::bind_front(handleLDAPCertificateDelete, std::ref(app))); 1097 } // requestRoutesLDAPCertificate 1098 1099 inline void handleTrustStoreCertificateCollectionGet( 1100 App& app, const crow::Request& req, 1101 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1102 { 1103 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1104 { 1105 return; 1106 } 1107 1108 asyncResp->res.jsonValue["@odata.id"] = 1109 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1110 asyncResp->res.jsonValue["@odata.type"] = 1111 "#CertificateCollection.CertificateCollection"; 1112 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1113 asyncResp->res.jsonValue["Description"] = 1114 "A Collection of TrustStore certificate instances"; 1115 1116 getCertificateList(asyncResp, certs::authorityObjectPath, 1117 "/Members"_json_pointer, 1118 "/Members@odata.count"_json_pointer); 1119 } 1120 1121 inline void handleTrustStoreCertificateCollectionPost( 1122 App& app, const crow::Request& req, 1123 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1124 { 1125 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1126 { 1127 return; 1128 } 1129 std::string certHttpBody = getCertificateFromReqBody(asyncResp, req); 1130 1131 if (certHttpBody.empty()) 1132 { 1133 BMCWEB_LOG_ERROR("Cannot get certificate from request body."); 1134 messages::unrecognizedRequestBody(asyncResp->res); 1135 return; 1136 } 1137 1138 std::shared_ptr<CertificateFile> certFile = 1139 std::make_shared<CertificateFile>(certHttpBody); 1140 crow::connections::systemBus->async_method_call( 1141 [asyncResp, certFile](const boost::system::error_code& ec, 1142 const std::string& objectPath) { 1143 if (ec) 1144 { 1145 BMCWEB_LOG_ERROR("DBUS response error: {}", ec); 1146 messages::internalError(asyncResp->res); 1147 return; 1148 } 1149 1150 sdbusplus::message::object_path path(objectPath); 1151 std::string certId = path.filename(); 1152 const boost::urls::url certURL = boost::urls::format( 1153 "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", certId); 1154 getCertificateProperties(asyncResp, objectPath, 1155 certs::authorityServiceName, certId, certURL, 1156 "TrustStore Certificate"); 1157 BMCWEB_LOG_DEBUG("TrustStore certificate install file={}", 1158 certFile->getCertFilePath()); 1159 }, 1160 certs::authorityServiceName, certs::authorityObjectPath, 1161 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1162 } 1163 1164 inline void handleTrustStoreCertificateGet( 1165 App& app, const crow::Request& req, 1166 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1167 { 1168 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1169 { 1170 return; 1171 } 1172 1173 BMCWEB_LOG_DEBUG("Truststore Certificate ID={}", id); 1174 const boost::urls::url certURL = boost::urls::format( 1175 "/redfish/v1/Managers/bmc/Truststore/Certificates/{}", id); 1176 std::string objPath = 1177 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1178 getCertificateProperties(asyncResp, objPath, certs::authorityServiceName, 1179 id, certURL, "TrustStore Certificate"); 1180 } 1181 1182 inline void handleTrustStoreCertificateDelete( 1183 App& app, const crow::Request& req, 1184 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1185 { 1186 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1187 { 1188 return; 1189 } 1190 1191 BMCWEB_LOG_DEBUG("Delete TrustStore Certificate ID={}", id); 1192 std::string objPath = 1193 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1194 1195 deleteCertificate(asyncResp, certs::authorityServiceName, objPath); 1196 } 1197 1198 inline void requestRoutesTrustStoreCertificate(App& app) 1199 { 1200 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1201 .privileges(redfish::privileges::getCertificate) 1202 .methods(boost::beast::http::verb::get)(std::bind_front( 1203 handleTrustStoreCertificateCollectionGet, std::ref(app))); 1204 1205 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1206 .privileges(redfish::privileges::postCertificateCollection) 1207 .methods(boost::beast::http::verb::post)(std::bind_front( 1208 handleTrustStoreCertificateCollectionPost, std::ref(app))); 1209 1210 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1211 .privileges(redfish::privileges::getCertificate) 1212 .methods(boost::beast::http::verb::get)( 1213 std::bind_front(handleTrustStoreCertificateGet, std::ref(app))); 1214 1215 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1216 .privileges(redfish::privileges::deleteCertificate) 1217 .methods(boost::beast::http::verb::delete_)( 1218 std::bind_front(handleTrustStoreCertificateDelete, std::ref(app))); 1219 } // requestRoutesTrustStoreCertificate 1220 } // namespace redfish 1221