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