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