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