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