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