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