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