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" 168 << certificateFile.string(); 169 } 170 } 171 ~CertificateFile() 172 { 173 if (std::filesystem::exists(certDirectory)) 174 { 175 BMCWEB_LOG_DEBUG << "Removing certificate file" 176 << certificateFile.string(); 177 std::error_code ec; 178 std::filesystem::remove_all(certDirectory, ec); 179 if (ec) 180 { 181 BMCWEB_LOG_ERROR << "Failed to remove temp directory" 182 << certDirectory.string(); 183 } 184 } 185 } 186 std::string getCertFilePath() 187 { 188 return certificateFile; 189 } 190 191 private: 192 std::filesystem::path certificateFile; 193 std::filesystem::path certDirectory; 194 }; 195 196 static std::unique_ptr<sdbusplus::bus::match::match> csrMatcher; 197 /** 198 * @brief Read data from CSR D-bus object and set to response 199 * 200 * @param[in] asyncResp Shared pointer to the response message 201 * @param[in] certURI Link to certifiate collection URI 202 * @param[in] service D-Bus service name 203 * @param[in] certObjPath certificate D-Bus object path 204 * @param[in] csrObjPath CSR D-Bus object path 205 * @return None 206 */ 207 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 208 const std::string& certURI, const std::string& service, 209 const std::string& certObjPath, 210 const std::string& csrObjPath) 211 { 212 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath 213 << " CSRObjectPath=" << csrObjPath 214 << " service=" << service; 215 crow::connections::systemBus->async_method_call( 216 [asyncResp, certURI](const boost::system::error_code ec, 217 const std::string& csr) { 218 if (ec) 219 { 220 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 221 messages::internalError(asyncResp->res); 222 return; 223 } 224 if (csr.empty()) 225 { 226 BMCWEB_LOG_ERROR << "CSR read is empty"; 227 messages::internalError(asyncResp->res); 228 return; 229 } 230 asyncResp->res.jsonValue["CSRString"] = csr; 231 asyncResp->res.jsonValue["CertificateCollection"] = { 232 {"@odata.id", certURI}}; 233 }, 234 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 235 } 236 237 /** 238 * Action to Generate CSR 239 */ 240 inline void requestRoutesCertificateActionGenerateCSR(App& app) 241 { 242 BMCWEB_ROUTE( 243 app, 244 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 245 // Incorrect Privilege; Should be ConfigureManager 246 //.privileges(redfish::privileges::postCertificateService) 247 .privileges({{"ConfigureComponents"}}) 248 .methods( 249 boost::beast::http::verb:: 250 post)([](const crow::Request& req, 251 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 252 static const int rsaKeyBitLength = 2048; 253 254 // Required parameters 255 std::string city; 256 std::string commonName; 257 std::string country; 258 std::string organization; 259 std::string organizationalUnit; 260 std::string state; 261 nlohmann::json certificateCollection; 262 263 // Optional parameters 264 std::optional<std::vector<std::string>> optAlternativeNames = 265 std::vector<std::string>(); 266 std::optional<std::string> optContactPerson = ""; 267 std::optional<std::string> optChallengePassword = ""; 268 std::optional<std::string> optEmail = ""; 269 std::optional<std::string> optGivenName = ""; 270 std::optional<std::string> optInitials = ""; 271 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength; 272 std::optional<std::string> optKeyCurveId = "secp384r1"; 273 std::optional<std::string> optKeyPairAlgorithm = "EC"; 274 std::optional<std::vector<std::string>> optKeyUsage = 275 std::vector<std::string>(); 276 std::optional<std::string> optSurname = ""; 277 std::optional<std::string> optUnstructuredName = ""; 278 if (!json_util::readJsonAction( 279 req, asyncResp->res, "City", city, "CommonName", commonName, 280 "ContactPerson", optContactPerson, "Country", country, 281 "Organization", organization, "OrganizationalUnit", 282 organizationalUnit, "State", state, "CertificateCollection", 283 certificateCollection, "AlternativeNames", 284 optAlternativeNames, "ChallengePassword", 285 optChallengePassword, "Email", optEmail, "GivenName", 286 optGivenName, "Initials", optInitials, "KeyBitLength", 287 optKeyBitLength, "KeyCurveId", optKeyCurveId, 288 "KeyPairAlgorithm", optKeyPairAlgorithm, "KeyUsage", 289 optKeyUsage, "Surname", optSurname, "UnstructuredName", 290 optUnstructuredName)) 291 { 292 return; 293 } 294 295 // bmcweb has no way to store or decode a private key challenge 296 // password, which will likely cause bmcweb to crash on startup 297 // if this is not set on a post so not allowing the user to set 298 // value 299 if (!optChallengePassword->empty()) 300 { 301 messages::actionParameterNotSupported( 302 asyncResp->res, "GenerateCSR", "ChallengePassword"); 303 return; 304 } 305 306 std::string certURI; 307 if (!redfish::json_util::readJson(certificateCollection, 308 asyncResp->res, "@odata.id", 309 certURI)) 310 { 311 return; 312 } 313 314 std::string objectPath; 315 std::string service; 316 if (boost::starts_with( 317 certURI, 318 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 319 { 320 objectPath = certs::httpsObjectPath; 321 service = certs::httpsServiceName; 322 } 323 else if (boost::starts_with( 324 certURI, 325 "/redfish/v1/AccountService/LDAP/Certificates")) 326 { 327 objectPath = certs::ldapObjectPath; 328 service = certs::ldapServiceName; 329 } 330 else 331 { 332 messages::actionParameterNotSupported( 333 asyncResp->res, "CertificateCollection", "GenerateCSR"); 334 return; 335 } 336 337 // supporting only EC and RSA algorithm 338 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 339 { 340 messages::actionParameterNotSupported( 341 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 342 return; 343 } 344 345 // supporting only 2048 key bit length for RSA algorithm due to 346 // time consumed in generating private key 347 if (*optKeyPairAlgorithm == "RSA" && 348 *optKeyBitLength != rsaKeyBitLength) 349 { 350 messages::propertyValueNotInList( 351 asyncResp->res, std::to_string(*optKeyBitLength), 352 "KeyBitLength"); 353 return; 354 } 355 356 // validate KeyUsage supporting only 1 type based on URL 357 if (boost::starts_with( 358 certURI, 359 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 360 { 361 if (optKeyUsage->empty()) 362 { 363 optKeyUsage->push_back("ServerAuthentication"); 364 } 365 else if (optKeyUsage->size() == 1) 366 { 367 if ((*optKeyUsage)[0] != "ServerAuthentication") 368 { 369 messages::propertyValueNotInList( 370 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 371 return; 372 } 373 } 374 else 375 { 376 messages::actionParameterNotSupported( 377 asyncResp->res, "KeyUsage", "GenerateCSR"); 378 return; 379 } 380 } 381 else if (boost::starts_with( 382 certURI, 383 "/redfish/v1/AccountService/LDAP/Certificates")) 384 { 385 if (optKeyUsage->empty()) 386 { 387 optKeyUsage->push_back("ClientAuthentication"); 388 } 389 else if (optKeyUsage->size() == 1) 390 { 391 if ((*optKeyUsage)[0] != "ClientAuthentication") 392 { 393 messages::propertyValueNotInList( 394 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 395 return; 396 } 397 } 398 else 399 { 400 messages::actionParameterNotSupported( 401 asyncResp->res, "KeyUsage", "GenerateCSR"); 402 return; 403 } 404 } 405 406 // Only allow one CSR matcher at a time so setting retry 407 // time-out and timer expiry to 10 seconds for now. 408 static const int timeOut = 10; 409 if (csrMatcher) 410 { 411 messages::serviceTemporarilyUnavailable( 412 asyncResp->res, std::to_string(timeOut)); 413 return; 414 } 415 416 // Make this static so it survives outside this method 417 static boost::asio::steady_timer timeout(*req.ioService); 418 timeout.expires_after(std::chrono::seconds(timeOut)); 419 timeout.async_wait( 420 [asyncResp](const boost::system::error_code& ec) { 421 csrMatcher = nullptr; 422 if (ec) 423 { 424 // operation_aborted is expected if timer is canceled 425 // before completion. 426 if (ec != boost::asio::error::operation_aborted) 427 { 428 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 429 } 430 return; 431 } 432 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 433 messages::internalError(asyncResp->res); 434 }); 435 436 // create a matcher to wait on CSR object 437 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 438 std::string match("type='signal'," 439 "interface='org.freedesktop.DBus.ObjectManager'," 440 "path='" + 441 objectPath + 442 "'," 443 "member='InterfacesAdded'"); 444 csrMatcher = std::make_unique<sdbusplus::bus::match::match>( 445 *crow::connections::systemBus, match, 446 [asyncResp, service, objectPath, 447 certURI](sdbusplus::message::message& m) { 448 timeout.cancel(); 449 if (m.is_method_error()) 450 { 451 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 452 messages::internalError(asyncResp->res); 453 return; 454 } 455 456 dbus::utility::DBusInteracesMap interfacesProperties; 457 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 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 574 << " certId=" << certId << " certURl=" << certURL; 575 crow::connections::systemBus->async_method_call( 576 [asyncResp, certURL, certId, 577 name](const boost::system::error_code ec, 578 const dbus::utility::DBusPropertiesMap& properties) { 579 if (ec) 580 { 581 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 582 messages::resourceNotFound(asyncResp->res, name, 583 std::to_string(certId)); 584 return; 585 } 586 asyncResp->res.jsonValue = { 587 {"@odata.id", certURL}, 588 {"@odata.type", "#Certificate.v1_0_0.Certificate"}, 589 {"Id", std::to_string(certId)}, 590 {"Name", name}, 591 {"Description", name}}; 592 for (const auto& property : properties) 593 { 594 if (property.first == "CertificateString") 595 { 596 asyncResp->res.jsonValue["CertificateString"] = ""; 597 const std::string* value = 598 std::get_if<std::string>(&property.second); 599 if (value != nullptr) 600 { 601 asyncResp->res.jsonValue["CertificateString"] = *value; 602 } 603 } 604 else if (property.first == "KeyUsage") 605 { 606 nlohmann::json& keyUsage = 607 asyncResp->res.jsonValue["KeyUsage"]; 608 keyUsage = nlohmann::json::array(); 609 const std::vector<std::string>* value = 610 std::get_if<std::vector<std::string>>(&property.second); 611 if (value != nullptr) 612 { 613 for (const std::string& usage : *value) 614 { 615 keyUsage.push_back(usage); 616 } 617 } 618 } 619 else if (property.first == "Issuer") 620 { 621 const std::string* value = 622 std::get_if<std::string>(&property.second); 623 if (value != nullptr) 624 { 625 updateCertIssuerOrSubject( 626 asyncResp->res.jsonValue["Issuer"], *value); 627 } 628 } 629 else if (property.first == "Subject") 630 { 631 const std::string* value = 632 std::get_if<std::string>(&property.second); 633 if (value != nullptr) 634 { 635 updateCertIssuerOrSubject( 636 asyncResp->res.jsonValue["Subject"], *value); 637 } 638 } 639 else if (property.first == "ValidNotAfter") 640 { 641 const uint64_t* value = 642 std::get_if<uint64_t>(&property.second); 643 if (value != nullptr) 644 { 645 asyncResp->res.jsonValue["ValidNotAfter"] = 646 crow::utility::getDateTimeUint(*value); 647 } 648 } 649 else if (property.first == "ValidNotBefore") 650 { 651 const uint64_t* value = 652 std::get_if<uint64_t>(&property.second); 653 if (value != nullptr) 654 { 655 asyncResp->res.jsonValue["ValidNotBefore"] = 656 crow::utility::getDateTimeUint(*value); 657 } 658 } 659 } 660 asyncResp->res.addHeader("Location", certURL); 661 }, 662 service, objectPath, certs::dbusPropIntf, "GetAll", 663 certs::certPropIntf); 664 } 665 666 /** 667 * Action to replace an existing certificate 668 */ 669 inline void requestRoutesCertificateActionsReplaceCertificate(App& app) 670 { 671 BMCWEB_ROUTE( 672 app, 673 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 674 .privileges(redfish::privileges::postCertificateService) 675 .methods( 676 boost::beast::http::verb:: 677 post)([](const crow::Request& req, 678 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 679 std::string certificate; 680 nlohmann::json certificateUri; 681 std::optional<std::string> certificateType = "PEM"; 682 683 if (!json_util::readJsonAction(req, asyncResp->res, 684 "CertificateString", certificate, 685 "CertificateUri", certificateUri, 686 "CertificateType", certificateType)) 687 { 688 BMCWEB_LOG_ERROR << "Required parameters are missing"; 689 messages::internalError(asyncResp->res); 690 return; 691 } 692 693 if (!certificateType) 694 { 695 // should never happen, but it never hurts to be paranoid. 696 return; 697 } 698 if (certificateType != "PEM") 699 { 700 messages::actionParameterNotSupported( 701 asyncResp->res, "CertificateType", "ReplaceCertificate"); 702 return; 703 } 704 705 std::string certURI; 706 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 707 "@odata.id", certURI)) 708 { 709 messages::actionParameterMissing( 710 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 711 return; 712 } 713 714 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 715 long id = getIDFromURL(certURI); 716 if (id < 0) 717 { 718 messages::actionParameterValueFormatError( 719 asyncResp->res, certURI, "CertificateUri", 720 "ReplaceCertificate"); 721 return; 722 } 723 std::string objectPath; 724 std::string name; 725 std::string service; 726 if (boost::starts_with( 727 certURI, 728 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 729 { 730 objectPath = std::string(certs::httpsObjectPath) + "/" + 731 std::to_string(id); 732 name = "HTTPS certificate"; 733 service = certs::httpsServiceName; 734 } 735 else if (boost::starts_with( 736 certURI, 737 "/redfish/v1/AccountService/LDAP/Certificates/")) 738 { 739 objectPath = std::string(certs::ldapObjectPath) + "/" + 740 std::to_string(id); 741 name = "LDAP certificate"; 742 service = certs::ldapServiceName; 743 } 744 else if (boost::starts_with( 745 certURI, 746 "/redfish/v1/Managers/bmc/Truststore/Certificates/")) 747 { 748 objectPath = std::string(certs::authorityObjectPath) + "/" + 749 std::to_string(id); 750 name = "TrustStore certificate"; 751 service = certs::authorityServiceName; 752 } 753 else 754 { 755 messages::actionParameterNotSupported( 756 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 757 return; 758 } 759 760 std::shared_ptr<CertificateFile> certFile = 761 std::make_shared<CertificateFile>(certificate); 762 crow::connections::systemBus->async_method_call( 763 [asyncResp, certFile, objectPath, service, certURI, id, 764 name](const boost::system::error_code ec) { 765 if (ec) 766 { 767 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 768 messages::resourceNotFound(asyncResp->res, name, 769 std::to_string(id)); 770 return; 771 } 772 getCertificateProperties(asyncResp, objectPath, service, id, 773 certURI, name); 774 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 775 << certFile->getCertFilePath(); 776 }, 777 service, objectPath, certs::certReplaceIntf, "Replace", 778 certFile->getCertFilePath()); 779 }); 780 } // requestRoutesCertificateActionsReplaceCertificate 781 782 /** 783 * Certificate resource describes a certificate used to prove the identity 784 * of a component, account or service. 785 */ 786 787 inline void requestRoutesHTTPSCertificate(App& app) 788 { 789 BMCWEB_ROUTE( 790 app, 791 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/") 792 .privileges(redfish::privileges::getCertificate) 793 .methods( 794 boost::beast::http::verb:: 795 get)([](const crow::Request& req, 796 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 797 const std::string& param) -> void { 798 if (param.empty()) 799 { 800 messages::internalError(asyncResp->res); 801 return; 802 } 803 long id = getIDFromURL(req.url); 804 805 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" 806 << std::to_string(id); 807 std::string certURL = 808 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 809 std::to_string(id); 810 std::string objectPath = certs::httpsObjectPath; 811 objectPath += "/"; 812 objectPath += std::to_string(id); 813 getCertificateProperties(asyncResp, objectPath, 814 certs::httpsServiceName, id, certURL, 815 "HTTPS Certificate"); 816 }); 817 } 818 819 /** 820 * Collection of HTTPS certificates 821 */ 822 inline void requestRoutesHTTPSCertificateCollection(App& app) 823 { 824 BMCWEB_ROUTE(app, 825 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 826 .privileges(redfish::privileges::getCertificateCollection) 827 .methods( 828 boost::beast::http::verb:: 829 get)([](const crow::Request&, 830 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 831 asyncResp->res.jsonValue = { 832 {"@odata.id", 833 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, 834 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 835 {"Name", "HTTPS Certificates Collection"}, 836 {"Description", "A Collection of HTTPS certificate instances"}}; 837 838 crow::connections::systemBus->async_method_call( 839 [asyncResp](const boost::system::error_code ec, 840 const dbus::utility::ManagedObjectType& certs) { 841 if (ec) 842 { 843 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 844 messages::internalError(asyncResp->res); 845 return; 846 } 847 nlohmann::json& members = 848 asyncResp->res.jsonValue["Members"]; 849 members = nlohmann::json::array(); 850 for (const auto& cert : certs) 851 { 852 long id = getIDFromURL(cert.first.str); 853 if (id >= 0) 854 { 855 members.push_back( 856 {{"@odata.id", 857 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 858 std::to_string(id)}}); 859 } 860 } 861 asyncResp->res.jsonValue["Members@odata.count"] = 862 members.size(); 863 }, 864 certs::httpsServiceName, certs::httpsObjectPath, 865 certs::dbusObjManagerIntf, "GetManagedObjects"); 866 }); 867 868 BMCWEB_ROUTE(app, 869 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 870 .privileges(redfish::privileges::postCertificateCollection) 871 .methods( 872 boost::beast::http::verb:: 873 post)([](const crow::Request& req, 874 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 875 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 876 877 asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, 878 {"Description", "HTTPS Certificate"}}; 879 880 std::string certFileBody = 881 getCertificateFromReqBody(asyncResp, req); 882 883 if (certFileBody.empty()) 884 { 885 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 886 messages::unrecognizedRequestBody(asyncResp->res); 887 return; 888 } 889 890 std::shared_ptr<CertificateFile> certFile = 891 std::make_shared<CertificateFile>(certFileBody); 892 893 crow::connections::systemBus->async_method_call( 894 [asyncResp, certFile](const boost::system::error_code ec, 895 const std::string& objectPath) { 896 if (ec) 897 { 898 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 899 messages::internalError(asyncResp->res); 900 return; 901 } 902 long certId = getIDFromURL(objectPath); 903 if (certId < 0) 904 { 905 BMCWEB_LOG_ERROR << "Invalid objectPath value" 906 << objectPath; 907 messages::internalError(asyncResp->res); 908 return; 909 } 910 std::string certURL = 911 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 912 std::to_string(certId); 913 getCertificateProperties(asyncResp, objectPath, 914 certs::httpsServiceName, certId, 915 certURL, "HTTPS Certificate"); 916 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 917 << certFile->getCertFilePath(); 918 }, 919 certs::httpsServiceName, certs::httpsObjectPath, 920 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 921 }); 922 } // requestRoutesHTTPSCertificateCollection 923 924 /** 925 * @brief Retrieve the certificates installed list and append to the 926 * response 927 * 928 * @param[in] asyncResp Shared pointer to the response message 929 * @param[in] certURL Path of the certificate object 930 * @param[in] path Path of the D-Bus service object 931 * @return None 932 */ 933 inline void 934 getCertificateLocations(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 935 const std::string& certURL, const std::string& path, 936 const std::string& service) 937 { 938 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL 939 << " Path=" << path << " service= " << service; 940 crow::connections::systemBus->async_method_call( 941 [asyncResp, certURL](const boost::system::error_code ec, 942 const dbus::utility::ManagedObjectType& certs) { 943 if (ec) 944 { 945 BMCWEB_LOG_WARNING 946 << "Certificate collection query failed: " << ec 947 << ", skipping " << certURL; 948 return; 949 } 950 nlohmann::json& links = 951 asyncResp->res.jsonValue["Links"]["Certificates"]; 952 for (const auto& cert : certs) 953 { 954 long id = getIDFromURL(cert.first.str); 955 if (id >= 0) 956 { 957 links.push_back( 958 {{"@odata.id", certURL + std::to_string(id)}}); 959 } 960 } 961 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] = 962 links.size(); 963 }, 964 service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); 965 } 966 967 /** 968 * The certificate location schema defines a resource that an administrator 969 * can use in order to locate all certificates installed on a given service. 970 */ 971 inline void requestRoutesCertificateLocations(App& app) 972 { 973 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 974 .privileges(redfish::privileges::getCertificateLocations) 975 .methods( 976 boost::beast::http::verb:: 977 get)([](const crow::Request&, 978 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 979 asyncResp->res.jsonValue = { 980 {"@odata.id", 981 "/redfish/v1/CertificateService/CertificateLocations"}, 982 {"@odata.type", 983 "#CertificateLocations.v1_0_0.CertificateLocations"}, 984 {"Name", "Certificate Locations"}, 985 {"Id", "CertificateLocations"}, 986 {"Description", 987 "Defines a resource that an administrator can use in order to " 988 "locate all certificates installed on a given service"}}; 989 990 nlohmann::json& links = 991 asyncResp->res.jsonValue["Links"]["Certificates"]; 992 links = nlohmann::json::array(); 993 getCertificateLocations( 994 asyncResp, 995 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", 996 certs::httpsObjectPath, certs::httpsServiceName); 997 getCertificateLocations( 998 asyncResp, "/redfish/v1/AccountService/LDAP/Certificates/", 999 certs::ldapObjectPath, certs::ldapServiceName); 1000 getCertificateLocations( 1001 asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/", 1002 certs::authorityObjectPath, certs::authorityServiceName); 1003 }); 1004 } 1005 // requestRoutesCertificateLocations 1006 1007 /** 1008 * Collection of LDAP certificates 1009 */ 1010 inline void requestRoutesLDAPCertificateCollection(App& app) 1011 { 1012 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1013 .privileges(redfish::privileges::getCertificateCollection) 1014 .methods( 1015 boost::beast::http::verb:: 1016 get)([](const crow::Request&, 1017 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1018 asyncResp->res.jsonValue = { 1019 {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"}, 1020 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1021 {"Name", "LDAP Certificates Collection"}, 1022 {"Description", "A Collection of LDAP certificate instances"}}; 1023 1024 crow::connections::systemBus->async_method_call( 1025 [asyncResp](const boost::system::error_code ec, 1026 const dbus::utility::ManagedObjectType& certs) { 1027 nlohmann::json& members = 1028 asyncResp->res.jsonValue["Members"]; 1029 nlohmann::json& count = 1030 asyncResp->res.jsonValue["Members@odata.count"]; 1031 members = nlohmann::json::array(); 1032 count = 0; 1033 if (ec) 1034 { 1035 BMCWEB_LOG_WARNING << "LDAP certificate query failed: " 1036 << ec; 1037 return; 1038 } 1039 for (const auto& cert : certs) 1040 { 1041 long id = getIDFromURL(cert.first.str); 1042 if (id >= 0) 1043 { 1044 members.push_back( 1045 {{"@odata.id", 1046 "/redfish/v1/AccountService/LDAP/Certificates/" + 1047 std::to_string(id)}}); 1048 } 1049 } 1050 count = members.size(); 1051 }, 1052 certs::ldapServiceName, certs::ldapObjectPath, 1053 certs::dbusObjManagerIntf, "GetManagedObjects"); 1054 }); 1055 1056 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1057 .privileges(redfish::privileges::postCertificateCollection) 1058 .methods(boost::beast::http::verb::post)( 1059 [](const crow::Request& req, 1060 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1061 std::string certFileBody = 1062 getCertificateFromReqBody(asyncResp, req); 1063 1064 if (certFileBody.empty()) 1065 { 1066 BMCWEB_LOG_ERROR 1067 << "Cannot get certificate from request body."; 1068 messages::unrecognizedRequestBody(asyncResp->res); 1069 return; 1070 } 1071 1072 std::shared_ptr<CertificateFile> certFile = 1073 std::make_shared<CertificateFile>(certFileBody); 1074 1075 crow::connections::systemBus->async_method_call( 1076 [asyncResp, certFile](const boost::system::error_code ec, 1077 const std::string& objectPath) { 1078 if (ec) 1079 { 1080 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1081 messages::internalError(asyncResp->res); 1082 return; 1083 } 1084 long certId = getIDFromURL(objectPath); 1085 if (certId < 0) 1086 { 1087 BMCWEB_LOG_ERROR << "Invalid objectPath value" 1088 << objectPath; 1089 messages::internalError(asyncResp->res); 1090 return; 1091 } 1092 std::string certURL = 1093 "/redfish/v1/AccountService/LDAP/Certificates/" + 1094 std::to_string(certId); 1095 getCertificateProperties(asyncResp, objectPath, 1096 certs::ldapServiceName, certId, 1097 certURL, "LDAP Certificate"); 1098 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1099 << certFile->getCertFilePath(); 1100 }, 1101 certs::ldapServiceName, certs::ldapObjectPath, 1102 certs::certInstallIntf, "Install", 1103 certFile->getCertFilePath()); 1104 }); 1105 } // requestRoutesLDAPCertificateCollection 1106 1107 /** 1108 * Certificate resource describes a certificate used to prove the identity 1109 * of a component, account or service. 1110 */ 1111 inline void requestRoutesLDAPCertificate(App& app) 1112 { 1113 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1114 .privileges(redfish::privileges::getCertificate) 1115 .methods(boost::beast::http::verb::get)( 1116 [](const crow::Request& req, 1117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1118 const std::string&) { 1119 long id = getIDFromURL(req.url); 1120 if (id < 0) 1121 { 1122 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1123 messages::internalError(asyncResp->res); 1124 return; 1125 } 1126 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" 1127 << std::to_string(id); 1128 std::string certURL = 1129 "/redfish/v1/AccountService/LDAP/Certificates/" + 1130 std::to_string(id); 1131 std::string objectPath = certs::ldapObjectPath; 1132 objectPath += "/"; 1133 objectPath += std::to_string(id); 1134 getCertificateProperties(asyncResp, objectPath, 1135 certs::ldapServiceName, id, certURL, 1136 "LDAP Certificate"); 1137 }); 1138 } // requestRoutesLDAPCertificate 1139 /** 1140 * Collection of TrustStoreCertificate certificates 1141 */ 1142 inline void requestRoutesTrustStoreCertificateCollection(App& app) 1143 { 1144 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1145 .privileges(redfish::privileges::getCertificate) 1146 .methods( 1147 boost::beast::http::verb:: 1148 get)([](const crow::Request&, 1149 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1150 asyncResp->res.jsonValue = { 1151 {"@odata.id", 1152 "/redfish/v1/Managers/bmc/Truststore/Certificates/"}, 1153 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1154 {"Name", "TrustStore Certificates Collection"}, 1155 {"Description", 1156 "A Collection of TrustStore certificate instances"}}; 1157 1158 crow::connections::systemBus->async_method_call( 1159 [asyncResp](const boost::system::error_code ec, 1160 const dbus::utility::ManagedObjectType& certs) { 1161 if (ec) 1162 { 1163 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1164 messages::internalError(asyncResp->res); 1165 return; 1166 } 1167 nlohmann::json& members = 1168 asyncResp->res.jsonValue["Members"]; 1169 members = nlohmann::json::array(); 1170 for (const auto& cert : certs) 1171 { 1172 long id = getIDFromURL(cert.first.str); 1173 if (id >= 0) 1174 { 1175 members.push_back( 1176 {{"@odata.id", 1177 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1178 std::to_string(id)}}); 1179 } 1180 } 1181 asyncResp->res.jsonValue["Members@odata.count"] = 1182 members.size(); 1183 }, 1184 certs::authorityServiceName, certs::authorityObjectPath, 1185 certs::dbusObjManagerIntf, "GetManagedObjects"); 1186 }); 1187 1188 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1189 .privileges(redfish::privileges::postCertificateCollection) 1190 .methods( 1191 boost::beast::http::verb:: 1192 post)([](const crow::Request& req, 1193 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1194 std::string certFileBody = 1195 getCertificateFromReqBody(asyncResp, req); 1196 1197 if (certFileBody.empty()) 1198 { 1199 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1200 messages::unrecognizedRequestBody(asyncResp->res); 1201 return; 1202 } 1203 1204 std::shared_ptr<CertificateFile> certFile = 1205 std::make_shared<CertificateFile>(certFileBody); 1206 crow::connections::systemBus->async_method_call( 1207 [asyncResp, certFile](const boost::system::error_code ec, 1208 const std::string& objectPath) { 1209 if (ec) 1210 { 1211 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1212 messages::internalError(asyncResp->res); 1213 return; 1214 } 1215 long certId = getIDFromURL(objectPath); 1216 if (certId < 0) 1217 { 1218 BMCWEB_LOG_ERROR << "Invalid objectPath value" 1219 << objectPath; 1220 messages::internalError(asyncResp->res); 1221 return; 1222 } 1223 std::string certURL = 1224 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1225 std::to_string(certId); 1226 1227 getCertificateProperties( 1228 asyncResp, objectPath, certs::authorityServiceName, 1229 certId, certURL, "TrustStore Certificate"); 1230 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1231 << certFile->getCertFilePath(); 1232 }, 1233 certs::authorityServiceName, certs::authorityObjectPath, 1234 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1235 }); 1236 } // requestRoutesTrustStoreCertificateCollection 1237 1238 /** 1239 * Certificate resource describes a certificate used to prove the identity 1240 * of a component, account or service. 1241 */ 1242 inline void requestRoutesTrustStoreCertificate(App& app) 1243 { 1244 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1245 .privileges(redfish::privileges::getCertificate) 1246 .methods(boost::beast::http::verb::get)( 1247 [](const crow::Request& req, 1248 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1249 const std::string&) { 1250 long id = getIDFromURL(req.url); 1251 if (id < 0) 1252 { 1253 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1254 messages::internalError(asyncResp->res); 1255 return; 1256 } 1257 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID=" 1258 << std::to_string(id); 1259 std::string certURL = 1260 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1261 std::to_string(id); 1262 std::string objectPath = certs::authorityObjectPath; 1263 objectPath += "/"; 1264 objectPath += std::to_string(id); 1265 getCertificateProperties(asyncResp, objectPath, 1266 certs::authorityServiceName, id, 1267 certURL, "TrustStore Certificate"); 1268 }); 1269 1270 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1271 .privileges(redfish::privileges::deleteCertificate) 1272 .methods(boost::beast::http::verb::delete_)( 1273 [](const crow::Request& req, 1274 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1275 const std::string& param) { 1276 if (param.empty()) 1277 { 1278 messages::internalError(asyncResp->res); 1279 return; 1280 } 1281 1282 long id = getIDFromURL(req.url); 1283 if (id < 0) 1284 { 1285 BMCWEB_LOG_ERROR << "Invalid url value: " << req.url; 1286 messages::resourceNotFound(asyncResp->res, 1287 "TrustStore Certificate", 1288 std::string(req.url)); 1289 return; 1290 } 1291 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID=" 1292 << std::to_string(id); 1293 std::string certPath = certs::authorityObjectPath; 1294 certPath += "/"; 1295 certPath += std::to_string(id); 1296 1297 crow::connections::systemBus->async_method_call( 1298 [asyncResp, id](const boost::system::error_code ec) { 1299 if (ec) 1300 { 1301 messages::resourceNotFound(asyncResp->res, 1302 "TrustStore Certificate", 1303 std::to_string(id)); 1304 return; 1305 } 1306 BMCWEB_LOG_INFO << "Certificate deleted"; 1307 asyncResp->res.result( 1308 boost::beast::http::status::no_content); 1309 }, 1310 certs::authorityServiceName, certPath, certs::objDeleteIntf, 1311 "Delete"); 1312 }); 1313 } // requestRoutesTrustStoreCertificate 1314 } // namespace redfish 1315