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 requestRoutesLDAPCertificate(App& app) 1072 { 1073 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1074 .privileges(redfish::privileges::getCertificateCollection) 1075 .methods(boost::beast::http::verb::get)( 1076 std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app))); 1077 1078 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1079 .privileges(redfish::privileges::postCertificateCollection) 1080 .methods(boost::beast::http::verb::post)(std::bind_front( 1081 handleLDAPCertificateCollectionPost, std::ref(app))); 1082 1083 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1084 .privileges(redfish::privileges::getCertificate) 1085 .methods(boost::beast::http::verb::get)( 1086 std::bind_front(handleLDAPCertificateGet, std::ref(app))); 1087 } // requestRoutesLDAPCertificate 1088 1089 inline void handleTrustStoreCertificateCollectionGet( 1090 App& app, const crow::Request& req, 1091 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1092 { 1093 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1094 { 1095 return; 1096 } 1097 1098 asyncResp->res.jsonValue["@odata.id"] = 1099 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1100 asyncResp->res.jsonValue["@odata.type"] = 1101 "#CertificateCollection.CertificateCollection"; 1102 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1103 asyncResp->res.jsonValue["Description"] = 1104 "A Collection of TrustStore certificate instances"; 1105 1106 getCertificateList(asyncResp, certs::authorityObjectPath, 1107 "/Members"_json_pointer, 1108 "/Members@odata.count"_json_pointer); 1109 } 1110 1111 inline void handleTrustStoreCertificateCollectionPost( 1112 App& app, const crow::Request& req, 1113 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1114 { 1115 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1116 { 1117 return; 1118 } 1119 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1120 1121 if (certFileBody.empty()) 1122 { 1123 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1124 messages::unrecognizedRequestBody(asyncResp->res); 1125 return; 1126 } 1127 1128 std::shared_ptr<CertificateFile> certFile = 1129 std::make_shared<CertificateFile>(certFileBody); 1130 crow::connections::systemBus->async_method_call( 1131 [asyncResp, certFile](const boost::system::error_code ec, 1132 const std::string& objectPath) { 1133 if (ec) 1134 { 1135 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1136 messages::internalError(asyncResp->res); 1137 return; 1138 } 1139 1140 sdbusplus::message::object_path path(objectPath); 1141 std::string certId = path.filename(); 1142 const boost::urls::url certURL = 1143 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 1144 "Truststore", "Certificates", certId); 1145 getCertificateProperties(asyncResp, objectPath, 1146 certs::authorityServiceName, certId, certURL, 1147 "TrustStore Certificate"); 1148 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1149 << certFile->getCertFilePath(); 1150 }, 1151 certs::authorityServiceName, certs::authorityObjectPath, 1152 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1153 } 1154 1155 inline void handleTrustStoreCertificateGet( 1156 App& app, const crow::Request& req, 1157 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1158 { 1159 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1160 { 1161 return; 1162 } 1163 1164 BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id; 1165 const boost::urls::url certURL = crow::utility::urlFromPieces( 1166 "redfish", "v1", "Managers", "bmc", "Truststore", "Certificates", id); 1167 std::string objPath = 1168 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1169 getCertificateProperties(asyncResp, objPath, certs::authorityServiceName, 1170 id, certURL, "TrustStore Certificate"); 1171 } 1172 1173 inline void handleTrustStoreCertificateDelete( 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 << "Delete TrustStore Certificate ID=" << id; 1183 std::string objPath = 1184 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1185 1186 deleteCertificate(asyncResp, certs::authorityServiceName, objPath); 1187 } 1188 1189 inline void requestRoutesTrustStoreCertificate(App& app) 1190 { 1191 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1192 .privileges(redfish::privileges::getCertificate) 1193 .methods(boost::beast::http::verb::get)(std::bind_front( 1194 handleTrustStoreCertificateCollectionGet, std::ref(app))); 1195 1196 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1197 .privileges(redfish::privileges::postCertificateCollection) 1198 .methods(boost::beast::http::verb::post)(std::bind_front( 1199 handleTrustStoreCertificateCollectionPost, std::ref(app))); 1200 1201 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1202 .privileges(redfish::privileges::getCertificate) 1203 .methods(boost::beast::http::verb::get)( 1204 std::bind_front(handleTrustStoreCertificateGet, std::ref(app))); 1205 1206 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1207 .privileges(redfish::privileges::deleteCertificate) 1208 .methods(boost::beast::http::verb::delete_)( 1209 std::bind_front(handleTrustStoreCertificateDelete, std::ref(app))); 1210 } // requestRoutesTrustStoreCertificate 1211 } // namespace redfish 1212