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