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, 644 const std::string& certId, const std::string& certURL, 645 const std::string& name) 646 { 647 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 648 << " certId=" << certId << " certURl=" << certURL; 649 crow::connections::systemBus->async_method_call( 650 [asyncResp, certURL, certId, 651 name](const boost::system::error_code ec, 652 const dbus::utility::DBusPropertiesMap& properties) { 653 if (ec) 654 { 655 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 656 messages::resourceNotFound(asyncResp->res, name, 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"] = 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, 849 std::to_string(id), 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& id) -> void { 874 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 875 { 876 return; 877 } 878 879 BMCWEB_LOG_DEBUG << "HTTPS Certificate ID=" << id; 880 std::string certURL = 881 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 882 id; 883 std::string objPath = 884 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 885 getCertificateProperties(asyncResp, objPath, 886 certs::httpsServiceName, id, certURL, 887 "HTTPS Certificate"); 888 }); 889 } 890 891 /** 892 * Collection of HTTPS certificates 893 */ 894 inline void requestRoutesHTTPSCertificateCollection(App& app) 895 { 896 BMCWEB_ROUTE(app, 897 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 898 .privileges(redfish::privileges::getCertificateCollection) 899 .methods(boost::beast::http::verb::get)( 900 [&app](const crow::Request& req, 901 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 902 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 903 { 904 return; 905 } 906 907 asyncResp->res.jsonValue["@odata.id"] = 908 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 909 asyncResp->res.jsonValue["@odata.type"] = 910 "#CertificateCollection.CertificateCollection"; 911 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection"; 912 asyncResp->res.jsonValue["Description"] = 913 "A Collection of HTTPS certificate instances"; 914 915 getCertificateList(asyncResp, certs::httpsObjectPath, 916 "/Members"_json_pointer, 917 "/Members@odata.count"_json_pointer); 918 }); 919 920 BMCWEB_ROUTE(app, 921 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 922 .privileges(redfish::privileges::postCertificateCollection) 923 .methods(boost::beast::http::verb::post)( 924 [&app](const crow::Request& req, 925 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 926 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 927 { 928 return; 929 } 930 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 931 932 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate"; 933 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate"; 934 935 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 936 937 if (certFileBody.empty()) 938 { 939 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 940 messages::unrecognizedRequestBody(asyncResp->res); 941 return; 942 } 943 944 std::shared_ptr<CertificateFile> certFile = 945 std::make_shared<CertificateFile>(certFileBody); 946 947 crow::connections::systemBus->async_method_call( 948 [asyncResp, certFile](const boost::system::error_code ec, 949 const std::string& objectPath) { 950 if (ec) 951 { 952 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 953 messages::internalError(asyncResp->res); 954 return; 955 } 956 957 sdbusplus::message::object_path path(objectPath); 958 std::string certId = path.filename(); 959 std::string certURL = 960 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 961 certId; 962 getCertificateProperties(asyncResp, objectPath, 963 certs::httpsServiceName, certId, certURL, 964 "HTTPS Certificate"); 965 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 966 << certFile->getCertFilePath(); 967 }, 968 certs::httpsServiceName, certs::httpsObjectPath, 969 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 970 }); 971 } // requestRoutesHTTPSCertificateCollection 972 973 /** 974 * The certificate location schema defines a resource that an administrator 975 * can use in order to locate all certificates installed on a given service. 976 */ 977 inline void requestRoutesCertificateLocations(App& app) 978 { 979 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 980 .privileges(redfish::privileges::getCertificateLocations) 981 .methods(boost::beast::http::verb::get)( 982 [&app](const crow::Request& req, 983 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 984 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 985 { 986 return; 987 } 988 asyncResp->res.jsonValue["@odata.id"] = 989 "/redfish/v1/CertificateService/CertificateLocations"; 990 asyncResp->res.jsonValue["@odata.type"] = 991 "#CertificateLocations.v1_0_0.CertificateLocations"; 992 asyncResp->res.jsonValue["Name"] = "Certificate Locations"; 993 asyncResp->res.jsonValue["Id"] = "CertificateLocations"; 994 asyncResp->res.jsonValue["Description"] = 995 "Defines a resource that an administrator can use in order to " 996 "locate all certificates installed on a given service"; 997 998 getCertificateList(asyncResp, certs::baseObjectPath, 999 "/Links/Certificates"_json_pointer, 1000 "/Links/Certificates@odata.count"_json_pointer); 1001 }); 1002 } 1003 // requestRoutesCertificateLocations 1004 1005 /** 1006 * Collection of LDAP certificates 1007 */ 1008 inline void requestRoutesLDAPCertificateCollection(App& app) 1009 { 1010 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1011 .privileges(redfish::privileges::getCertificateCollection) 1012 .methods(boost::beast::http::verb::get)( 1013 [&app](const crow::Request& req, 1014 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1015 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1016 { 1017 return; 1018 } 1019 1020 asyncResp->res.jsonValue["@odata.id"] = 1021 "/redfish/v1/AccountService/LDAP/Certificates"; 1022 asyncResp->res.jsonValue["@odata.type"] = 1023 "#CertificateCollection.CertificateCollection"; 1024 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection"; 1025 asyncResp->res.jsonValue["Description"] = 1026 "A Collection of LDAP certificate instances"; 1027 1028 getCertificateList(asyncResp, certs::ldapObjectPath, 1029 "/Members"_json_pointer, 1030 "/Members@odata.count"_json_pointer); 1031 }); 1032 1033 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1034 .privileges(redfish::privileges::postCertificateCollection) 1035 .methods(boost::beast::http::verb::post)( 1036 [&app](const crow::Request& req, 1037 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1038 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1039 { 1040 return; 1041 } 1042 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1043 1044 if (certFileBody.empty()) 1045 { 1046 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1047 messages::unrecognizedRequestBody(asyncResp->res); 1048 return; 1049 } 1050 1051 std::shared_ptr<CertificateFile> certFile = 1052 std::make_shared<CertificateFile>(certFileBody); 1053 1054 crow::connections::systemBus->async_method_call( 1055 [asyncResp, certFile](const boost::system::error_code ec, 1056 const std::string& objectPath) { 1057 if (ec) 1058 { 1059 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1060 messages::internalError(asyncResp->res); 1061 return; 1062 } 1063 1064 sdbusplus::message::object_path path(objectPath); 1065 std::string certId = path.filename(); 1066 std::string certURL = 1067 "/redfish/v1/AccountService/LDAP/Certificates/" + certId; 1068 getCertificateProperties(asyncResp, objectPath, 1069 certs::ldapServiceName, certId, certURL, 1070 "LDAP Certificate"); 1071 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1072 << certFile->getCertFilePath(); 1073 }, 1074 certs::ldapServiceName, certs::ldapObjectPath, 1075 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1076 }); 1077 } // requestRoutesLDAPCertificateCollection 1078 1079 /** 1080 * Certificate resource describes a certificate used to prove the identity 1081 * of a component, account or service. 1082 */ 1083 inline void requestRoutesLDAPCertificate(App& app) 1084 { 1085 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1086 .privileges(redfish::privileges::getCertificate) 1087 .methods(boost::beast::http::verb::get)( 1088 [&app](const crow::Request& req, 1089 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1090 const std::string& id) { 1091 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1092 { 1093 return; 1094 } 1095 1096 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id; 1097 std::string certURL = 1098 "/redfish/v1/AccountService/LDAP/Certificates/" + id; 1099 std::string objPath = 1100 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1101 getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id, 1102 certURL, "LDAP Certificate"); 1103 }); 1104 } // requestRoutesLDAPCertificate 1105 /** 1106 * Collection of TrustStoreCertificate certificates 1107 */ 1108 inline void requestRoutesTrustStoreCertificateCollection(App& app) 1109 { 1110 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1111 .privileges(redfish::privileges::getCertificate) 1112 .methods(boost::beast::http::verb::get)( 1113 [&app](const crow::Request& req, 1114 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1115 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1116 { 1117 return; 1118 } 1119 1120 asyncResp->res.jsonValue["@odata.id"] = 1121 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1122 asyncResp->res.jsonValue["@odata.type"] = 1123 "#CertificateCollection.CertificateCollection"; 1124 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1125 asyncResp->res.jsonValue["Description"] = 1126 "A Collection of TrustStore certificate instances"; 1127 1128 getCertificateList(asyncResp, certs::authorityObjectPath, 1129 "/Members"_json_pointer, 1130 "/Members@odata.count"_json_pointer); 1131 }); 1132 1133 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1134 .privileges(redfish::privileges::postCertificateCollection) 1135 .methods(boost::beast::http::verb::post)( 1136 [&app](const crow::Request& req, 1137 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1138 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1139 { 1140 return; 1141 } 1142 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1143 1144 if (certFileBody.empty()) 1145 { 1146 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1147 messages::unrecognizedRequestBody(asyncResp->res); 1148 return; 1149 } 1150 1151 std::shared_ptr<CertificateFile> certFile = 1152 std::make_shared<CertificateFile>(certFileBody); 1153 crow::connections::systemBus->async_method_call( 1154 [asyncResp, certFile](const boost::system::error_code ec, 1155 const std::string& objectPath) { 1156 if (ec) 1157 { 1158 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1159 messages::internalError(asyncResp->res); 1160 return; 1161 } 1162 1163 sdbusplus::message::object_path path(objectPath); 1164 std::string certId = path.filename(); 1165 std::string certURL = 1166 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + certId; 1167 getCertificateProperties(asyncResp, objectPath, 1168 certs::authorityServiceName, certId, 1169 certURL, "TrustStore Certificate"); 1170 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1171 << certFile->getCertFilePath(); 1172 }, 1173 certs::authorityServiceName, certs::authorityObjectPath, 1174 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1175 }); 1176 } // requestRoutesTrustStoreCertificateCollection 1177 1178 /** 1179 * Certificate resource describes a certificate used to prove the identity 1180 * of a component, account or service. 1181 */ 1182 inline void requestRoutesTrustStoreCertificate(App& app) 1183 { 1184 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1185 .privileges(redfish::privileges::getCertificate) 1186 .methods(boost::beast::http::verb::get)( 1187 [&app](const crow::Request& req, 1188 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1189 const std::string& id) { 1190 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1191 { 1192 return; 1193 } 1194 1195 BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id; 1196 std::string certURL = 1197 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + id; 1198 std::string objPath = 1199 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1200 getCertificateProperties(asyncResp, objPath, 1201 certs::authorityServiceName, id, certURL, 1202 "TrustStore Certificate"); 1203 }); 1204 1205 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1206 .privileges(redfish::privileges::deleteCertificate) 1207 .methods(boost::beast::http::verb::delete_)( 1208 [&app](const crow::Request& req, 1209 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1210 const std::string& id) { 1211 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1212 { 1213 return; 1214 } 1215 1216 BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id; 1217 std::string objPath = 1218 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1219 1220 crow::connections::systemBus->async_method_call( 1221 [asyncResp, id](const boost::system::error_code ec) { 1222 if (ec) 1223 { 1224 messages::resourceNotFound(asyncResp->res, 1225 "TrustStore Certificate", id); 1226 return; 1227 } 1228 BMCWEB_LOG_INFO << "Certificate deleted"; 1229 asyncResp->res.result(boost::beast::http::status::no_content); 1230 }, 1231 certs::authorityServiceName, objPath, certs::objDeleteIntf, 1232 "Delete"); 1233 }); 1234 } // requestRoutesTrustStoreCertificate 1235 } // namespace redfish 1236