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