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