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