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::match> 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::match>( 445 *crow::connections::systemBus, match, 446 [asyncResp, service, objectPath, 447 certURI](sdbusplus::message::message& m) { 448 timeout.cancel(); 449 if (m.is_method_error()) 450 { 451 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 452 messages::internalError(asyncResp->res); 453 return; 454 } 455 456 dbus::utility::DBusInteracesMap interfacesProperties; 457 458 sdbusplus::message::object_path csrObjectPath; 459 m.read(csrObjectPath, interfacesProperties); 460 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 461 for (const auto& interface : interfacesProperties) 462 { 463 if (interface.first == "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: " << ec.message(); 477 messages::internalError(asyncResp->res); 478 return; 479 } 480 }, 481 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 482 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 483 commonName, *optContactPerson, country, *optEmail, *optGivenName, 484 *optInitials, *optKeyBitLength, *optKeyCurveId, 485 *optKeyPairAlgorithm, *optKeyUsage, organization, 486 organizationalUnit, state, *optSurname, *optUnstructuredName); 487 }); 488 } // requestRoutesCertificateActionGenerateCSR 489 490 /** 491 * @brief Parse and update Certificate Issue/Subject property 492 * 493 * @param[in] asyncResp Shared pointer to the response message 494 * @param[in] str Issuer/Subject value in key=value pairs 495 * @param[in] type Issuer/Subject 496 * @return None 497 */ 498 static void updateCertIssuerOrSubject(nlohmann::json& out, 499 const std::string_view value) 500 { 501 // example: O=openbmc-project.xyz,CN=localhost 502 std::string_view::iterator i = value.begin(); 503 while (i != value.end()) 504 { 505 std::string_view::iterator tokenBegin = i; 506 while (i != value.end() && *i != '=') 507 { 508 ++i; 509 } 510 if (i == value.end()) 511 { 512 break; 513 } 514 const std::string_view key(tokenBegin, 515 static_cast<size_t>(i - tokenBegin)); 516 ++i; 517 tokenBegin = i; 518 while (i != value.end() && *i != ',') 519 { 520 ++i; 521 } 522 const std::string_view val(tokenBegin, 523 static_cast<size_t>(i - tokenBegin)); 524 if (key == "L") 525 { 526 out["City"] = val; 527 } 528 else if (key == "CN") 529 { 530 out["CommonName"] = val; 531 } 532 else if (key == "C") 533 { 534 out["Country"] = val; 535 } 536 else if (key == "O") 537 { 538 out["Organization"] = val; 539 } 540 else if (key == "OU") 541 { 542 out["OrganizationalUnit"] = val; 543 } 544 else if (key == "ST") 545 { 546 out["State"] = val; 547 } 548 // skip comma character 549 if (i != value.end()) 550 { 551 ++i; 552 } 553 } 554 } 555 556 /** 557 * @brief Retrieve the installed certificate list 558 * 559 * @param[in] asyncResp Shared pointer to the response message 560 * @param[in] basePath DBus object path to search 561 * @param[in] listPtr Json pointer to the list in asyncResp 562 * @param[in] countPtr Json pointer to the count in asyncResp 563 * @return None 564 */ 565 static void 566 getCertificateList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 567 const std::string& basePath, 568 const nlohmann::json::json_pointer& listPtr, 569 const nlohmann::json::json_pointer& countPtr) 570 { 571 crow::connections::systemBus->async_method_call( 572 [asyncResp, listPtr, countPtr]( 573 const boost::system::error_code ec, 574 const dbus::utility::MapperGetSubTreePathsResponse& certPaths) { 575 if (ec) 576 { 577 BMCWEB_LOG_ERROR << "Certificate collection query failed: " << ec; 578 messages::internalError(asyncResp->res); 579 return; 580 } 581 582 nlohmann::json& links = asyncResp->res.jsonValue[listPtr]; 583 links = nlohmann::json::array(); 584 for (const auto& certPath : certPaths) 585 { 586 sdbusplus::message::object_path objPath(certPath); 587 std::string certId = objPath.filename(); 588 if (certId.empty()) 589 { 590 BMCWEB_LOG_ERROR << "Invalid certificate objPath " << certPath; 591 continue; 592 } 593 594 boost::urls::url certURL; 595 if (objPath.parent_path() == certs::httpsObjectPath) 596 { 597 certURL = crow::utility::urlFromPieces( 598 "redfish", "v1", "Managers", "bmc", "NetworkProtocol", 599 "HTTPS", "Certificates", certId); 600 } 601 else if (objPath.parent_path() == certs::ldapObjectPath) 602 { 603 certURL = crow::utility::urlFromPieces("redfish", "v1", 604 "AccountService", "LDAP", 605 "Certificates", certId); 606 } 607 else if (objPath.parent_path() == certs::authorityObjectPath) 608 { 609 certURL = crow::utility::urlFromPieces( 610 "redfish", "v1", "Managers", "bmc", "Truststore", 611 "Certificates", certId); 612 } 613 else 614 { 615 continue; 616 } 617 618 nlohmann::json::object_t link; 619 link["@odata.id"] = certURL; 620 links.emplace_back(std::move(link)); 621 } 622 623 asyncResp->res.jsonValue[countPtr] = links.size(); 624 }, 625 "xyz.openbmc_project.ObjectMapper", 626 "/xyz/openbmc_project/object_mapper", 627 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", basePath, 0, 628 std::array<const char*, 1>{certs::certPropIntf}); 629 } 630 631 /** 632 * @brief Retrieve the certificates properties and append to the response 633 * message 634 * 635 * @param[in] asyncResp Shared pointer to the response message 636 * @param[in] objectPath Path of the D-Bus service object 637 * @param[in] certId Id of the certificate 638 * @param[in] certURL URL of the certificate object 639 * @param[in] name name of the certificate 640 * @return None 641 */ 642 static void getCertificateProperties( 643 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 644 const std::string& objectPath, const std::string& service, long certId, 645 const std::string& certURL, 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, 657 std::to_string(certId)); 658 return; 659 } 660 asyncResp->res.jsonValue["@odata.id"] = certURL; 661 asyncResp->res.jsonValue["@odata.type"] = 662 "#Certificate.v1_0_0.Certificate"; 663 asyncResp->res.jsonValue["Id"] = std::to_string(certId); 664 asyncResp->res.jsonValue["Name"] = name; 665 asyncResp->res.jsonValue["Description"] = name; 666 for (const auto& property : properties) 667 { 668 if (property.first == "CertificateString") 669 { 670 asyncResp->res.jsonValue["CertificateString"] = ""; 671 const std::string* value = 672 std::get_if<std::string>(&property.second); 673 if (value != nullptr) 674 { 675 asyncResp->res.jsonValue["CertificateString"] = *value; 676 } 677 } 678 else if (property.first == "KeyUsage") 679 { 680 nlohmann::json& keyUsage = asyncResp->res.jsonValue["KeyUsage"]; 681 keyUsage = nlohmann::json::array(); 682 const std::vector<std::string>* value = 683 std::get_if<std::vector<std::string>>(&property.second); 684 if (value != nullptr) 685 { 686 for (const std::string& usage : *value) 687 { 688 keyUsage.push_back(usage); 689 } 690 } 691 } 692 else if (property.first == "Issuer") 693 { 694 const std::string* value = 695 std::get_if<std::string>(&property.second); 696 if (value != nullptr) 697 { 698 updateCertIssuerOrSubject( 699 asyncResp->res.jsonValue["Issuer"], *value); 700 } 701 } 702 else if (property.first == "Subject") 703 { 704 const std::string* value = 705 std::get_if<std::string>(&property.second); 706 if (value != nullptr) 707 { 708 updateCertIssuerOrSubject( 709 asyncResp->res.jsonValue["Subject"], *value); 710 } 711 } 712 else if (property.first == "ValidNotAfter") 713 { 714 const uint64_t* value = std::get_if<uint64_t>(&property.second); 715 if (value != nullptr) 716 { 717 asyncResp->res.jsonValue["ValidNotAfter"] = 718 crow::utility::getDateTimeUint(*value); 719 } 720 } 721 else if (property.first == "ValidNotBefore") 722 { 723 const uint64_t* value = std::get_if<uint64_t>(&property.second); 724 if (value != nullptr) 725 { 726 asyncResp->res.jsonValue["ValidNotBefore"] = 727 crow::utility::getDateTimeUint(*value); 728 } 729 } 730 } 731 asyncResp->res.addHeader("Location", certURL); 732 }, 733 service, objectPath, certs::dbusPropIntf, "GetAll", 734 certs::certPropIntf); 735 } 736 737 /** 738 * Action to replace an existing certificate 739 */ 740 inline void requestRoutesCertificateActionsReplaceCertificate(App& app) 741 { 742 BMCWEB_ROUTE( 743 app, 744 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 745 .privileges(redfish::privileges::postCertificateService) 746 .methods(boost::beast::http::verb::post)( 747 [&app](const crow::Request& req, 748 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 749 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 750 { 751 return; 752 } 753 std::string certificate; 754 nlohmann::json certificateUri; 755 std::optional<std::string> certificateType = "PEM"; 756 757 if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString", 758 certificate, "CertificateUri", 759 certificateUri, "CertificateType", 760 certificateType)) 761 { 762 BMCWEB_LOG_ERROR << "Required parameters are missing"; 763 messages::internalError(asyncResp->res); 764 return; 765 } 766 767 if (!certificateType) 768 { 769 // should never happen, but it never hurts to be paranoid. 770 return; 771 } 772 if (certificateType != "PEM") 773 { 774 messages::actionParameterNotSupported( 775 asyncResp->res, "CertificateType", "ReplaceCertificate"); 776 return; 777 } 778 779 std::string certURI; 780 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 781 "@odata.id", certURI)) 782 { 783 messages::actionParameterMissing( 784 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 785 return; 786 } 787 788 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 789 long id = getIDFromURL(certURI); 790 if (id < 0) 791 { 792 messages::actionParameterValueFormatError(asyncResp->res, certURI, 793 "CertificateUri", 794 "ReplaceCertificate"); 795 return; 796 } 797 std::string objectPath; 798 std::string name; 799 std::string service; 800 if (certURI.starts_with( 801 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 802 { 803 objectPath = 804 std::string(certs::httpsObjectPath) + "/" + std::to_string(id); 805 name = "HTTPS certificate"; 806 service = certs::httpsServiceName; 807 } 808 else if (certURI.starts_with( 809 "/redfish/v1/AccountService/LDAP/Certificates/")) 810 { 811 objectPath = 812 std::string(certs::ldapObjectPath) + "/" + std::to_string(id); 813 name = "LDAP certificate"; 814 service = certs::ldapServiceName; 815 } 816 else if (certURI.starts_with( 817 "/redfish/v1/Managers/bmc/Truststore/Certificates/")) 818 { 819 objectPath = std::string(certs::authorityObjectPath) + "/" + 820 std::to_string(id); 821 name = "TrustStore certificate"; 822 service = certs::authorityServiceName; 823 } 824 else 825 { 826 messages::actionParameterNotSupported( 827 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 828 return; 829 } 830 831 std::shared_ptr<CertificateFile> certFile = 832 std::make_shared<CertificateFile>(certificate); 833 crow::connections::systemBus->async_method_call( 834 [asyncResp, certFile, objectPath, service, certURI, id, 835 name](const boost::system::error_code ec) { 836 if (ec) 837 { 838 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 839 if (ec.value() == 840 boost::system::linux_error::bad_request_descriptor) 841 { 842 messages::resourceNotFound(asyncResp->res, name, 843 std::to_string(id)); 844 return; 845 } 846 messages::internalError(asyncResp->res); 847 return; 848 } 849 getCertificateProperties(asyncResp, objectPath, service, id, 850 certURI, name); 851 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 852 << certFile->getCertFilePath(); 853 }, 854 service, objectPath, certs::certReplaceIntf, "Replace", 855 certFile->getCertFilePath()); 856 }); 857 } // requestRoutesCertificateActionsReplaceCertificate 858 859 /** 860 * Certificate resource describes a certificate used to prove the identity 861 * of a component, account or service. 862 */ 863 864 inline void requestRoutesHTTPSCertificate(App& app) 865 { 866 BMCWEB_ROUTE( 867 app, 868 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/") 869 .privileges(redfish::privileges::getCertificate) 870 .methods( 871 boost::beast::http::verb:: 872 get)([&app](const crow::Request& req, 873 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 874 const std::string& param) -> void { 875 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 876 { 877 return; 878 } 879 if (param.empty()) 880 { 881 messages::internalError(asyncResp->res); 882 return; 883 } 884 long id = getIDFromURL(req.url); 885 886 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" 887 << std::to_string(id); 888 std::string certURL = 889 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 890 std::to_string(id); 891 std::string objectPath = certs::httpsObjectPath; 892 objectPath += "/"; 893 objectPath += std::to_string(id); 894 getCertificateProperties(asyncResp, objectPath, 895 certs::httpsServiceName, id, certURL, 896 "HTTPS Certificate"); 897 }); 898 } 899 900 /** 901 * Collection of HTTPS certificates 902 */ 903 inline void requestRoutesHTTPSCertificateCollection(App& app) 904 { 905 BMCWEB_ROUTE(app, 906 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 907 .privileges(redfish::privileges::getCertificateCollection) 908 .methods(boost::beast::http::verb::get)( 909 [&app](const crow::Request& req, 910 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 911 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 912 { 913 return; 914 } 915 916 asyncResp->res.jsonValue["@odata.id"] = 917 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"; 918 asyncResp->res.jsonValue["@odata.type"] = 919 "#CertificateCollection.CertificateCollection"; 920 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection"; 921 asyncResp->res.jsonValue["Description"] = 922 "A Collection of HTTPS certificate instances"; 923 924 getCertificateList(asyncResp, certs::httpsObjectPath, 925 "/Members"_json_pointer, 926 "/Members@odata.count"_json_pointer); 927 }); 928 929 BMCWEB_ROUTE(app, 930 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 931 .privileges(redfish::privileges::postCertificateCollection) 932 .methods(boost::beast::http::verb::post)( 933 [&app](const crow::Request& req, 934 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 935 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 936 { 937 return; 938 } 939 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 940 941 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate"; 942 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate"; 943 944 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 945 946 if (certFileBody.empty()) 947 { 948 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 949 messages::unrecognizedRequestBody(asyncResp->res); 950 return; 951 } 952 953 std::shared_ptr<CertificateFile> certFile = 954 std::make_shared<CertificateFile>(certFileBody); 955 956 crow::connections::systemBus->async_method_call( 957 [asyncResp, certFile](const boost::system::error_code ec, 958 const std::string& objectPath) { 959 if (ec) 960 { 961 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 962 messages::internalError(asyncResp->res); 963 return; 964 } 965 long certId = getIDFromURL(objectPath); 966 if (certId < 0) 967 { 968 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath; 969 messages::internalError(asyncResp->res); 970 return; 971 } 972 std::string certURL = 973 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 974 std::to_string(certId); 975 getCertificateProperties(asyncResp, objectPath, 976 certs::httpsServiceName, certId, certURL, 977 "HTTPS Certificate"); 978 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 979 << certFile->getCertFilePath(); 980 }, 981 certs::httpsServiceName, certs::httpsObjectPath, 982 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 983 }); 984 } // requestRoutesHTTPSCertificateCollection 985 986 /** 987 * The certificate location schema defines a resource that an administrator 988 * can use in order to locate all certificates installed on a given service. 989 */ 990 inline void requestRoutesCertificateLocations(App& app) 991 { 992 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/") 993 .privileges(redfish::privileges::getCertificateLocations) 994 .methods(boost::beast::http::verb::get)( 995 [&app](const crow::Request& req, 996 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 997 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 998 { 999 return; 1000 } 1001 asyncResp->res.jsonValue["@odata.id"] = 1002 "/redfish/v1/CertificateService/CertificateLocations"; 1003 asyncResp->res.jsonValue["@odata.type"] = 1004 "#CertificateLocations.v1_0_0.CertificateLocations"; 1005 asyncResp->res.jsonValue["Name"] = "Certificate Locations"; 1006 asyncResp->res.jsonValue["Id"] = "CertificateLocations"; 1007 asyncResp->res.jsonValue["Description"] = 1008 "Defines a resource that an administrator can use in order to " 1009 "locate all certificates installed on a given service"; 1010 1011 getCertificateList(asyncResp, certs::baseObjectPath, 1012 "/Links/Certificates"_json_pointer, 1013 "/Links/Certificates@odata.count"_json_pointer); 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 [&app](const crow::Request& req, 1027 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1028 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1029 { 1030 return; 1031 } 1032 1033 asyncResp->res.jsonValue["@odata.id"] = 1034 "/redfish/v1/AccountService/LDAP/Certificates"; 1035 asyncResp->res.jsonValue["@odata.type"] = 1036 "#CertificateCollection.CertificateCollection"; 1037 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection"; 1038 asyncResp->res.jsonValue["Description"] = 1039 "A Collection of LDAP certificate instances"; 1040 1041 getCertificateList(asyncResp, certs::ldapObjectPath, 1042 "/Members"_json_pointer, 1043 "/Members@odata.count"_json_pointer); 1044 }); 1045 1046 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1047 .privileges(redfish::privileges::postCertificateCollection) 1048 .methods(boost::beast::http::verb::post)( 1049 [&app](const crow::Request& req, 1050 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1051 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1052 { 1053 return; 1054 } 1055 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1056 1057 if (certFileBody.empty()) 1058 { 1059 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1060 messages::unrecognizedRequestBody(asyncResp->res); 1061 return; 1062 } 1063 1064 std::shared_ptr<CertificateFile> certFile = 1065 std::make_shared<CertificateFile>(certFileBody); 1066 1067 crow::connections::systemBus->async_method_call( 1068 [asyncResp, certFile](const boost::system::error_code ec, 1069 const std::string& objectPath) { 1070 if (ec) 1071 { 1072 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1073 messages::internalError(asyncResp->res); 1074 return; 1075 } 1076 long certId = getIDFromURL(objectPath); 1077 if (certId < 0) 1078 { 1079 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath; 1080 messages::internalError(asyncResp->res); 1081 return; 1082 } 1083 std::string certURL = 1084 "/redfish/v1/AccountService/LDAP/Certificates/" + 1085 std::to_string(certId); 1086 getCertificateProperties(asyncResp, objectPath, 1087 certs::ldapServiceName, certId, certURL, 1088 "LDAP Certificate"); 1089 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1090 << certFile->getCertFilePath(); 1091 }, 1092 certs::ldapServiceName, certs::ldapObjectPath, 1093 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1094 }); 1095 } // requestRoutesLDAPCertificateCollection 1096 1097 /** 1098 * Certificate resource describes a certificate used to prove the identity 1099 * of a component, account or service. 1100 */ 1101 inline void requestRoutesLDAPCertificate(App& app) 1102 { 1103 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1104 .privileges(redfish::privileges::getCertificate) 1105 .methods(boost::beast::http::verb::get)( 1106 [&app](const crow::Request& req, 1107 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1108 const std::string&) { 1109 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1110 { 1111 return; 1112 } 1113 long id = getIDFromURL(req.url); 1114 if (id < 0) 1115 { 1116 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1117 messages::internalError(asyncResp->res); 1118 return; 1119 } 1120 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id); 1121 std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" + 1122 std::to_string(id); 1123 std::string objectPath = certs::ldapObjectPath; 1124 objectPath += "/"; 1125 objectPath += std::to_string(id); 1126 getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName, 1127 id, certURL, "LDAP Certificate"); 1128 }); 1129 } // requestRoutesLDAPCertificate 1130 /** 1131 * Collection of TrustStoreCertificate certificates 1132 */ 1133 inline void requestRoutesTrustStoreCertificateCollection(App& app) 1134 { 1135 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1136 .privileges(redfish::privileges::getCertificate) 1137 .methods(boost::beast::http::verb::get)( 1138 [&app](const crow::Request& req, 1139 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1140 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1141 { 1142 return; 1143 } 1144 1145 asyncResp->res.jsonValue["@odata.id"] = 1146 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1147 asyncResp->res.jsonValue["@odata.type"] = 1148 "#CertificateCollection.CertificateCollection"; 1149 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1150 asyncResp->res.jsonValue["Description"] = 1151 "A Collection of TrustStore certificate instances"; 1152 1153 getCertificateList(asyncResp, certs::authorityObjectPath, 1154 "/Members"_json_pointer, 1155 "/Members@odata.count"_json_pointer); 1156 }); 1157 1158 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1159 .privileges(redfish::privileges::postCertificateCollection) 1160 .methods(boost::beast::http::verb::post)( 1161 [&app](const crow::Request& req, 1162 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1163 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1164 { 1165 return; 1166 } 1167 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1168 1169 if (certFileBody.empty()) 1170 { 1171 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1172 messages::unrecognizedRequestBody(asyncResp->res); 1173 return; 1174 } 1175 1176 std::shared_ptr<CertificateFile> certFile = 1177 std::make_shared<CertificateFile>(certFileBody); 1178 crow::connections::systemBus->async_method_call( 1179 [asyncResp, certFile](const boost::system::error_code ec, 1180 const std::string& objectPath) { 1181 if (ec) 1182 { 1183 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1184 messages::internalError(asyncResp->res); 1185 return; 1186 } 1187 long certId = getIDFromURL(objectPath); 1188 if (certId < 0) 1189 { 1190 BMCWEB_LOG_ERROR << "Invalid objectPath value" << objectPath; 1191 messages::internalError(asyncResp->res); 1192 return; 1193 } 1194 std::string certURL = 1195 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1196 std::to_string(certId); 1197 1198 getCertificateProperties(asyncResp, objectPath, 1199 certs::authorityServiceName, certId, 1200 certURL, "TrustStore Certificate"); 1201 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1202 << certFile->getCertFilePath(); 1203 }, 1204 certs::authorityServiceName, certs::authorityObjectPath, 1205 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1206 }); 1207 } // requestRoutesTrustStoreCertificateCollection 1208 1209 /** 1210 * Certificate resource describes a certificate used to prove the identity 1211 * of a component, account or service. 1212 */ 1213 inline void requestRoutesTrustStoreCertificate(App& app) 1214 { 1215 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1216 .privileges(redfish::privileges::getCertificate) 1217 .methods(boost::beast::http::verb::get)( 1218 [&app](const crow::Request& req, 1219 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1220 const std::string&) { 1221 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1222 { 1223 return; 1224 } 1225 long id = getIDFromURL(req.url); 1226 if (id < 0) 1227 { 1228 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1229 messages::internalError(asyncResp->res); 1230 return; 1231 } 1232 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID=" 1233 << std::to_string(id); 1234 std::string certURL = 1235 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + 1236 std::to_string(id); 1237 std::string objectPath = certs::authorityObjectPath; 1238 objectPath += "/"; 1239 objectPath += std::to_string(id); 1240 getCertificateProperties(asyncResp, objectPath, 1241 certs::authorityServiceName, id, certURL, 1242 "TrustStore Certificate"); 1243 }); 1244 1245 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1246 .privileges(redfish::privileges::deleteCertificate) 1247 .methods(boost::beast::http::verb::delete_)( 1248 [&app](const crow::Request& req, 1249 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1250 const std::string& param) { 1251 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1252 { 1253 return; 1254 } 1255 if (param.empty()) 1256 { 1257 messages::internalError(asyncResp->res); 1258 return; 1259 } 1260 1261 long id = getIDFromURL(req.url); 1262 if (id < 0) 1263 { 1264 BMCWEB_LOG_ERROR << "Invalid url value: " << req.url; 1265 messages::resourceNotFound(asyncResp->res, "TrustStore Certificate", 1266 std::string(req.url)); 1267 return; 1268 } 1269 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID=" 1270 << std::to_string(id); 1271 std::string certPath = certs::authorityObjectPath; 1272 certPath += "/"; 1273 certPath += std::to_string(id); 1274 1275 crow::connections::systemBus->async_method_call( 1276 [asyncResp, id](const boost::system::error_code ec) { 1277 if (ec) 1278 { 1279 messages::resourceNotFound(asyncResp->res, 1280 "TrustStore Certificate", 1281 std::to_string(id)); 1282 return; 1283 } 1284 BMCWEB_LOG_INFO << "Certificate deleted"; 1285 asyncResp->res.result(boost::beast::http::status::no_content); 1286 }, 1287 certs::authorityServiceName, certPath, certs::objDeleteIntf, 1288 "Delete"); 1289 }); 1290 } // requestRoutesTrustStoreCertificate 1291 } // namespace redfish 1292