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