1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/convert.hpp> 5 #include <boost/convert/strtol.hpp> 6 #include <dbus_utility.hpp> 7 #include <registries/privilege_registry.hpp> 8 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::readJsonPatch(req, asyncResp->res, "CertificateString", 123 certificate, "CertificateType", 124 certificateType)) 125 { 126 BMCWEB_LOG_ERROR << "Required parameters are missing"; 127 messages::internalError(asyncResp->res); 128 return {}; 129 } 130 131 if (*certificateType != "PEM") 132 { 133 messages::propertyValueNotInList(asyncResp->res, *certificateType, 134 "CertificateType"); 135 return {}; 136 } 137 138 return certificate; 139 } 140 141 /** 142 * Class to create a temporary certificate file for uploading to system 143 */ 144 class CertificateFile 145 { 146 public: 147 CertificateFile() = delete; 148 CertificateFile(const CertificateFile&) = delete; 149 CertificateFile& operator=(const CertificateFile&) = delete; 150 CertificateFile(CertificateFile&&) = delete; 151 CertificateFile& operator=(CertificateFile&&) = delete; 152 CertificateFile(const std::string& certString) 153 { 154 std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C', 155 'e', 'r', 't', 's', '.', 'X', 156 'X', 'X', 'X', 'X', 'X', '\0'}; 157 char* tempDirectory = mkdtemp(dirTemplate.data()); 158 if (tempDirectory != nullptr) 159 { 160 certDirectory = tempDirectory; 161 certificateFile = certDirectory / "cert.pem"; 162 std::ofstream out(certificateFile, std::ofstream::out | 163 std::ofstream::binary | 164 std::ofstream::trunc); 165 out << certString; 166 out.close(); 167 BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile; 168 } 169 } 170 ~CertificateFile() 171 { 172 if (std::filesystem::exists(certDirectory)) 173 { 174 BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile; 175 std::error_code ec; 176 std::filesystem::remove_all(certDirectory, ec); 177 if (ec) 178 { 179 BMCWEB_LOG_ERROR << "Failed to remove temp directory" 180 << certDirectory; 181 } 182 } 183 } 184 std::string getCertFilePath() 185 { 186 return certificateFile; 187 } 188 189 private: 190 std::filesystem::path certificateFile; 191 std::filesystem::path certDirectory; 192 }; 193 194 static std::unique_ptr<sdbusplus::bus::match::match> csrMatcher; 195 /** 196 * @brief Read data from CSR D-bus object and set to response 197 * 198 * @param[in] asyncResp Shared pointer to the response message 199 * @param[in] certURI Link to certifiate collection URI 200 * @param[in] service D-Bus service name 201 * @param[in] certObjPath certificate D-Bus object path 202 * @param[in] csrObjPath CSR D-Bus object path 203 * @return None 204 */ 205 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 206 const std::string& certURI, const std::string& service, 207 const std::string& certObjPath, 208 const std::string& csrObjPath) 209 { 210 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath 211 << " CSRObjectPath=" << csrObjPath 212 << " service=" << service; 213 crow::connections::systemBus->async_method_call( 214 [asyncResp, certURI](const boost::system::error_code ec, 215 const std::string& csr) { 216 if (ec) 217 { 218 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 219 messages::internalError(asyncResp->res); 220 return; 221 } 222 if (csr.empty()) 223 { 224 BMCWEB_LOG_ERROR << "CSR read is empty"; 225 messages::internalError(asyncResp->res); 226 return; 227 } 228 asyncResp->res.jsonValue["CSRString"] = csr; 229 asyncResp->res.jsonValue["CertificateCollection"] = { 230 {"@odata.id", certURI}}; 231 }, 232 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 233 } 234 235 /** 236 * Action to Generate CSR 237 */ 238 inline void requestRoutesCertificateActionGenerateCSR(App& app) 239 { 240 BMCWEB_ROUTE( 241 app, 242 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 243 // Incorrect Privilege; Should be ConfigureManager 244 //.privileges(redfish::privileges::postCertificateService) 245 .privileges({{"ConfigureComponents"}}) 246 .methods( 247 boost::beast::http::verb:: 248 post)([](const crow::Request& req, 249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 250 static const int rsaKeyBitLength = 2048; 251 252 // Required parameters 253 std::string city; 254 std::string commonName; 255 std::string country; 256 std::string organization; 257 std::string organizationalUnit; 258 std::string state; 259 nlohmann::json certificateCollection; 260 261 // Optional parameters 262 std::optional<std::vector<std::string>> optAlternativeNames = 263 std::vector<std::string>(); 264 std::optional<std::string> optContactPerson = ""; 265 std::optional<std::string> optChallengePassword = ""; 266 std::optional<std::string> optEmail = ""; 267 std::optional<std::string> optGivenName = ""; 268 std::optional<std::string> optInitials = ""; 269 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength; 270 std::optional<std::string> optKeyCurveId = "secp384r1"; 271 std::optional<std::string> optKeyPairAlgorithm = "EC"; 272 std::optional<std::vector<std::string>> optKeyUsage = 273 std::vector<std::string>(); 274 std::optional<std::string> optSurname = ""; 275 std::optional<std::string> optUnstructuredName = ""; 276 if (!json_util::readJsonAction( 277 req, asyncResp->res, "City", city, "CommonName", commonName, 278 "ContactPerson", optContactPerson, "Country", country, 279 "Organization", organization, "OrganizationalUnit", 280 organizationalUnit, "State", state, "CertificateCollection", 281 certificateCollection, "AlternativeNames", 282 optAlternativeNames, "ChallengePassword", 283 optChallengePassword, "Email", optEmail, "GivenName", 284 optGivenName, "Initials", optInitials, "KeyBitLength", 285 optKeyBitLength, "KeyCurveId", optKeyCurveId, 286 "KeyPairAlgorithm", optKeyPairAlgorithm, "KeyUsage", 287 optKeyUsage, "Surname", optSurname, "UnstructuredName", 288 optUnstructuredName)) 289 { 290 return; 291 } 292 293 // bmcweb has no way to store or decode a private key challenge 294 // password, which will likely cause bmcweb to crash on startup 295 // if this is not set on a post so not allowing the user to set 296 // value 297 if (!optChallengePassword->empty()) 298 { 299 messages::actionParameterNotSupported( 300 asyncResp->res, "GenerateCSR", "ChallengePassword"); 301 return; 302 } 303 304 std::string certURI; 305 if (!redfish::json_util::readJson(certificateCollection, 306 asyncResp->res, "@odata.id", 307 certURI)) 308 { 309 return; 310 } 311 312 std::string objectPath; 313 std::string service; 314 if (boost::starts_with( 315 certURI, 316 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 317 { 318 objectPath = certs::httpsObjectPath; 319 service = certs::httpsServiceName; 320 } 321 else if (boost::starts_with( 322 certURI, 323 "/redfish/v1/AccountService/LDAP/Certificates")) 324 { 325 objectPath = certs::ldapObjectPath; 326 service = certs::ldapServiceName; 327 } 328 else 329 { 330 messages::actionParameterNotSupported( 331 asyncResp->res, "CertificateCollection", "GenerateCSR"); 332 return; 333 } 334 335 // supporting only EC and RSA algorithm 336 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 337 { 338 messages::actionParameterNotSupported( 339 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 340 return; 341 } 342 343 // supporting only 2048 key bit length for RSA algorithm due to 344 // time consumed in generating private key 345 if (*optKeyPairAlgorithm == "RSA" && 346 *optKeyBitLength != rsaKeyBitLength) 347 { 348 messages::propertyValueNotInList( 349 asyncResp->res, std::to_string(*optKeyBitLength), 350 "KeyBitLength"); 351 return; 352 } 353 354 // validate KeyUsage supporting only 1 type based on URL 355 if (boost::starts_with( 356 certURI, 357 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 358 { 359 if (optKeyUsage->empty()) 360 { 361 optKeyUsage->push_back("ServerAuthentication"); 362 } 363 else if (optKeyUsage->size() == 1) 364 { 365 if ((*optKeyUsage)[0] != "ServerAuthentication") 366 { 367 messages::propertyValueNotInList( 368 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 369 return; 370 } 371 } 372 else 373 { 374 messages::actionParameterNotSupported( 375 asyncResp->res, "KeyUsage", "GenerateCSR"); 376 return; 377 } 378 } 379 else if (boost::starts_with( 380 certURI, 381 "/redfish/v1/AccountService/LDAP/Certificates")) 382 { 383 if (optKeyUsage->empty()) 384 { 385 optKeyUsage->push_back("ClientAuthentication"); 386 } 387 else if (optKeyUsage->size() == 1) 388 { 389 if ((*optKeyUsage)[0] != "ClientAuthentication") 390 { 391 messages::propertyValueNotInList( 392 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 393 return; 394 } 395 } 396 else 397 { 398 messages::actionParameterNotSupported( 399 asyncResp->res, "KeyUsage", "GenerateCSR"); 400 return; 401 } 402 } 403 404 // Only allow one CSR matcher at a time so setting retry 405 // time-out and timer expiry to 10 seconds for now. 406 static const int timeOut = 10; 407 if (csrMatcher) 408 { 409 messages::serviceTemporarilyUnavailable( 410 asyncResp->res, std::to_string(timeOut)); 411 return; 412 } 413 414 // Make this static so it survives outside this method 415 static boost::asio::steady_timer timeout(*req.ioService); 416 timeout.expires_after(std::chrono::seconds(timeOut)); 417 timeout.async_wait( 418 [asyncResp](const boost::system::error_code& ec) { 419 csrMatcher = nullptr; 420 if (ec) 421 { 422 // operation_aborted is expected if timer is canceled 423 // before completion. 424 if (ec != boost::asio::error::operation_aborted) 425 { 426 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 427 } 428 return; 429 } 430 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 431 messages::internalError(asyncResp->res); 432 }); 433 434 // create a matcher to wait on CSR object 435 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 436 std::string match("type='signal'," 437 "interface='org.freedesktop.DBus.ObjectManager'," 438 "path='" + 439 objectPath + 440 "'," 441 "member='InterfacesAdded'"); 442 csrMatcher = std::make_unique<sdbusplus::bus::match::match>( 443 *crow::connections::systemBus, match, 444 [asyncResp, service, objectPath, 445 certURI](sdbusplus::message::message& m) { 446 timeout.cancel(); 447 if (m.is_method_error()) 448 { 449 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 450 messages::internalError(asyncResp->res); 451 return; 452 } 453 std::vector<std::pair< 454 std::string, 455 std::vector<std::pair<std::string, 456 dbus::utility::DbusVariantType>>>> 457 interfacesProperties; 458 sdbusplus::message::object_path csrObjectPath; 459 m.read(csrObjectPath, interfacesProperties); 460 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 461 for (auto& interface : interfacesProperties) 462 { 463 if (interface.first == "xyz.openbmc_project.Certs.CSR") 464 { 465 getCSR(asyncResp, certURI, service, objectPath, 466 csrObjectPath.str); 467 break; 468 } 469 } 470 }); 471 crow::connections::systemBus->async_method_call( 472 [asyncResp](const boost::system::error_code ec, 473 const std::string&) { 474 if (ec) 475 { 476 BMCWEB_LOG_ERROR << "DBUS response error: " 477 << ec.message(); 478 messages::internalError(asyncResp->res); 479 return; 480 } 481 }, 482 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 483 "GenerateCSR", *optAlternativeNames, *optChallengePassword, 484 city, commonName, *optContactPerson, country, *optEmail, 485 *optGivenName, *optInitials, *optKeyBitLength, *optKeyCurveId, 486 *optKeyPairAlgorithm, *optKeyUsage, organization, 487 organizationalUnit, state, *optSurname, *optUnstructuredName); 488 }); 489 } // requestRoutesCertificateActionGenerateCSR 490 491 /** 492 * @brief Parse and update Certificate Issue/Subject property 493 * 494 * @param[in] asyncResp Shared pointer to the response message 495 * @param[in] str Issuer/Subject value in key=value pairs 496 * @param[in] type Issuer/Subject 497 * @return None 498 */ 499 static void updateCertIssuerOrSubject(nlohmann::json& out, 500 const std::string_view value) 501 { 502 // example: O=openbmc-project.xyz,CN=localhost 503 std::string_view::iterator i = value.begin(); 504 while (i != value.end()) 505 { 506 std::string_view::iterator tokenBegin = i; 507 while (i != value.end() && *i != '=') 508 { 509 ++i; 510 } 511 if (i == value.end()) 512 { 513 break; 514 } 515 const std::string_view key(tokenBegin, 516 static_cast<size_t>(i - tokenBegin)); 517 ++i; 518 tokenBegin = i; 519 while (i != value.end() && *i != ',') 520 { 521 ++i; 522 } 523 const std::string_view val(tokenBegin, 524 static_cast<size_t>(i - tokenBegin)); 525 if (key == "L") 526 { 527 out["City"] = val; 528 } 529 else if (key == "CN") 530 { 531 out["CommonName"] = val; 532 } 533 else if (key == "C") 534 { 535 out["Country"] = val; 536 } 537 else if (key == "O") 538 { 539 out["Organization"] = val; 540 } 541 else if (key == "OU") 542 { 543 out["OrganizationalUnit"] = val; 544 } 545 else if (key == "ST") 546 { 547 out["State"] = val; 548 } 549 // skip comma character 550 if (i != value.end()) 551 { 552 ++i; 553 } 554 } 555 } 556 557 /** 558 * @brief Retrieve the certificates properties and append to the response 559 * message 560 * 561 * @param[in] asyncResp Shared pointer to the response message 562 * @param[in] objectPath Path of the D-Bus service object 563 * @param[in] certId Id of the certificate 564 * @param[in] certURL URL of the certificate object 565 * @param[in] name name of the certificate 566 * @return None 567 */ 568 static void getCertificateProperties( 569 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 570 const std::string& objectPath, const std::string& service, long certId, 571 const std::string& certURL, const std::string& name) 572 { 573 using PropertiesMap = 574 boost::container::flat_map<std::string, dbus::utility::DbusVariantType>; 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 != nullptr) 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 != nullptr) 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 != nullptr) 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 != nullptr) 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 != nullptr) 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 != nullptr) 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::readJsonAction(req, asyncResp->res, 688 "CertificateString", certificate, 689 "CertificateUri", certificateUri, 690 "CertificateType", 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 dbus::utility::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 dbus::utility::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 (const 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 dbus::utility::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 dbus::utility::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