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 std::time_t time = static_cast<std::time_t>(*value); 647 asyncResp->res.jsonValue["ValidNotAfter"] = 648 crow::utility::getDateTime(time); 649 } 650 } 651 else if (property.first == "ValidNotBefore") 652 { 653 const uint64_t* value = 654 std::get_if<uint64_t>(&property.second); 655 if (value) 656 { 657 std::time_t time = static_cast<std::time_t>(*value); 658 asyncResp->res.jsonValue["ValidNotBefore"] = 659 crow::utility::getDateTime(time); 660 } 661 } 662 } 663 asyncResp->res.addHeader("Location", certURL); 664 }, 665 service, objectPath, certs::dbusPropIntf, "GetAll", 666 certs::certPropIntf); 667 } 668 669 using GetObjectType = 670 std::vector<std::pair<std::string, std::vector<std::string>>>; 671 672 /** 673 * Action to replace an existing certificate 674 */ 675 inline void requestRoutesCertificateActionsReplaceCertificate(App& app) 676 { 677 BMCWEB_ROUTE( 678 app, 679 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 680 .privileges(redfish::privileges::postCertificateService) 681 .methods( 682 boost::beast::http::verb:: 683 post)([](const crow::Request& req, 684 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 685 std::string certificate; 686 nlohmann::json certificateUri; 687 std::optional<std::string> certificateType = "PEM"; 688 689 if (!json_util::readJson(req, asyncResp->res, "CertificateString", 690 certificate, "CertificateUri", 691 certificateUri, "CertificateType", 692 certificateType)) 693 { 694 BMCWEB_LOG_ERROR << "Required parameters are missing"; 695 messages::internalError(asyncResp->res); 696 return; 697 } 698 699 if (!certificateType) 700 { 701 // should never happen, but it never hurts to be paranoid. 702 return; 703 } 704 if (certificateType != "PEM") 705 { 706 messages::actionParameterNotSupported( 707 asyncResp->res, "CertificateType", "ReplaceCertificate"); 708 return; 709 } 710 711 std::string certURI; 712 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 713 "@odata.id", certURI)) 714 { 715 messages::actionParameterMissing( 716 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 717 return; 718 } 719 720 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 721 long id = getIDFromURL(certURI); 722 if (id < 0) 723 { 724 messages::actionParameterValueFormatError( 725 asyncResp->res, certURI, "CertificateUri", 726 "ReplaceCertificate"); 727 return; 728 } 729 std::string objectPath; 730 std::string name; 731 std::string service; 732 if (boost::starts_with( 733 certURI, 734 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 735 { 736 objectPath = std::string(certs::httpsObjectPath) + "/" + 737 std::to_string(id); 738 name = "HTTPS certificate"; 739 service = certs::httpsServiceName; 740 } 741 else if (boost::starts_with( 742 certURI, 743 "/redfish/v1/AccountService/LDAP/Certificates/")) 744 { 745 objectPath = std::string(certs::ldapObjectPath) + "/" + 746 std::to_string(id); 747 name = "LDAP certificate"; 748 service = certs::ldapServiceName; 749 } 750 else if (boost::starts_with( 751 certURI, 752 "/redfish/v1/Managers/bmc/Truststore/Certificates/")) 753 { 754 objectPath = std::string(certs::authorityObjectPath) + "/" + 755 std::to_string(id); 756 name = "TrustStore certificate"; 757 service = certs::authorityServiceName; 758 } 759 else 760 { 761 messages::actionParameterNotSupported( 762 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 763 return; 764 } 765 766 std::shared_ptr<CertificateFile> certFile = 767 std::make_shared<CertificateFile>(certificate); 768 crow::connections::systemBus->async_method_call( 769 [asyncResp, certFile, objectPath, service, certURI, id, 770 name](const boost::system::error_code ec) { 771 if (ec) 772 { 773 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 774 messages::resourceNotFound(asyncResp->res, name, 775 std::to_string(id)); 776 return; 777 } 778 getCertificateProperties(asyncResp, objectPath, service, id, 779 certURI, name); 780 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 781 << certFile->getCertFilePath(); 782 }, 783 service, objectPath, certs::certReplaceIntf, "Replace", 784 certFile->getCertFilePath()); 785 }); 786 } // requestRoutesCertificateActionsReplaceCertificate 787 788 /** 789 * Certificate resource describes a certificate used to prove the identity 790 * of a component, account or service. 791 */ 792 793 inline void requestRoutesHTTPSCertificate(App& app) 794 { 795 BMCWEB_ROUTE( 796 app, 797 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/") 798 .privileges(redfish::privileges::getCertificate) 799 .methods( 800 boost::beast::http::verb:: 801 get)([](const crow::Request& req, 802 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 803 const std::string& param) -> void { 804 if (param.empty()) 805 { 806 messages::internalError(asyncResp->res); 807 return; 808 } 809 long id = getIDFromURL(req.url); 810 811 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" 812 << std::to_string(id); 813 std::string certURL = 814 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 815 std::to_string(id); 816 std::string objectPath = certs::httpsObjectPath; 817 objectPath += "/"; 818 objectPath += std::to_string(id); 819 getCertificateProperties(asyncResp, objectPath, 820 certs::httpsServiceName, id, certURL, 821 "HTTPS Certificate"); 822 }); 823 } 824 825 /** 826 * Collection of HTTPS certificates 827 */ 828 inline void requestRoutesHTTPSCertificateCollection(App& app) 829 { 830 BMCWEB_ROUTE(app, 831 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 832 .privileges(redfish::privileges::getCertificateCollection) 833 .methods( 834 boost::beast::http::verb:: 835 get)([](const crow::Request&, 836 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 837 asyncResp->res.jsonValue = { 838 {"@odata.id", 839 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, 840 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 841 {"Name", "HTTPS Certificates Collection"}, 842 {"Description", "A Collection of HTTPS certificate instances"}}; 843 844 crow::connections::systemBus->async_method_call( 845 [asyncResp](const boost::system::error_code ec, 846 const ManagedObjectType& certs) { 847 if (ec) 848 { 849 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 850 messages::internalError(asyncResp->res); 851 return; 852 } 853 nlohmann::json& members = 854 asyncResp->res.jsonValue["Members"]; 855 members = nlohmann::json::array(); 856 for (const auto& cert : certs) 857 { 858 long id = getIDFromURL(cert.first.str); 859 if (id >= 0) 860 { 861 members.push_back( 862 {{"@odata.id", 863 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 864 std::to_string(id)}}); 865 } 866 } 867 asyncResp->res.jsonValue["Members@odata.count"] = 868 members.size(); 869 }, 870 certs::httpsServiceName, certs::httpsObjectPath, 871 certs::dbusObjManagerIntf, "GetManagedObjects"); 872 }); 873 874 BMCWEB_ROUTE(app, 875 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 876 .privileges(redfish::privileges::postCertificateCollection) 877 .methods( 878 boost::beast::http::verb:: 879 post)([](const crow::Request& req, 880 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 881 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 882 883 asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, 884 {"Description", "HTTPS Certificate"}}; 885 886 std::string certFileBody = 887 getCertificateFromReqBody(asyncResp, req); 888 889 if (certFileBody.empty()) 890 { 891 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 892 messages::unrecognizedRequestBody(asyncResp->res); 893 return; 894 } 895 896 std::shared_ptr<CertificateFile> certFile = 897 std::make_shared<CertificateFile>(certFileBody); 898 899 crow::connections::systemBus->async_method_call( 900 [asyncResp, certFile](const boost::system::error_code ec, 901 const std::string& objectPath) { 902 if (ec) 903 { 904 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 905 messages::internalError(asyncResp->res); 906 return; 907 } 908 long certId = getIDFromURL(objectPath); 909 if (certId < 0) 910 { 911 BMCWEB_LOG_ERROR << "Invalid objectPath value" 912 << objectPath; 913 messages::internalError(asyncResp->res); 914 return; 915 } 916 std::string certURL = 917 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 918 std::to_string(certId); 919 getCertificateProperties(asyncResp, objectPath, 920 certs::httpsServiceName, certId, 921 certURL, "HTTPS Certificate"); 922 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 923 << certFile->getCertFilePath(); 924 }, 925 certs::httpsServiceName, certs::httpsObjectPath, 926 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 927 }); 928 } // requestRoutesHTTPSCertificateCollection 929 930 /** 931 * @brief Retrieve the certificates installed list and append to the 932 * response 933 * 934 * @param[in] asyncResp Shared pointer to the response message 935 * @param[in] certURL Path of the certificate object 936 * @param[in] path Path of the D-Bus service object 937 * @return None 938 */ 939 inline void 940 getCertificateLocations(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 941 const std::string& certURL, const std::string& path, 942 const std::string& service) 943 { 944 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL 945 << " Path=" << path << " service= " << service; 946 crow::connections::systemBus->async_method_call( 947 [asyncResp, certURL](const boost::system::error_code ec, 948 const ManagedObjectType& certs) { 949 if (ec) 950 { 951 BMCWEB_LOG_WARNING 952 << "Certificate collection query failed: " << ec 953 << ", skipping " << certURL; 954 return; 955 } 956 nlohmann::json& links = 957 asyncResp->res.jsonValue["Links"]["Certificates"]; 958 for (auto& cert : certs) 959 { 960 long id = getIDFromURL(cert.first.str); 961 if (id >= 0) 962 { 963 links.push_back( 964 {{"@odata.id", certURL + std::to_string(id)}}); 965 } 966 } 967 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] = 968 links.size(); 969 }, 970 service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); 971 } 972 973 /** 974 * The certificate location schema defines a resource that an administrator 975 * can use in order to locate all certificates installed on a given service. 976 */ 977 inline void requestRoutesCertificateLocations(App& app) 978 { 979 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 980 .privileges(redfish::privileges::getCertificateLocations) 981 .methods( 982 boost::beast::http::verb:: 983 get)([](const crow::Request&, 984 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 985 asyncResp->res.jsonValue = { 986 {"@odata.id", 987 "/redfish/v1/CertificateService/CertificateLocations"}, 988 {"@odata.type", 989 "#CertificateLocations.v1_0_0.CertificateLocations"}, 990 {"Name", "Certificate Locations"}, 991 {"Id", "CertificateLocations"}, 992 {"Description", 993 "Defines a resource that an administrator can use in order to " 994 "locate all certificates installed on a given service"}}; 995 996 nlohmann::json& links = 997 asyncResp->res.jsonValue["Links"]["Certificates"]; 998 links = nlohmann::json::array(); 999 getCertificateLocations( 1000 asyncResp, 1001 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", 1002 certs::httpsObjectPath, certs::httpsServiceName); 1003 getCertificateLocations( 1004 asyncResp, "/redfish/v1/AccountService/LDAP/Certificates/", 1005 certs::ldapObjectPath, certs::ldapServiceName); 1006 getCertificateLocations( 1007 asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/", 1008 certs::authorityObjectPath, certs::authorityServiceName); 1009 }); 1010 } 1011 // requestRoutesCertificateLocations 1012 1013 /** 1014 * Collection of LDAP certificates 1015 */ 1016 inline void requestRoutesLDAPCertificateCollection(App& app) 1017 { 1018 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1019 .privileges(redfish::privileges::getCertificateCollection) 1020 .methods( 1021 boost::beast::http::verb:: 1022 get)([](const crow::Request&, 1023 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1024 asyncResp->res.jsonValue = { 1025 {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"}, 1026 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1027 {"Name", "LDAP Certificates Collection"}, 1028 {"Description", "A Collection of LDAP certificate instances"}}; 1029 1030 crow::connections::systemBus->async_method_call( 1031 [asyncResp](const boost::system::error_code ec, 1032 const ManagedObjectType& certs) { 1033 nlohmann::json& members = 1034 asyncResp->res.jsonValue["Members"]; 1035 nlohmann::json& count = 1036 asyncResp->res.jsonValue["Members@odata.count"]; 1037 members = nlohmann::json::array(); 1038 count = 0; 1039 if (ec) 1040 { 1041 BMCWEB_LOG_WARNING << "LDAP certificate query failed: " 1042 << ec; 1043 return; 1044 } 1045 for (const auto& cert : certs) 1046 { 1047 long id = getIDFromURL(cert.first.str); 1048 if (id >= 0) 1049 { 1050 members.push_back( 1051 {{"@odata.id", 1052 "/redfish/v1/AccountService/LDAP/Certificates/" + 1053 std::to_string(id)}}); 1054 } 1055 } 1056 count = members.size(); 1057 }, 1058 certs::ldapServiceName, certs::ldapObjectPath, 1059 certs::dbusObjManagerIntf, "GetManagedObjects"); 1060 }); 1061 1062 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1063 .privileges(redfish::privileges::postCertificateCollection) 1064 .methods(boost::beast::http::verb::post)( 1065 [](const crow::Request& req, 1066 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1067 std::string certFileBody = 1068 getCertificateFromReqBody(asyncResp, req); 1069 1070 if (certFileBody.empty()) 1071 { 1072 BMCWEB_LOG_ERROR 1073 << "Cannot get certificate from request body."; 1074 messages::unrecognizedRequestBody(asyncResp->res); 1075 return; 1076 } 1077 1078 std::shared_ptr<CertificateFile> certFile = 1079 std::make_shared<CertificateFile>(certFileBody); 1080 1081 crow::connections::systemBus->async_method_call( 1082 [asyncResp, certFile](const boost::system::error_code ec, 1083 const std::string& objectPath) { 1084 if (ec) 1085 { 1086 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1087 messages::internalError(asyncResp->res); 1088 return; 1089 } 1090 long certId = getIDFromURL(objectPath); 1091 if (certId < 0) 1092 { 1093 BMCWEB_LOG_ERROR << "Invalid objectPath value" 1094 << objectPath; 1095 messages::internalError(asyncResp->res); 1096 return; 1097 } 1098 std::string certURL = 1099 "/redfish/v1/AccountService/LDAP/Certificates/" + 1100 std::to_string(certId); 1101 getCertificateProperties(asyncResp, objectPath, 1102 certs::ldapServiceName, certId, 1103 certURL, "LDAP Certificate"); 1104 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1105 << certFile->getCertFilePath(); 1106 }, 1107 certs::ldapServiceName, certs::ldapObjectPath, 1108 certs::certInstallIntf, "Install", 1109 certFile->getCertFilePath()); 1110 }); 1111 } // requestRoutesLDAPCertificateCollection 1112 1113 /** 1114 * Certificate resource describes a certificate used to prove the identity 1115 * of a component, account or service. 1116 */ 1117 inline void requestRoutesLDAPCertificate(App& app) 1118 { 1119 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1120 .privileges(redfish::privileges::getCertificate) 1121 .methods(boost::beast::http::verb::get)( 1122 [](const crow::Request& req, 1123 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1124 const std::string&) { 1125 long id = getIDFromURL(req.url); 1126 if (id < 0) 1127 { 1128 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1129 messages::internalError(asyncResp->res); 1130 return; 1131 } 1132 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" 1133 << std::to_string(id); 1134 std::string certURL = 1135 "/redfish/v1/AccountService/LDAP/Certificates/" + 1136 std::to_string(id); 1137 std::string objectPath = certs::ldapObjectPath; 1138 objectPath += "/"; 1139 objectPath += std::to_string(id); 1140 getCertificateProperties(asyncResp, objectPath, 1141 certs::ldapServiceName, id, certURL, 1142 "LDAP Certificate"); 1143 }); 1144 } // requestRoutesLDAPCertificate 1145 /** 1146 * Collection of TrustStoreCertificate certificates 1147 */ 1148 inline void requestRoutesTrustStoreCertificateCollection(App& app) 1149 { 1150 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1151 .privileges(redfish::privileges::getCertificate) 1152 .methods( 1153 boost::beast::http::verb:: 1154 get)([](const crow::Request&, 1155 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1156 asyncResp->res.jsonValue = { 1157 {"@odata.id", 1158 "/redfish/v1/Managers/bmc/Truststore/Certificates/"}, 1159 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1160 {"Name", "TrustStore Certificates Collection"}, 1161 {"Description", 1162 "A Collection of TrustStore certificate instances"}}; 1163 1164 crow::connections::systemBus->async_method_call( 1165 [asyncResp](const boost::system::error_code ec, 1166 const ManagedObjectType& certs) { 1167 if (ec) 1168 { 1169 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1170 messages::internalError(asyncResp->res); 1171 return; 1172 } 1173 nlohmann::json& members = 1174 asyncResp->res.jsonValue["Members"]; 1175 members = nlohmann::json::array(); 1176 for (const auto& cert : certs) 1177 { 1178 long id = getIDFromURL(cert.first.str); 1179 if (id >= 0) 1180 { 1181 members.push_back( 1182 {{"@odata.id", 1183 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1184 std::to_string(id)}}); 1185 } 1186 } 1187 asyncResp->res.jsonValue["Members@odata.count"] = 1188 members.size(); 1189 }, 1190 certs::authorityServiceName, certs::authorityObjectPath, 1191 certs::dbusObjManagerIntf, "GetManagedObjects"); 1192 }); 1193 1194 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1195 .privileges(redfish::privileges::postCertificateCollection) 1196 .methods( 1197 boost::beast::http::verb:: 1198 post)([](const crow::Request& req, 1199 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1200 std::string certFileBody = 1201 getCertificateFromReqBody(asyncResp, req); 1202 1203 if (certFileBody.empty()) 1204 { 1205 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1206 messages::unrecognizedRequestBody(asyncResp->res); 1207 return; 1208 } 1209 1210 std::shared_ptr<CertificateFile> certFile = 1211 std::make_shared<CertificateFile>(certFileBody); 1212 crow::connections::systemBus->async_method_call( 1213 [asyncResp, certFile](const boost::system::error_code ec, 1214 const std::string& objectPath) { 1215 if (ec) 1216 { 1217 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1218 messages::internalError(asyncResp->res); 1219 return; 1220 } 1221 long certId = getIDFromURL(objectPath); 1222 if (certId < 0) 1223 { 1224 BMCWEB_LOG_ERROR << "Invalid objectPath value" 1225 << objectPath; 1226 messages::internalError(asyncResp->res); 1227 return; 1228 } 1229 std::string certURL = 1230 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1231 std::to_string(certId); 1232 1233 getCertificateProperties( 1234 asyncResp, objectPath, certs::authorityServiceName, 1235 certId, certURL, "TrustStore Certificate"); 1236 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1237 << certFile->getCertFilePath(); 1238 }, 1239 certs::authorityServiceName, certs::authorityObjectPath, 1240 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1241 }); 1242 } // requestRoutesTrustStoreCertificateCollection 1243 1244 /** 1245 * Certificate resource describes a certificate used to prove the identity 1246 * of a component, account or service. 1247 */ 1248 inline void requestRoutesTrustStoreCertificate(App& app) 1249 { 1250 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1251 .privileges(redfish::privileges::getCertificate) 1252 .methods(boost::beast::http::verb::get)( 1253 [](const crow::Request& req, 1254 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1255 const std::string&) { 1256 long id = getIDFromURL(req.url); 1257 if (id < 0) 1258 { 1259 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1260 messages::internalError(asyncResp->res); 1261 return; 1262 } 1263 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID=" 1264 << std::to_string(id); 1265 std::string certURL = 1266 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1267 std::to_string(id); 1268 std::string objectPath = certs::authorityObjectPath; 1269 objectPath += "/"; 1270 objectPath += std::to_string(id); 1271 getCertificateProperties(asyncResp, objectPath, 1272 certs::authorityServiceName, id, 1273 certURL, "TrustStore Certificate"); 1274 }); 1275 1276 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1277 .privileges(redfish::privileges::deleteCertificate) 1278 .methods(boost::beast::http::verb::delete_)( 1279 [](const crow::Request& req, 1280 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1281 const std::string& param) { 1282 if (param.empty()) 1283 { 1284 messages::internalError(asyncResp->res); 1285 return; 1286 } 1287 1288 long id = getIDFromURL(req.url); 1289 if (id < 0) 1290 { 1291 BMCWEB_LOG_ERROR << "Invalid url value: " << req.url; 1292 messages::resourceNotFound(asyncResp->res, 1293 "TrustStore Certificate", 1294 std::string(req.url)); 1295 return; 1296 } 1297 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID=" 1298 << std::to_string(id); 1299 std::string certPath = certs::authorityObjectPath; 1300 certPath += "/"; 1301 certPath += std::to_string(id); 1302 1303 crow::connections::systemBus->async_method_call( 1304 [asyncResp, id](const boost::system::error_code ec) { 1305 if (ec) 1306 { 1307 messages::resourceNotFound(asyncResp->res, 1308 "TrustStore Certificate", 1309 std::to_string(id)); 1310 return; 1311 } 1312 BMCWEB_LOG_INFO << "Certificate deleted"; 1313 asyncResp->res.result( 1314 boost::beast::http::status::no_content); 1315 }, 1316 certs::authorityServiceName, certPath, certs::objDeleteIntf, 1317 "Delete"); 1318 }); 1319 } // requestRoutesTrustStoreCertificate 1320 } // namespace redfish 1321