1 #pragma once 2 3 #include <app.hpp> 4 #include <boost/system/linux_error.hpp> 5 #include <dbus_utility.hpp> 6 #include <query.hpp> 7 #include <registries/privilege_registry.hpp> 8 9 namespace redfish 10 { 11 namespace certs 12 { 13 constexpr char const* certInstallIntf = "xyz.openbmc_project.Certs.Install"; 14 constexpr char const* certReplaceIntf = "xyz.openbmc_project.Certs.Replace"; 15 constexpr char const* objDeleteIntf = "xyz.openbmc_project.Object.Delete"; 16 constexpr char const* certPropIntf = "xyz.openbmc_project.Certs.Certificate"; 17 constexpr char const* dbusPropIntf = "org.freedesktop.DBus.Properties"; 18 constexpr char const* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 19 constexpr char const* httpsServiceName = 20 "xyz.openbmc_project.Certs.Manager.Server.Https"; 21 constexpr char const* ldapServiceName = 22 "xyz.openbmc_project.Certs.Manager.Client.Ldap"; 23 constexpr char const* authorityServiceName = 24 "xyz.openbmc_project.Certs.Manager.Authority.Ldap"; 25 constexpr char const* baseObjectPath = "/xyz/openbmc_project/certs"; 26 constexpr char const* httpsObjectPath = 27 "/xyz/openbmc_project/certs/server/https"; 28 constexpr char const* ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap"; 29 constexpr char const* authorityObjectPath = 30 "/xyz/openbmc_project/certs/authority/ldap"; 31 } // namespace certs 32 33 /** 34 * The Certificate schema defines a Certificate Service which represents the 35 * actions available to manage certificates and links to where certificates 36 * are installed. 37 */ 38 39 // TODO: Issue#61 No entries are available for Certificate 40 // service at https://www.dmtf.org/standards/redfish 41 // "redfish standard registries". Need to modify after DMTF 42 // publish Privilege details for certificate service 43 44 inline void requestRoutesCertificateService(App& app) 45 { 46 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/") 47 .privileges(redfish::privileges::getCertificateService) 48 .methods(boost::beast::http::verb::get)( 49 [&app](const crow::Request& req, 50 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 51 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 52 { 53 return; 54 } 55 56 asyncResp->res.jsonValue["@odata.type"] = 57 "#CertificateService.v1_0_0.CertificateService"; 58 asyncResp->res.jsonValue["@odata.id"] = 59 "/redfish/v1/CertificateService"; 60 asyncResp->res.jsonValue["Id"] = "CertificateService"; 61 asyncResp->res.jsonValue["Name"] = "Certificate Service"; 62 asyncResp->res.jsonValue["Description"] = 63 "Actions available to manage certificates"; 64 // /redfish/v1/CertificateService/CertificateLocations is something 65 // only ConfigureManager can access then only display when the user 66 // has permissions ConfigureManager 67 Privileges effectiveUserPrivileges = 68 redfish::getUserPrivileges(req.userRole); 69 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}}, 70 effectiveUserPrivileges)) 71 { 72 asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] = 73 "/redfish/v1/CertificateService/CertificateLocations"; 74 } 75 asyncResp->res 76 .jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = { 77 {"target", 78 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"}, 79 {"CertificateType@Redfish.AllowableValues", {"PEM"}}}; 80 asyncResp->res 81 .jsonValue["Actions"]["#CertificateService.GenerateCSR"] = { 82 {"target", 83 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"}}; 84 }); 85 } // requestRoutesCertificateService 86 87 inline std::string getCertificateFromReqBody( 88 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 89 const crow::Request& req) 90 { 91 nlohmann::json reqJson = nlohmann::json::parse(req.body, nullptr, false); 92 93 if (reqJson.is_discarded()) 94 { 95 // We did not receive JSON request, proceed as it is RAW data 96 return req.body; 97 } 98 99 std::string certificate; 100 std::optional<std::string> certificateType = "PEM"; 101 102 if (!json_util::readJsonPatch(req, asyncResp->res, "CertificateString", 103 certificate, "CertificateType", 104 certificateType)) 105 { 106 BMCWEB_LOG_ERROR << "Required parameters are missing"; 107 messages::internalError(asyncResp->res); 108 return {}; 109 } 110 111 if (*certificateType != "PEM") 112 { 113 messages::propertyValueNotInList(asyncResp->res, *certificateType, 114 "CertificateType"); 115 return {}; 116 } 117 118 return certificate; 119 } 120 121 /** 122 * Class to create a temporary certificate file for uploading to system 123 */ 124 class CertificateFile 125 { 126 public: 127 CertificateFile() = delete; 128 CertificateFile(const CertificateFile&) = delete; 129 CertificateFile& operator=(const CertificateFile&) = delete; 130 CertificateFile(CertificateFile&&) = delete; 131 CertificateFile& operator=(CertificateFile&&) = delete; 132 explicit CertificateFile(const std::string& certString) 133 { 134 std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C', 135 'e', 'r', 't', 's', '.', 'X', 136 'X', 'X', 'X', 'X', 'X', '\0'}; 137 char* tempDirectory = mkdtemp(dirTemplate.data()); 138 if (tempDirectory != nullptr) 139 { 140 certDirectory = tempDirectory; 141 certificateFile = certDirectory / "cert.pem"; 142 std::ofstream out(certificateFile, std::ofstream::out | 143 std::ofstream::binary | 144 std::ofstream::trunc); 145 out << certString; 146 out.close(); 147 BMCWEB_LOG_DEBUG << "Creating certificate file" 148 << certificateFile.string(); 149 } 150 } 151 ~CertificateFile() 152 { 153 if (std::filesystem::exists(certDirectory)) 154 { 155 BMCWEB_LOG_DEBUG << "Removing certificate file" 156 << certificateFile.string(); 157 std::error_code ec; 158 std::filesystem::remove_all(certDirectory, ec); 159 if (ec) 160 { 161 BMCWEB_LOG_ERROR << "Failed to remove temp directory" 162 << certDirectory.string(); 163 } 164 } 165 } 166 std::string getCertFilePath() 167 { 168 return certificateFile; 169 } 170 171 private: 172 std::filesystem::path certificateFile; 173 std::filesystem::path certDirectory; 174 }; 175 176 static std::unique_ptr<sdbusplus::bus::match_t> csrMatcher; 177 /** 178 * @brief Read data from CSR D-bus object and set to response 179 * 180 * @param[in] asyncResp Shared pointer to the response message 181 * @param[in] certURI Link to certifiate collection URI 182 * @param[in] service D-Bus service name 183 * @param[in] certObjPath certificate D-Bus object path 184 * @param[in] csrObjPath CSR D-Bus object path 185 * @return None 186 */ 187 static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 188 const std::string& certURI, const std::string& service, 189 const std::string& certObjPath, 190 const std::string& csrObjPath) 191 { 192 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath 193 << " CSRObjectPath=" << csrObjPath 194 << " service=" << service; 195 crow::connections::systemBus->async_method_call( 196 [asyncResp, certURI](const boost::system::error_code ec, 197 const std::string& csr) { 198 if (ec) 199 { 200 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 201 messages::internalError(asyncResp->res); 202 return; 203 } 204 if (csr.empty()) 205 { 206 BMCWEB_LOG_ERROR << "CSR read is empty"; 207 messages::internalError(asyncResp->res); 208 return; 209 } 210 asyncResp->res.jsonValue["CSRString"] = csr; 211 asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] = 212 certURI; 213 }, 214 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 215 } 216 217 /** 218 * Action to Generate CSR 219 */ 220 inline void requestRoutesCertificateActionGenerateCSR(App& app) 221 { 222 BMCWEB_ROUTE( 223 app, 224 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/") 225 .privileges(redfish::privileges::postCertificateService) 226 .methods(boost::beast::http::verb::post)( 227 [&app](const crow::Request& req, 228 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 229 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 230 { 231 return; 232 } 233 static const int rsaKeyBitLength = 2048; 234 235 // Required parameters 236 std::string city; 237 std::string commonName; 238 std::string country; 239 std::string organization; 240 std::string organizationalUnit; 241 std::string state; 242 nlohmann::json certificateCollection; 243 244 // Optional parameters 245 std::optional<std::vector<std::string>> optAlternativeNames = 246 std::vector<std::string>(); 247 std::optional<std::string> optContactPerson = ""; 248 std::optional<std::string> optChallengePassword = ""; 249 std::optional<std::string> optEmail = ""; 250 std::optional<std::string> optGivenName = ""; 251 std::optional<std::string> optInitials = ""; 252 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength; 253 std::optional<std::string> optKeyCurveId = "secp384r1"; 254 std::optional<std::string> optKeyPairAlgorithm = "EC"; 255 std::optional<std::vector<std::string>> optKeyUsage = 256 std::vector<std::string>(); 257 std::optional<std::string> optSurname = ""; 258 std::optional<std::string> optUnstructuredName = ""; 259 if (!json_util::readJsonAction( 260 req, asyncResp->res, "City", city, "CommonName", commonName, 261 "ContactPerson", optContactPerson, "Country", country, 262 "Organization", organization, "OrganizationalUnit", 263 organizationalUnit, "State", state, "CertificateCollection", 264 certificateCollection, "AlternativeNames", optAlternativeNames, 265 "ChallengePassword", optChallengePassword, "Email", optEmail, 266 "GivenName", optGivenName, "Initials", optInitials, 267 "KeyBitLength", optKeyBitLength, "KeyCurveId", optKeyCurveId, 268 "KeyPairAlgorithm", optKeyPairAlgorithm, "KeyUsage", 269 optKeyUsage, "Surname", optSurname, "UnstructuredName", 270 optUnstructuredName)) 271 { 272 return; 273 } 274 275 // bmcweb has no way to store or decode a private key challenge 276 // password, which will likely cause bmcweb to crash on startup 277 // if this is not set on a post so not allowing the user to set 278 // value 279 if (!optChallengePassword->empty()) 280 { 281 messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR", 282 "ChallengePassword"); 283 return; 284 } 285 286 std::string certURI; 287 if (!redfish::json_util::readJson(certificateCollection, asyncResp->res, 288 "@odata.id", certURI)) 289 { 290 return; 291 } 292 293 std::string objectPath; 294 std::string service; 295 if (certURI.starts_with( 296 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 297 { 298 objectPath = certs::httpsObjectPath; 299 service = certs::httpsServiceName; 300 } 301 else if (certURI.starts_with( 302 "/redfish/v1/AccountService/LDAP/Certificates")) 303 { 304 objectPath = certs::ldapObjectPath; 305 service = certs::ldapServiceName; 306 } 307 else 308 { 309 messages::actionParameterNotSupported( 310 asyncResp->res, "CertificateCollection", "GenerateCSR"); 311 return; 312 } 313 314 // supporting only EC and RSA algorithm 315 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 316 { 317 messages::actionParameterNotSupported( 318 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 319 return; 320 } 321 322 // supporting only 2048 key bit length for RSA algorithm due to 323 // time consumed in generating private key 324 if (*optKeyPairAlgorithm == "RSA" && 325 *optKeyBitLength != rsaKeyBitLength) 326 { 327 messages::propertyValueNotInList(asyncResp->res, 328 std::to_string(*optKeyBitLength), 329 "KeyBitLength"); 330 return; 331 } 332 333 // validate KeyUsage supporting only 1 type based on URL 334 if (certURI.starts_with( 335 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 336 { 337 if (optKeyUsage->empty()) 338 { 339 optKeyUsage->push_back("ServerAuthentication"); 340 } 341 else if (optKeyUsage->size() == 1) 342 { 343 if ((*optKeyUsage)[0] != "ServerAuthentication") 344 { 345 messages::propertyValueNotInList( 346 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 347 return; 348 } 349 } 350 else 351 { 352 messages::actionParameterNotSupported( 353 asyncResp->res, "KeyUsage", "GenerateCSR"); 354 return; 355 } 356 } 357 else if (certURI.starts_with( 358 "/redfish/v1/AccountService/LDAP/Certificates")) 359 { 360 if (optKeyUsage->empty()) 361 { 362 optKeyUsage->push_back("ClientAuthentication"); 363 } 364 else if (optKeyUsage->size() == 1) 365 { 366 if ((*optKeyUsage)[0] != "ClientAuthentication") 367 { 368 messages::propertyValueNotInList( 369 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 370 return; 371 } 372 } 373 else 374 { 375 messages::actionParameterNotSupported( 376 asyncResp->res, "KeyUsage", "GenerateCSR"); 377 return; 378 } 379 } 380 381 // Only allow one CSR matcher at a time so setting retry 382 // time-out and timer expiry to 10 seconds for now. 383 static const int timeOut = 10; 384 if (csrMatcher) 385 { 386 messages::serviceTemporarilyUnavailable(asyncResp->res, 387 std::to_string(timeOut)); 388 return; 389 } 390 391 // Make this static so it survives outside this method 392 static boost::asio::steady_timer timeout(*req.ioService); 393 timeout.expires_after(std::chrono::seconds(timeOut)); 394 timeout.async_wait([asyncResp](const boost::system::error_code& ec) { 395 csrMatcher = nullptr; 396 if (ec) 397 { 398 // operation_aborted is expected if timer is canceled 399 // before completion. 400 if (ec != boost::asio::error::operation_aborted) 401 { 402 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 403 } 404 return; 405 } 406 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 407 messages::internalError(asyncResp->res); 408 }); 409 410 // create a matcher to wait on CSR object 411 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 412 std::string match("type='signal'," 413 "interface='org.freedesktop.DBus.ObjectManager'," 414 "path='" + 415 objectPath + 416 "'," 417 "member='InterfacesAdded'"); 418 csrMatcher = std::make_unique<sdbusplus::bus::match_t>( 419 *crow::connections::systemBus, match, 420 [asyncResp, service, objectPath, certURI](sdbusplus::message_t& m) { 421 timeout.cancel(); 422 if (m.is_method_error()) 423 { 424 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 425 messages::internalError(asyncResp->res); 426 return; 427 } 428 429 dbus::utility::DBusInteracesMap interfacesProperties; 430 431 sdbusplus::message::object_path csrObjectPath; 432 m.read(csrObjectPath, interfacesProperties); 433 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 434 for (const auto& interface : interfacesProperties) 435 { 436 if (interface.first == "xyz.openbmc_project.Certs.CSR") 437 { 438 getCSR(asyncResp, certURI, service, objectPath, 439 csrObjectPath.str); 440 break; 441 } 442 } 443 }); 444 crow::connections::systemBus->async_method_call( 445 [asyncResp](const boost::system::error_code ec, 446 const std::string&) { 447 if (ec) 448 { 449 BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message(); 450 messages::internalError(asyncResp->res); 451 return; 452 } 453 }, 454 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 455 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 456 commonName, *optContactPerson, country, *optEmail, *optGivenName, 457 *optInitials, *optKeyBitLength, *optKeyCurveId, 458 *optKeyPairAlgorithm, *optKeyUsage, organization, 459 organizationalUnit, state, *optSurname, *optUnstructuredName); 460 }); 461 } // requestRoutesCertificateActionGenerateCSR 462 463 /** 464 * @brief Parse and update Certificate Issue/Subject property 465 * 466 * @param[in] asyncResp Shared pointer to the response message 467 * @param[in] str Issuer/Subject value in key=value pairs 468 * @param[in] type Issuer/Subject 469 * @return None 470 */ 471 static void updateCertIssuerOrSubject(nlohmann::json& out, 472 const std::string_view value) 473 { 474 // example: O=openbmc-project.xyz,CN=localhost 475 std::string_view::iterator i = value.begin(); 476 while (i != value.end()) 477 { 478 std::string_view::iterator tokenBegin = i; 479 while (i != value.end() && *i != '=') 480 { 481 ++i; 482 } 483 if (i == value.end()) 484 { 485 break; 486 } 487 const std::string_view key(tokenBegin, 488 static_cast<size_t>(i - tokenBegin)); 489 ++i; 490 tokenBegin = i; 491 while (i != value.end() && *i != ',') 492 { 493 ++i; 494 } 495 const std::string_view val(tokenBegin, 496 static_cast<size_t>(i - tokenBegin)); 497 if (key == "L") 498 { 499 out["City"] = val; 500 } 501 else if (key == "CN") 502 { 503 out["CommonName"] = val; 504 } 505 else if (key == "C") 506 { 507 out["Country"] = val; 508 } 509 else if (key == "O") 510 { 511 out["Organization"] = val; 512 } 513 else if (key == "OU") 514 { 515 out["OrganizationalUnit"] = val; 516 } 517 else if (key == "ST") 518 { 519 out["State"] = val; 520 } 521 // skip comma character 522 if (i != value.end()) 523 { 524 ++i; 525 } 526 } 527 } 528 529 /** 530 * @brief Retrieve the installed certificate list 531 * 532 * @param[in] asyncResp Shared pointer to the response message 533 * @param[in] basePath DBus object path to search 534 * @param[in] listPtr Json pointer to the list in asyncResp 535 * @param[in] countPtr Json pointer to the count in asyncResp 536 * @return None 537 */ 538 static void 539 getCertificateList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 540 const std::string& basePath, 541 const nlohmann::json::json_pointer& listPtr, 542 const nlohmann::json::json_pointer& countPtr) 543 { 544 crow::connections::systemBus->async_method_call( 545 [asyncResp, listPtr, countPtr]( 546 const boost::system::error_code ec, 547 const dbus::utility::MapperGetSubTreePathsResponse& certPaths) { 548 if (ec) 549 { 550 BMCWEB_LOG_ERROR << "Certificate collection query failed: " << ec; 551 messages::internalError(asyncResp->res); 552 return; 553 } 554 555 nlohmann::json& links = asyncResp->res.jsonValue[listPtr]; 556 links = nlohmann::json::array(); 557 for (const auto& certPath : certPaths) 558 { 559 sdbusplus::message::object_path objPath(certPath); 560 std::string certId = objPath.filename(); 561 if (certId.empty()) 562 { 563 BMCWEB_LOG_ERROR << "Invalid certificate objPath " << certPath; 564 continue; 565 } 566 567 boost::urls::url certURL; 568 if (objPath.parent_path() == certs::httpsObjectPath) 569 { 570 certURL = crow::utility::urlFromPieces( 571 "redfish", "v1", "Managers", "bmc", "NetworkProtocol", 572 "HTTPS", "Certificates", certId); 573 } 574 else if (objPath.parent_path() == certs::ldapObjectPath) 575 { 576 certURL = crow::utility::urlFromPieces("redfish", "v1", 577 "AccountService", "LDAP", 578 "Certificates", certId); 579 } 580 else if (objPath.parent_path() == certs::authorityObjectPath) 581 { 582 certURL = crow::utility::urlFromPieces( 583 "redfish", "v1", "Managers", "bmc", "Truststore", 584 "Certificates", certId); 585 } 586 else 587 { 588 continue; 589 } 590 591 nlohmann::json::object_t link; 592 link["@odata.id"] = certURL; 593 links.emplace_back(std::move(link)); 594 } 595 596 asyncResp->res.jsonValue[countPtr] = links.size(); 597 }, 598 "xyz.openbmc_project.ObjectMapper", 599 "/xyz/openbmc_project/object_mapper", 600 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", basePath, 0, 601 std::array<const char*, 1>{certs::certPropIntf}); 602 } 603 604 /** 605 * @brief Retrieve the certificates properties and append to the response 606 * message 607 * 608 * @param[in] asyncResp Shared pointer to the response message 609 * @param[in] objectPath Path of the D-Bus service object 610 * @param[in] certId Id of the certificate 611 * @param[in] certURL URL of the certificate object 612 * @param[in] name name of the certificate 613 * @return None 614 */ 615 static void getCertificateProperties( 616 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 617 const std::string& objectPath, const std::string& service, 618 const std::string& certId, const boost::urls::url& certURL, 619 const std::string& name) 620 { 621 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 622 << " certId=" << certId << " certURl=" << certURL; 623 crow::connections::systemBus->async_method_call( 624 [asyncResp, certURL, certId, 625 name](const boost::system::error_code ec, 626 const dbus::utility::DBusPropertiesMap& properties) { 627 if (ec) 628 { 629 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 630 messages::resourceNotFound(asyncResp->res, name, certId); 631 return; 632 } 633 asyncResp->res.jsonValue["@odata.id"] = certURL; 634 asyncResp->res.jsonValue["@odata.type"] = 635 "#Certificate.v1_0_0.Certificate"; 636 asyncResp->res.jsonValue["Id"] = certId; 637 asyncResp->res.jsonValue["Name"] = name; 638 asyncResp->res.jsonValue["Description"] = name; 639 for (const auto& property : properties) 640 { 641 if (property.first == "CertificateString") 642 { 643 asyncResp->res.jsonValue["CertificateString"] = ""; 644 const std::string* value = 645 std::get_if<std::string>(&property.second); 646 if (value != nullptr) 647 { 648 asyncResp->res.jsonValue["CertificateString"] = *value; 649 } 650 } 651 else if (property.first == "KeyUsage") 652 { 653 nlohmann::json& keyUsage = asyncResp->res.jsonValue["KeyUsage"]; 654 keyUsage = nlohmann::json::array(); 655 const std::vector<std::string>* value = 656 std::get_if<std::vector<std::string>>(&property.second); 657 if (value != nullptr) 658 { 659 for (const std::string& usage : *value) 660 { 661 keyUsage.push_back(usage); 662 } 663 } 664 } 665 else if (property.first == "Issuer") 666 { 667 const std::string* value = 668 std::get_if<std::string>(&property.second); 669 if (value != nullptr) 670 { 671 updateCertIssuerOrSubject( 672 asyncResp->res.jsonValue["Issuer"], *value); 673 } 674 } 675 else if (property.first == "Subject") 676 { 677 const std::string* value = 678 std::get_if<std::string>(&property.second); 679 if (value != nullptr) 680 { 681 updateCertIssuerOrSubject( 682 asyncResp->res.jsonValue["Subject"], *value); 683 } 684 } 685 else if (property.first == "ValidNotAfter") 686 { 687 const uint64_t* value = std::get_if<uint64_t>(&property.second); 688 if (value != nullptr) 689 { 690 asyncResp->res.jsonValue["ValidNotAfter"] = 691 crow::utility::getDateTimeUint(*value); 692 } 693 } 694 else if (property.first == "ValidNotBefore") 695 { 696 const uint64_t* value = std::get_if<uint64_t>(&property.second); 697 if (value != nullptr) 698 { 699 asyncResp->res.jsonValue["ValidNotBefore"] = 700 crow::utility::getDateTimeUint(*value); 701 } 702 } 703 } 704 asyncResp->res.addHeader( 705 "Location", std::string_view(certURL.data(), certURL.size())); 706 }, 707 service, objectPath, certs::dbusPropIntf, "GetAll", 708 certs::certPropIntf); 709 } 710 711 /** 712 * Action to replace an existing certificate 713 */ 714 inline void requestRoutesCertificateActionsReplaceCertificate(App& app) 715 { 716 BMCWEB_ROUTE( 717 app, 718 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 719 .privileges(redfish::privileges::postCertificateService) 720 .methods(boost::beast::http::verb::post)( 721 [&app](const crow::Request& req, 722 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 723 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 724 { 725 return; 726 } 727 std::string certificate; 728 nlohmann::json certificateUri; 729 std::optional<std::string> certificateType = "PEM"; 730 731 if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString", 732 certificate, "CertificateUri", 733 certificateUri, "CertificateType", 734 certificateType)) 735 { 736 BMCWEB_LOG_ERROR << "Required parameters are missing"; 737 messages::internalError(asyncResp->res); 738 return; 739 } 740 741 if (!certificateType) 742 { 743 // should never happen, but it never hurts to be paranoid. 744 return; 745 } 746 if (certificateType != "PEM") 747 { 748 messages::actionParameterNotSupported( 749 asyncResp->res, "CertificateType", "ReplaceCertificate"); 750 return; 751 } 752 753 std::string certURI; 754 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 755 "@odata.id", certURI)) 756 { 757 messages::actionParameterMissing( 758 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 759 return; 760 } 761 BMCWEB_LOG_INFO << "Certificate URI to replace: " << certURI; 762 763 boost::urls::result<boost::urls::url_view> parsedUrl = 764 boost::urls::parse_relative_ref(certURI); 765 if (!parsedUrl) 766 { 767 messages::actionParameterValueFormatError(asyncResp->res, certURI, 768 "CertificateUri", 769 "ReplaceCertificate"); 770 return; 771 } 772 773 std::string id; 774 sdbusplus::message::object_path objectPath; 775 std::string name; 776 std::string service; 777 if (crow::utility::readUrlSegments( 778 *parsedUrl, "redfish", "v1", "Managers", "bmc", 779 "NetworkProtocol", "HTTPS", "Certificates", std::ref(id))) 780 { 781 objectPath = 782 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 783 name = "HTTPS certificate"; 784 service = certs::httpsServiceName; 785 } 786 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 787 "AccountService", "LDAP", 788 "Certificates", std::ref(id))) 789 { 790 objectPath = 791 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 792 name = "LDAP certificate"; 793 service = certs::ldapServiceName; 794 } 795 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 796 "Managers", "bmc", "Truststore", 797 "Certificates", std::ref(id))) 798 { 799 objectPath = 800 sdbusplus::message::object_path(certs::authorityObjectPath) / 801 id; 802 name = "TrustStore certificate"; 803 service = certs::authorityServiceName; 804 } 805 else 806 { 807 messages::actionParameterNotSupported( 808 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 809 return; 810 } 811 812 std::shared_ptr<CertificateFile> certFile = 813 std::make_shared<CertificateFile>(certificate); 814 crow::connections::systemBus->async_method_call( 815 [asyncResp, certFile, objectPath, service, url{*parsedUrl}, id, 816 name](const boost::system::error_code ec) { 817 if (ec) 818 { 819 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 820 if (ec.value() == 821 boost::system::linux_error::bad_request_descriptor) 822 { 823 messages::resourceNotFound(asyncResp->res, name, 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, 1209 "TrustStore Certificate", id); 1210 return; 1211 } 1212 BMCWEB_LOG_INFO << "Certificate deleted"; 1213 asyncResp->res.result(boost::beast::http::status::no_content); 1214 }, 1215 certs::authorityServiceName, objPath, certs::objDeleteIntf, 1216 "Delete"); 1217 }); 1218 } // requestRoutesTrustStoreCertificate 1219 } // namespace redfish 1220