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