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 inline void handleCertificateServiceGet( 380 App& app, const crow::Request& req, 381 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 382 { 383 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 384 { 385 return; 386 } 387 388 asyncResp->res.jsonValue["@odata.type"] = 389 "#CertificateService.v1_0_0.CertificateService"; 390 asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/CertificateService"; 391 asyncResp->res.jsonValue["Id"] = "CertificateService"; 392 asyncResp->res.jsonValue["Name"] = "Certificate Service"; 393 asyncResp->res.jsonValue["Description"] = 394 "Actions available to manage certificates"; 395 // /redfish/v1/CertificateService/CertificateLocations is something 396 // only ConfigureManager can access then only display when the user 397 // has permissions ConfigureManager 398 Privileges effectiveUserPrivileges = 399 redfish::getUserPrivileges(req.userRole); 400 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 401 effectiveUserPrivileges)) 402 { 403 asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] = 404 "/redfish/v1/CertificateService/CertificateLocations"; 405 } 406 nlohmann::json& actions = asyncResp->res.jsonValue["Actions"]; 407 nlohmann::json& replace = actions["#CertificateService.ReplaceCertificate"]; 408 replace["target"] = 409 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"; 410 nlohmann::json::array_t allowed; 411 allowed.push_back("PEM"); 412 replace["CertificateType@Redfish.AllowableValues"] = std::move(allowed); 413 actions["#CertificateService.GenerateCSR"]["target"] = 414 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"; 415 } 416 417 inline void handleCertificateLocationsGet( 418 App& app, const crow::Request& req, 419 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 420 { 421 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 422 { 423 return; 424 } 425 asyncResp->res.jsonValue["@odata.id"] = 426 "/redfish/v1/CertificateService/CertificateLocations"; 427 asyncResp->res.jsonValue["@odata.type"] = 428 "#CertificateLocations.v1_0_0.CertificateLocations"; 429 asyncResp->res.jsonValue["Name"] = "Certificate Locations"; 430 asyncResp->res.jsonValue["Id"] = "CertificateLocations"; 431 asyncResp->res.jsonValue["Description"] = 432 "Defines a resource that an administrator can use in order to " 433 "locate all certificates installed on a given service"; 434 435 getCertificateList(asyncResp, certs::baseObjectPath, 436 "/Links/Certificates"_json_pointer, 437 "/Links/Certificates@odata.count"_json_pointer); 438 } 439 440 inline void handleReplaceCertificateAction( 441 App& app, const crow::Request& req, 442 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 443 { 444 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 445 { 446 return; 447 } 448 std::string certificate; 449 nlohmann::json certificateUri; 450 std::optional<std::string> certificateType = "PEM"; 451 452 if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString", 453 certificate, "CertificateUri", 454 certificateUri, "CertificateType", 455 certificateType)) 456 { 457 BMCWEB_LOG_ERROR << "Required parameters are missing"; 458 messages::internalError(asyncResp->res); 459 return; 460 } 461 462 if (!certificateType) 463 { 464 // should never happen, but it never hurts to be paranoid. 465 return; 466 } 467 if (certificateType != "PEM") 468 { 469 messages::actionParameterNotSupported(asyncResp->res, "CertificateType", 470 "ReplaceCertificate"); 471 return; 472 } 473 474 std::string certURI; 475 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 476 "@odata.id", certURI)) 477 { 478 messages::actionParameterMissing(asyncResp->res, "ReplaceCertificate", 479 "CertificateUri"); 480 return; 481 } 482 BMCWEB_LOG_INFO << "Certificate URI to replace: " << certURI; 483 484 boost::urls::result<boost::urls::url_view> parsedUrl = 485 boost::urls::parse_relative_ref(certURI); 486 if (!parsedUrl) 487 { 488 messages::actionParameterValueFormatError( 489 asyncResp->res, certURI, "CertificateUri", "ReplaceCertificate"); 490 return; 491 } 492 493 std::string id; 494 sdbusplus::message::object_path objectPath; 495 std::string name; 496 std::string service; 497 if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", "Managers", 498 "bmc", "NetworkProtocol", "HTTPS", 499 "Certificates", std::ref(id))) 500 { 501 objectPath = 502 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 503 name = "HTTPS certificate"; 504 service = certs::httpsServiceName; 505 } 506 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 507 "AccountService", "LDAP", 508 "Certificates", std::ref(id))) 509 { 510 objectPath = 511 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 512 name = "LDAP certificate"; 513 service = certs::ldapServiceName; 514 } 515 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 516 "Managers", "bmc", "Truststore", 517 "Certificates", std::ref(id))) 518 { 519 objectPath = 520 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 521 name = "TrustStore certificate"; 522 service = certs::authorityServiceName; 523 } 524 else 525 { 526 messages::actionParameterNotSupported(asyncResp->res, "CertificateUri", 527 "ReplaceCertificate"); 528 return; 529 } 530 531 std::shared_ptr<CertificateFile> certFile = 532 std::make_shared<CertificateFile>(certificate); 533 crow::connections::systemBus->async_method_call( 534 [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, 535 name](const boost::system::error_code ec) { 536 if (ec) 537 { 538 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 539 if (ec.value() == 540 boost::system::linux_error::bad_request_descriptor) 541 { 542 messages::resourceNotFound(asyncResp->res, "Certificate", id); 543 return; 544 } 545 messages::internalError(asyncResp->res); 546 return; 547 } 548 getCertificateProperties(asyncResp, objectPath, service, id, url, name); 549 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 550 << certFile->getCertFilePath(); 551 }, 552 service, objectPath, certs::certReplaceIntf, "Replace", 553 certFile->getCertFilePath()); 554 } 555 556 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher; 557 /** 558 * @brief Read data from CSR D-bus object and set to response 559 * 560 * @param[in] asyncResp Shared pointer to the response message 561 * @param[in] certURI Link to certifiate collection URI 562 * @param[in] service D-Bus service name 563 * @param[in] certObjPath certificate D-Bus object path 564 * @param[in] csrObjPath CSR D-Bus object path 565 * @return None 566 */ 567 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 568 const std::string& certURI, const std::string& service, 569 const std::string& certObjPath, 570 const std::string& csrObjPath) 571 { 572 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath 573 << " CSRObjectPath=" << csrObjPath 574 << " service=" << service; 575 crow::connections::systemBus->async_method_call( 576 [asyncResp, certURI](const boost::system::error_code ec, 577 const std::string& csr) { 578 if (ec) 579 { 580 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 581 messages::internalError(asyncResp->res); 582 return; 583 } 584 if (csr.empty()) 585 { 586 BMCWEB_LOG_ERROR << "CSR read is empty"; 587 messages::internalError(asyncResp->res); 588 return; 589 } 590 asyncResp->res.jsonValue["CSRString"] = csr; 591 asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] = 592 certURI; 593 }, 594 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 595 } 596 597 inline void 598 handleGenerateCSRAction(App& app, const crow::Request& req, 599 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 600 { 601 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 602 { 603 return; 604 } 605 static const int rsaKeyBitLength = 2048; 606 607 // Required parameters 608 std::string city; 609 std::string commonName; 610 std::string country; 611 std::string organization; 612 std::string organizationalUnit; 613 std::string state; 614 nlohmann::json certificateCollection; 615 616 // Optional parameters 617 std::optional<std::vector<std::string>> optAlternativeNames = 618 std::vector<std::string>(); 619 std::optional<std::string> optContactPerson = ""; 620 std::optional<std::string> optChallengePassword = ""; 621 std::optional<std::string> optEmail = ""; 622 std::optional<std::string> optGivenName = ""; 623 std::optional<std::string> optInitials = ""; 624 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength; 625 std::optional<std::string> optKeyCurveId = "secp384r1"; 626 std::optional<std::string> optKeyPairAlgorithm = "EC"; 627 std::optional<std::vector<std::string>> optKeyUsage = 628 std::vector<std::string>(); 629 std::optional<std::string> optSurname = ""; 630 std::optional<std::string> optUnstructuredName = ""; 631 if (!json_util::readJsonAction( 632 req, asyncResp->res, "City", city, "CommonName", commonName, 633 "ContactPerson", optContactPerson, "Country", country, 634 "Organization", organization, "OrganizationalUnit", 635 organizationalUnit, "State", state, "CertificateCollection", 636 certificateCollection, "AlternativeNames", optAlternativeNames, 637 "ChallengePassword", optChallengePassword, "Email", optEmail, 638 "GivenName", optGivenName, "Initials", optInitials, "KeyBitLength", 639 optKeyBitLength, "KeyCurveId", optKeyCurveId, "KeyPairAlgorithm", 640 optKeyPairAlgorithm, "KeyUsage", optKeyUsage, "Surname", optSurname, 641 "UnstructuredName", optUnstructuredName)) 642 { 643 return; 644 } 645 646 // bmcweb has no way to store or decode a private key challenge 647 // password, which will likely cause bmcweb to crash on startup 648 // if this is not set on a post so not allowing the user to set 649 // value 650 if (!optChallengePassword->empty()) 651 { 652 messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR", 653 "ChallengePassword"); 654 return; 655 } 656 657 std::string certURI; 658 if (!redfish::json_util::readJson(certificateCollection, asyncResp->res, 659 "@odata.id", certURI)) 660 { 661 return; 662 } 663 664 std::string objectPath; 665 std::string service; 666 if (certURI.starts_with( 667 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 668 { 669 objectPath = certs::httpsObjectPath; 670 service = certs::httpsServiceName; 671 } 672 else if (certURI.starts_with( 673 "/redfish/v1/AccountService/LDAP/Certificates")) 674 { 675 objectPath = certs::ldapObjectPath; 676 service = certs::ldapServiceName; 677 } 678 else 679 { 680 messages::actionParameterNotSupported( 681 asyncResp->res, "CertificateCollection", "GenerateCSR"); 682 return; 683 } 684 685 // supporting only EC and RSA algorithm 686 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 687 { 688 messages::actionParameterNotSupported( 689 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 690 return; 691 } 692 693 // supporting only 2048 key bit length for RSA algorithm due to 694 // time consumed in generating private key 695 if (*optKeyPairAlgorithm == "RSA" && *optKeyBitLength != rsaKeyBitLength) 696 { 697 messages::propertyValueNotInList( 698 asyncResp->res, std::to_string(*optKeyBitLength), "KeyBitLength"); 699 return; 700 } 701 702 // validate KeyUsage supporting only 1 type based on URL 703 if (certURI.starts_with( 704 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 705 { 706 if (optKeyUsage->empty()) 707 { 708 optKeyUsage->push_back("ServerAuthentication"); 709 } 710 else if (optKeyUsage->size() == 1) 711 { 712 if ((*optKeyUsage)[0] != "ServerAuthentication") 713 { 714 messages::propertyValueNotInList(asyncResp->res, 715 (*optKeyUsage)[0], "KeyUsage"); 716 return; 717 } 718 } 719 else 720 { 721 messages::actionParameterNotSupported(asyncResp->res, "KeyUsage", 722 "GenerateCSR"); 723 return; 724 } 725 } 726 else if (certURI.starts_with( 727 "/redfish/v1/AccountService/LDAP/Certificates")) 728 { 729 if (optKeyUsage->empty()) 730 { 731 optKeyUsage->push_back("ClientAuthentication"); 732 } 733 else if (optKeyUsage->size() == 1) 734 { 735 if ((*optKeyUsage)[0] != "ClientAuthentication") 736 { 737 messages::propertyValueNotInList(asyncResp->res, 738 (*optKeyUsage)[0], "KeyUsage"); 739 return; 740 } 741 } 742 else 743 { 744 messages::actionParameterNotSupported(asyncResp->res, "KeyUsage", 745 "GenerateCSR"); 746 return; 747 } 748 } 749 750 // Only allow one CSR matcher at a time so setting retry 751 // time-out and timer expiry to 10 seconds for now. 752 static const int timeOut = 10; 753 if (csrMatcher) 754 { 755 messages::serviceTemporarilyUnavailable(asyncResp->res, 756 std::to_string(timeOut)); 757 return; 758 } 759 760 // Make this static so it survives outside this method 761 static boost::asio::steady_timer timeout(*req.ioService); 762 timeout.expires_after(std::chrono::seconds(timeOut)); 763 timeout.async_wait([asyncResp](const boost::system::error_code& ec) { 764 csrMatcher = nullptr; 765 if (ec) 766 { 767 // operation_aborted is expected if timer is canceled 768 // before completion. 769 if (ec != boost::asio::error::operation_aborted) 770 { 771 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 772 } 773 return; 774 } 775 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 776 messages::internalError(asyncResp->res); 777 }); 778 779 // create a matcher to wait on CSR object 780 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 781 std::string match("type='signal'," 782 "interface='org.freedesktop.DBus.ObjectManager'," 783 "path='" + 784 objectPath + 785 "'," 786 "member='InterfacesAdded'"); 787 csrMatcher = std::make_unique<sdbusplus::bus::match_t>( 788 *crow::connections::systemBus, match, 789 [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) { 790 timeout.cancel(); 791 if (m.is_method_error()) 792 { 793 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 794 messages::internalError(asyncResp->res); 795 return; 796 } 797 798 dbus::utility::DBusInteracesMap interfacesProperties; 799 800 sdbusplus::message::object_path csrObjectPath; 801 m.read(csrObjectPath, interfacesProperties); 802 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 803 for (const auto& interface : interfacesProperties) 804 { 805 if (interface.first == "xyz.openbmc_project.Certs.CSR") 806 { 807 getCSR(asyncResp, certURI, service, objectPath, 808 csrObjectPath.str); 809 break; 810 } 811 } 812 }); 813 crow::connections::systemBus->async_method_call( 814 [asyncResp](const boost::system::error_code ec, const std::string&) { 815 if (ec) 816 { 817 BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message(); 818 messages::internalError(asyncResp->res); 819 return; 820 } 821 }, 822 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 823 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 824 commonName, *optContactPerson, country, *optEmail, *optGivenName, 825 *optInitials, *optKeyBitLength, *optKeyCurveId, *optKeyPairAlgorithm, 826 *optKeyUsage, organization, organizationalUnit, state, *optSurname, 827 *optUnstructuredName); 828 } 829 830 inline void requestRoutesCertificateService(App& app) 831 { 832 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/") 833 .privileges(redfish::privileges::getCertificateService) 834 .methods(boost::beast::http::verb::get)( 835 std::bind_front(handleCertificateServiceGet, std::ref(app))); 836 837 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 838 .privileges(redfish::privileges::getCertificateLocations) 839 .methods(boost::beast::http::verb::get)( 840 std::bind_front(handleCertificateLocationsGet, std::ref(app))); 841 842 BMCWEB_ROUTE( 843 app, 844 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 845 .privileges(redfish::privileges::postCertificateService) 846 .methods(boost::beast::http::verb::post)( 847 std::bind_front(handleReplaceCertificateAction, std::ref(app))); 848 849 BMCWEB_ROUTE( 850 app, 851 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 852 .privileges(redfish::privileges::postCertificateService) 853 .methods(boost::beast::http::verb::post)( 854 std::bind_front(handleGenerateCSRAction, std::ref(app))); 855 } // requestRoutesCertificateService 856 857 inline void handleHTTPSCertificateCollectionGet( 858 App& app, const crow::Request& req, 859 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 860 { 861 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 862 { 863 return; 864 } 865 866 asyncResp->res.jsonValue["@odata.id"] = 867 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 868 asyncResp->res.jsonValue["@odata.type"] = 869 "#CertificateCollection.CertificateCollection"; 870 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection"; 871 asyncResp->res.jsonValue["Description"] = 872 "A Collection of HTTPS certificate instances"; 873 874 getCertificateList(asyncResp, certs::httpsObjectPath, 875 "/Members"_json_pointer, 876 "/Members@odata.count"_json_pointer); 877 } 878 879 inline void handleHTTPSCertificateCollectionPost( 880 App& app, const crow::Request& req, 881 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 882 { 883 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 884 { 885 return; 886 } 887 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 888 889 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate"; 890 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate"; 891 892 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 893 894 if (certFileBody.empty()) 895 { 896 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 897 messages::unrecognizedRequestBody(asyncResp->res); 898 return; 899 } 900 901 std::shared_ptr<CertificateFile> certFile = 902 std::make_shared<CertificateFile>(certFileBody); 903 904 crow::connections::systemBus->async_method_call( 905 [asyncResp, certFile](const boost::system::error_code ec, 906 const std::string& objectPath) { 907 if (ec) 908 { 909 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 910 messages::internalError(asyncResp->res); 911 return; 912 } 913 914 sdbusplus::message::object_path path(objectPath); 915 std::string certId = path.filename(); 916 const boost::urls::url certURL = crow::utility::urlFromPieces( 917 "redfish", "v1", "Managers", "bmc", "NetworkProtocol", "HTTPS", 918 "Certificates", certId); 919 getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName, 920 certId, certURL, "HTTPS Certificate"); 921 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 922 << certFile->getCertFilePath(); 923 }, 924 certs::httpsServiceName, certs::httpsObjectPath, certs::certInstallIntf, 925 "Install", certFile->getCertFilePath()); 926 } 927 928 inline void handleHTTPSCertificateGet( 929 App& app, const crow::Request& req, 930 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 931 { 932 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 933 { 934 return; 935 } 936 937 BMCWEB_LOG_DEBUG << "HTTPS Certificate ID=" << id; 938 const boost::urls::url certURL = crow::utility::urlFromPieces( 939 "redfish", "v1", "Managers", "bmc", "NetworkProtocol", "HTTPS", 940 "Certificates", id); 941 std::string objPath = 942 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 943 getCertificateProperties(asyncResp, objPath, certs::httpsServiceName, id, 944 certURL, "HTTPS Certificate"); 945 } 946 947 inline void requestRoutesHTTPSCertificate(App& app) 948 { 949 BMCWEB_ROUTE(app, 950 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 951 .privileges(redfish::privileges::getCertificateCollection) 952 .methods(boost::beast::http::verb::get)(std::bind_front( 953 handleHTTPSCertificateCollectionGet, std::ref(app))); 954 955 BMCWEB_ROUTE(app, 956 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 957 .privileges(redfish::privileges::postCertificateCollection) 958 .methods(boost::beast::http::verb::post)(std::bind_front( 959 handleHTTPSCertificateCollectionPost, std::ref(app))); 960 961 BMCWEB_ROUTE( 962 app, 963 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/") 964 .privileges(redfish::privileges::getCertificate) 965 .methods(boost::beast::http::verb::get)( 966 std::bind_front(handleHTTPSCertificateGet, std::ref(app))); 967 } 968 969 inline void handleLDAPCertificateCollectionGet( 970 App& app, const crow::Request& req, 971 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 972 { 973 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 974 { 975 return; 976 } 977 978 asyncResp->res.jsonValue["@odata.id"] = 979 "/redfish/v1/AccountService/LDAP/Certificates"; 980 asyncResp->res.jsonValue["@odata.type"] = 981 "#CertificateCollection.CertificateCollection"; 982 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection"; 983 asyncResp->res.jsonValue["Description"] = 984 "A Collection of LDAP certificate instances"; 985 986 getCertificateList(asyncResp, certs::ldapObjectPath, 987 "/Members"_json_pointer, 988 "/Members@odata.count"_json_pointer); 989 } 990 991 inline void handleLDAPCertificateCollectionPost( 992 App& app, const crow::Request& req, 993 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 994 { 995 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 996 { 997 return; 998 } 999 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1000 1001 if (certFileBody.empty()) 1002 { 1003 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1004 messages::unrecognizedRequestBody(asyncResp->res); 1005 return; 1006 } 1007 1008 std::shared_ptr<CertificateFile> certFile = 1009 std::make_shared<CertificateFile>(certFileBody); 1010 1011 crow::connections::systemBus->async_method_call( 1012 [asyncResp, certFile](const boost::system::error_code ec, 1013 const std::string& objectPath) { 1014 if (ec) 1015 { 1016 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1017 messages::internalError(asyncResp->res); 1018 return; 1019 } 1020 1021 sdbusplus::message::object_path path(objectPath); 1022 std::string certId = path.filename(); 1023 const boost::urls::url certURL = crow::utility::urlFromPieces( 1024 "redfish", "v1", "AccountService", "LDAP", "Certificates", certId); 1025 getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName, 1026 certId, certURL, "LDAP Certificate"); 1027 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1028 << certFile->getCertFilePath(); 1029 }, 1030 certs::ldapServiceName, certs::ldapObjectPath, certs::certInstallIntf, 1031 "Install", certFile->getCertFilePath()); 1032 } 1033 1034 inline void handleLDAPCertificateGet( 1035 App& app, const crow::Request& req, 1036 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1037 { 1038 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1039 { 1040 return; 1041 } 1042 1043 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id; 1044 const boost::urls::url certURL = crow::utility::urlFromPieces( 1045 "redfish", "v1", "AccountService", "LDAP", "Certificates", id); 1046 std::string objPath = 1047 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1048 getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id, 1049 certURL, "LDAP Certificate"); 1050 } 1051 1052 inline void requestRoutesLDAPCertificate(App& app) 1053 { 1054 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1055 .privileges(redfish::privileges::getCertificateCollection) 1056 .methods(boost::beast::http::verb::get)( 1057 std::bind_front(handleLDAPCertificateCollectionGet, std::ref(app))); 1058 1059 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1060 .privileges(redfish::privileges::postCertificateCollection) 1061 .methods(boost::beast::http::verb::post)(std::bind_front( 1062 handleLDAPCertificateCollectionPost, std::ref(app))); 1063 1064 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1065 .privileges(redfish::privileges::getCertificate) 1066 .methods(boost::beast::http::verb::get)( 1067 std::bind_front(handleLDAPCertificateGet, std::ref(app))); 1068 } // requestRoutesLDAPCertificate 1069 1070 inline void handleTrustStoreCertificateCollectionGet( 1071 App& app, const crow::Request& req, 1072 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1073 { 1074 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1075 { 1076 return; 1077 } 1078 1079 asyncResp->res.jsonValue["@odata.id"] = 1080 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1081 asyncResp->res.jsonValue["@odata.type"] = 1082 "#CertificateCollection.CertificateCollection"; 1083 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1084 asyncResp->res.jsonValue["Description"] = 1085 "A Collection of TrustStore certificate instances"; 1086 1087 getCertificateList(asyncResp, certs::authorityObjectPath, 1088 "/Members"_json_pointer, 1089 "/Members@odata.count"_json_pointer); 1090 } 1091 1092 inline void handleTrustStoreCertificateCollectionPost( 1093 App& app, const crow::Request& req, 1094 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 1095 { 1096 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1097 { 1098 return; 1099 } 1100 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1101 1102 if (certFileBody.empty()) 1103 { 1104 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1105 messages::unrecognizedRequestBody(asyncResp->res); 1106 return; 1107 } 1108 1109 std::shared_ptr<CertificateFile> certFile = 1110 std::make_shared<CertificateFile>(certFileBody); 1111 crow::connections::systemBus->async_method_call( 1112 [asyncResp, certFile](const boost::system::error_code ec, 1113 const std::string& objectPath) { 1114 if (ec) 1115 { 1116 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1117 messages::internalError(asyncResp->res); 1118 return; 1119 } 1120 1121 sdbusplus::message::object_path path(objectPath); 1122 std::string certId = path.filename(); 1123 const boost::urls::url certURL = 1124 crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc", 1125 "Truststore", "Certificates", certId); 1126 getCertificateProperties(asyncResp, objectPath, 1127 certs::authorityServiceName, certId, certURL, 1128 "TrustStore Certificate"); 1129 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1130 << certFile->getCertFilePath(); 1131 }, 1132 certs::authorityServiceName, certs::authorityObjectPath, 1133 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1134 } 1135 1136 inline void handleTrustStoreCertificateGet( 1137 App& app, const crow::Request& req, 1138 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1139 { 1140 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1141 { 1142 return; 1143 } 1144 1145 BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id; 1146 const boost::urls::url certURL = crow::utility::urlFromPieces( 1147 "redfish", "v1", "Managers", "bmc", "Truststore", "Certificates", id); 1148 std::string objPath = 1149 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1150 getCertificateProperties(asyncResp, objPath, certs::authorityServiceName, 1151 id, certURL, "TrustStore Certificate"); 1152 } 1153 1154 inline void handleTrustStoreCertificateDelete( 1155 App& app, const crow::Request& req, 1156 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id) 1157 { 1158 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1159 { 1160 return; 1161 } 1162 1163 BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id; 1164 std::string objPath = 1165 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1166 1167 crow::connections::systemBus->async_method_call( 1168 [asyncResp, id](const boost::system::error_code ec) { 1169 if (ec) 1170 { 1171 messages::resourceNotFound(asyncResp->res, "Certificate", id); 1172 return; 1173 } 1174 BMCWEB_LOG_INFO << "Certificate deleted"; 1175 asyncResp->res.result(boost::beast::http::status::no_content); 1176 }, 1177 certs::authorityServiceName, objPath, certs::objDeleteIntf, "Delete"); 1178 } 1179 1180 inline void requestRoutesTrustStoreCertificate(App& app) 1181 { 1182 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1183 .privileges(redfish::privileges::getCertificate) 1184 .methods(boost::beast::http::verb::get)(std::bind_front( 1185 handleTrustStoreCertificateCollectionGet, std::ref(app))); 1186 1187 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1188 .privileges(redfish::privileges::postCertificateCollection) 1189 .methods(boost::beast::http::verb::post)(std::bind_front( 1190 handleTrustStoreCertificateCollectionPost, std::ref(app))); 1191 1192 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1193 .privileges(redfish::privileges::getCertificate) 1194 .methods(boost::beast::http::verb::get)( 1195 std::bind_front(handleTrustStoreCertificateGet, std::ref(app))); 1196 1197 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1198 .privileges(redfish::privileges::deleteCertificate) 1199 .methods(boost::beast::http::verb::delete_)( 1200 std::bind_front(handleTrustStoreCertificateDelete, std::ref(app))); 1201 } // requestRoutesTrustStoreCertificate 1202 } // namespace redfish 1203