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