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 std::string& 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("Location", certURL); 705 }, 706 service, objectPath, certs::dbusPropIntf, "GetAll", 707 certs::certPropIntf); 708 } 709 710 /** 711 * Action to replace an existing certificate 712 */ 713 inline void requestRoutesCertificateActionsReplaceCertificate(App& app) 714 { 715 BMCWEB_ROUTE( 716 app, 717 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/") 718 .privileges(redfish::privileges::postCertificateService) 719 .methods(boost::beast::http::verb::post)( 720 [&app](const crow::Request& req, 721 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 722 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 723 { 724 return; 725 } 726 std::string certificate; 727 nlohmann::json certificateUri; 728 std::optional<std::string> certificateType = "PEM"; 729 730 if (!json_util::readJsonAction(req, asyncResp->res, "CertificateString", 731 certificate, "CertificateUri", 732 certificateUri, "CertificateType", 733 certificateType)) 734 { 735 BMCWEB_LOG_ERROR << "Required parameters are missing"; 736 messages::internalError(asyncResp->res); 737 return; 738 } 739 740 if (!certificateType) 741 { 742 // should never happen, but it never hurts to be paranoid. 743 return; 744 } 745 if (certificateType != "PEM") 746 { 747 messages::actionParameterNotSupported( 748 asyncResp->res, "CertificateType", "ReplaceCertificate"); 749 return; 750 } 751 752 std::string certURI; 753 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 754 "@odata.id", certURI)) 755 { 756 messages::actionParameterMissing( 757 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 758 return; 759 } 760 BMCWEB_LOG_INFO << "Certificate URI to replace: " << certURI; 761 762 boost::urls::result<boost::urls::url_view> parsedUrl = 763 boost::urls::parse_relative_ref(certURI); 764 if (!parsedUrl) 765 { 766 messages::actionParameterValueFormatError(asyncResp->res, certURI, 767 "CertificateUri", 768 "ReplaceCertificate"); 769 return; 770 } 771 772 std::string id; 773 sdbusplus::message::object_path objectPath; 774 std::string name; 775 std::string service; 776 if (crow::utility::readUrlSegments( 777 *parsedUrl, "redfish", "v1", "Managers", "bmc", 778 "NetworkProtocol", "HTTPS", "Certificates", std::ref(id))) 779 { 780 objectPath = 781 sdbusplus::message::object_path(certs::httpsObjectPath) / id; 782 name = "HTTPS certificate"; 783 service = certs::httpsServiceName; 784 } 785 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 786 "AccountService", "LDAP", 787 "Certificates", std::ref(id))) 788 { 789 objectPath = 790 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 791 name = "LDAP certificate"; 792 service = certs::ldapServiceName; 793 } 794 else if (crow::utility::readUrlSegments(*parsedUrl, "redfish", "v1", 795 "Managers", "bmc", "Truststore", 796 "Certificates", std::ref(id))) 797 { 798 objectPath = 799 sdbusplus::message::object_path(certs::authorityObjectPath) / 800 id; 801 name = "TrustStore certificate"; 802 service = certs::authorityServiceName; 803 } 804 else 805 { 806 messages::actionParameterNotSupported( 807 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 808 return; 809 } 810 811 std::shared_ptr<CertificateFile> certFile = 812 std::make_shared<CertificateFile>(certificate); 813 crow::connections::systemBus->async_method_call( 814 [asyncResp, certFile, objectPath, service, certURI, id, 815 name](const boost::system::error_code ec) { 816 if (ec) 817 { 818 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 819 if (ec.value() == 820 boost::system::linux_error::bad_request_descriptor) 821 { 822 messages::resourceNotFound(asyncResp->res, name, id); 823 return; 824 } 825 messages::internalError(asyncResp->res); 826 return; 827 } 828 getCertificateProperties(asyncResp, objectPath, service, id, 829 certURI, 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( 850 boost::beast::http::verb:: 851 get)([&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 std::string certURL = 861 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 862 id; 863 std::string objPath = 864 sdbusplus::message::object_path(certs::httpsObjectPath) / 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 std::string certURL = 940 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 941 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 std::string certURL = 1047 "/redfish/v1/AccountService/LDAP/Certificates/" + certId; 1048 getCertificateProperties(asyncResp, objectPath, 1049 certs::ldapServiceName, certId, certURL, 1050 "LDAP Certificate"); 1051 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1052 << certFile->getCertFilePath(); 1053 }, 1054 certs::ldapServiceName, certs::ldapObjectPath, 1055 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1056 }); 1057 } // requestRoutesLDAPCertificateCollection 1058 1059 /** 1060 * Certificate resource describes a certificate used to prove the identity 1061 * of a component, account or service. 1062 */ 1063 inline void requestRoutesLDAPCertificate(App& app) 1064 { 1065 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/") 1066 .privileges(redfish::privileges::getCertificate) 1067 .methods(boost::beast::http::verb::get)( 1068 [&app](const crow::Request& req, 1069 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1070 const std::string& id) { 1071 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1072 { 1073 return; 1074 } 1075 1076 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << id; 1077 std::string certURL = 1078 "/redfish/v1/AccountService/LDAP/Certificates/" + id; 1079 std::string objPath = 1080 sdbusplus::message::object_path(certs::ldapObjectPath) / id; 1081 getCertificateProperties(asyncResp, objPath, certs::ldapServiceName, id, 1082 certURL, "LDAP Certificate"); 1083 }); 1084 } // requestRoutesLDAPCertificate 1085 /** 1086 * Collection of TrustStoreCertificate certificates 1087 */ 1088 inline void requestRoutesTrustStoreCertificateCollection(App& app) 1089 { 1090 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1091 .privileges(redfish::privileges::getCertificate) 1092 .methods(boost::beast::http::verb::get)( 1093 [&app](const crow::Request& req, 1094 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1095 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1096 { 1097 return; 1098 } 1099 1100 asyncResp->res.jsonValue["@odata.id"] = 1101 "/redfish/v1/Managers/bmc/Truststore/Certificates/"; 1102 asyncResp->res.jsonValue["@odata.type"] = 1103 "#CertificateCollection.CertificateCollection"; 1104 asyncResp->res.jsonValue["Name"] = "TrustStore Certificates Collection"; 1105 asyncResp->res.jsonValue["Description"] = 1106 "A Collection of TrustStore certificate instances"; 1107 1108 getCertificateList(asyncResp, certs::authorityObjectPath, 1109 "/Members"_json_pointer, 1110 "/Members@odata.count"_json_pointer); 1111 }); 1112 1113 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/") 1114 .privileges(redfish::privileges::postCertificateCollection) 1115 .methods(boost::beast::http::verb::post)( 1116 [&app](const crow::Request& req, 1117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 1118 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1119 { 1120 return; 1121 } 1122 std::string certFileBody = getCertificateFromReqBody(asyncResp, req); 1123 1124 if (certFileBody.empty()) 1125 { 1126 BMCWEB_LOG_ERROR << "Cannot get certificate from request body."; 1127 messages::unrecognizedRequestBody(asyncResp->res); 1128 return; 1129 } 1130 1131 std::shared_ptr<CertificateFile> certFile = 1132 std::make_shared<CertificateFile>(certFileBody); 1133 crow::connections::systemBus->async_method_call( 1134 [asyncResp, certFile](const boost::system::error_code ec, 1135 const std::string& objectPath) { 1136 if (ec) 1137 { 1138 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1139 messages::internalError(asyncResp->res); 1140 return; 1141 } 1142 1143 sdbusplus::message::object_path path(objectPath); 1144 std::string certId = path.filename(); 1145 std::string certURL = 1146 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + certId; 1147 getCertificateProperties(asyncResp, objectPath, 1148 certs::authorityServiceName, certId, 1149 certURL, "TrustStore Certificate"); 1150 BMCWEB_LOG_DEBUG << "TrustStore certificate install file=" 1151 << certFile->getCertFilePath(); 1152 }, 1153 certs::authorityServiceName, certs::authorityObjectPath, 1154 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1155 }); 1156 } // requestRoutesTrustStoreCertificateCollection 1157 1158 /** 1159 * Certificate resource describes a certificate used to prove the identity 1160 * of a component, account or service. 1161 */ 1162 inline void requestRoutesTrustStoreCertificate(App& app) 1163 { 1164 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1165 .privileges(redfish::privileges::getCertificate) 1166 .methods(boost::beast::http::verb::get)( 1167 [&app](const crow::Request& req, 1168 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1169 const std::string& id) { 1170 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1171 { 1172 return; 1173 } 1174 1175 BMCWEB_LOG_DEBUG << "Truststore Certificate ID=" << id; 1176 std::string certURL = 1177 "/redfish/v1/Managers/bmc/Truststore/Certificates/" + id; 1178 std::string objPath = 1179 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1180 getCertificateProperties(asyncResp, objPath, 1181 certs::authorityServiceName, id, certURL, 1182 "TrustStore Certificate"); 1183 }); 1184 1185 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/") 1186 .privileges(redfish::privileges::deleteCertificate) 1187 .methods(boost::beast::http::verb::delete_)( 1188 [&app](const crow::Request& req, 1189 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 1190 const std::string& id) { 1191 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 1192 { 1193 return; 1194 } 1195 1196 BMCWEB_LOG_DEBUG << "Delete TrustStore Certificate ID=" << id; 1197 std::string objPath = 1198 sdbusplus::message::object_path(certs::authorityObjectPath) / id; 1199 1200 crow::connections::systemBus->async_method_call( 1201 [asyncResp, id](const boost::system::error_code ec) { 1202 if (ec) 1203 { 1204 messages::resourceNotFound(asyncResp->res, 1205 "TrustStore Certificate", id); 1206 return; 1207 } 1208 BMCWEB_LOG_INFO << "Certificate deleted"; 1209 asyncResp->res.result(boost::beast::http::status::no_content); 1210 }, 1211 certs::authorityServiceName, objPath, certs::objDeleteIntf, 1212 "Delete"); 1213 }); 1214 } // requestRoutesTrustStoreCertificate 1215 } // namespace redfish 1216