1 /* 2 // Copyright (c) 2018 IBM Corporation 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 */ 16 #pragma once 17 18 #include "node.hpp" 19 20 #include <variant> 21 namespace redfish 22 { 23 namespace certs 24 { 25 constexpr char const *httpsObjectPath = 26 "/xyz/openbmc_project/certs/server/https"; 27 constexpr char const *certInstallIntf = "xyz.openbmc_project.Certs.Install"; 28 constexpr char const *certReplaceIntf = "xyz.openbmc_project.Certs.Replace"; 29 constexpr char const *certPropIntf = "xyz.openbmc_project.Certs.Certificate"; 30 constexpr char const *dbusPropIntf = "org.freedesktop.DBus.Properties"; 31 constexpr char const *dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager"; 32 constexpr char const *mapperBusName = "xyz.openbmc_project.ObjectMapper"; 33 constexpr char const *mapperObjectPath = "/xyz/openbmc_project/object_mapper"; 34 constexpr char const *mapperIntf = "xyz.openbmc_project.ObjectMapper"; 35 } // namespace certs 36 37 /** 38 * The Certificate schema defines a Certificate Service which represents the 39 * actions available to manage certificates and links to where certificates 40 * are installed. 41 */ 42 class CertificateService : public Node 43 { 44 public: 45 CertificateService(CrowApp &app) : 46 Node(app, "/redfish/v1/CertificateService/") 47 { 48 // TODO: Issue#61 No entries are available for Certificate 49 // sevice at https://www.dmtf.org/standards/redfish 50 // "redfish standard registries". Need to modify after DMTF 51 // publish Privilege details for certificate service 52 entityPrivileges = { 53 {boost::beast::http::verb::get, {{"Login"}}}, 54 {boost::beast::http::verb::head, {{"Login"}}}, 55 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 56 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 57 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 58 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 59 } 60 61 private: 62 void doGet(crow::Response &res, const crow::Request &req, 63 const std::vector<std::string> ¶ms) override 64 { 65 res.jsonValue = { 66 {"@odata.type", "#CertificateService.v1_0_0.CertificateService"}, 67 {"@odata.id", "/redfish/v1/CertificateService"}, 68 {"@odata.context", 69 "/redfish/v1/$metadata#CertificateService.CertificateService"}, 70 {"Id", "CertificateService"}, 71 {"Name", "Certificate Service"}, 72 {"Description", "Actions available to manage certificates"}}; 73 res.jsonValue["CertificateLocations"] = { 74 {"@odata.id", 75 "/redfish/v1/CertificateService/CertificateLocations"}}; 76 res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = { 77 {"target", "/redfish/v1/CertificateService/Actions/" 78 "CertificateService.ReplaceCertificate"}, 79 {"CertificateType@Redfish.AllowableValues", {"PEM"}}}; 80 res.end(); 81 } 82 }; // CertificateService 83 /** 84 * @brief Find the ID specified in the URL 85 * Finds the numbers specified after the last "/" in the URL and returns. 86 * @param[in] path URL 87 * @return -1 on failure and number on success 88 */ 89 long getIDFromURL(const std::string_view url) 90 { 91 std::size_t found = url.rfind("/"); 92 if (found == std::string::npos) 93 { 94 return -1; 95 } 96 if ((found + 1) < url.length()) 97 { 98 char *endPtr; 99 std::string_view str = url.substr(found + 1); 100 long value = std::strtol(str.data(), &endPtr, 10); 101 if (endPtr != &str.back()) 102 { 103 return -1; 104 } 105 return value; 106 } 107 return -1; 108 } 109 110 /** 111 * Class to create a temporary certificate file for uploading to system 112 */ 113 class CertificateFile 114 { 115 public: 116 CertificateFile() = delete; 117 CertificateFile(const CertificateFile &) = delete; 118 CertificateFile &operator=(const CertificateFile &) = delete; 119 CertificateFile(CertificateFile &&) = delete; 120 CertificateFile &operator=(CertificateFile &&) = delete; 121 CertificateFile(const std::string &certString) 122 { 123 char dirTemplate[] = "/tmp/Certs.XXXXXX"; 124 char *tempDirectory = mkdtemp(dirTemplate); 125 if (tempDirectory) 126 { 127 certDirectory = tempDirectory; 128 certificateFile = certDirectory / "cert.pem"; 129 std::ofstream out(certificateFile, std::ofstream::out | 130 std::ofstream::binary | 131 std::ofstream::trunc); 132 out << certString; 133 out.close(); 134 BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile; 135 } 136 } 137 ~CertificateFile() 138 { 139 if (std::filesystem::exists(certDirectory)) 140 { 141 BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile; 142 try 143 { 144 std::filesystem::remove_all(certDirectory); 145 } 146 catch (const std::filesystem::filesystem_error &e) 147 { 148 BMCWEB_LOG_ERROR << "Failed to remove temp directory" 149 << certDirectory; 150 } 151 } 152 } 153 std::string getCertFilePath() 154 { 155 return certificateFile; 156 } 157 158 private: 159 std::filesystem::path certificateFile; 160 std::filesystem::path certDirectory; 161 }; 162 163 /** 164 * @brief Parse and update Certficate Issue/Subject property 165 * 166 * @param[in] asyncResp Shared pointer to the response message 167 * @param[in] str Issuer/Subject value in key=value pairs 168 * @param[in] type Issuer/Subject 169 * @return None 170 */ 171 static void updateCertIssuerOrSubject(nlohmann::json &out, 172 const std::string_view value) 173 { 174 // example: O=openbmc-project.xyz,CN=localhost 175 std::string_view::iterator i = value.begin(); 176 while (i != value.end()) 177 { 178 std::string_view::iterator tokenBegin = i; 179 while (i != value.end() && *i != '=') 180 { 181 i++; 182 } 183 if (i == value.end()) 184 { 185 break; 186 } 187 const std::string_view key(tokenBegin, i - tokenBegin); 188 i++; 189 tokenBegin = i; 190 while (i != value.end() && *i != ',') 191 { 192 i++; 193 } 194 const std::string_view val(tokenBegin, i - tokenBegin); 195 if (key == "L") 196 { 197 out["City"] = val; 198 } 199 else if (key == "CN") 200 { 201 out["CommonName"] = val; 202 } 203 else if (key == "C") 204 { 205 out["Country"] = val; 206 } 207 else if (key == "O") 208 { 209 out["Organization"] = val; 210 } 211 else if (key == "OU") 212 { 213 out["OrganizationalUnit"] = val; 214 } 215 else if (key == "ST") 216 { 217 out["State"] = val; 218 } 219 // skip comma character 220 if (i != value.end()) 221 { 222 i++; 223 } 224 } 225 } 226 227 /** 228 * @brief Retrieve the certificates properties and append to the response 229 * message 230 * 231 * @param[in] asyncResp Shared pointer to the response message 232 * @param[in] objectPath Path of the D-Bus service object 233 * @param[in] certId Id of the certificate 234 * @param[in] certURL URL of the certificate object 235 * @param[in] name name of the certificate 236 * @return None 237 */ 238 static void getCertificateProperties( 239 const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath, 240 long certId, const std::string &certURL, const std::string &name) 241 { 242 using PropertyType = 243 std::variant<std::string, uint64_t, std::vector<std::string>>; 244 using PropertiesMap = boost::container::flat_map<std::string, PropertyType>; 245 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 246 << " certId=" << certId << " certURl=" << certURL; 247 crow::connections::systemBus->async_method_call( 248 [asyncResp, objectPath, certURL, certId, 249 name](const boost::system::error_code ec, const GetObjectType &resp) { 250 if (ec) 251 { 252 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 253 messages::internalError(asyncResp->res); 254 return; 255 } 256 if (resp.size() > 1 || resp.empty()) 257 { 258 BMCWEB_LOG_ERROR << "Invalid number of objects found " 259 << resp.size(); 260 messages::internalError(asyncResp->res); 261 return; 262 } 263 const std::string &service = resp.begin()->first; 264 crow::connections::systemBus->async_method_call( 265 [asyncResp, certURL, certId, 266 name](const boost::system::error_code ec, 267 const PropertiesMap &properties) { 268 if (ec) 269 { 270 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 271 messages::internalError(asyncResp->res); 272 return; 273 } 274 asyncResp->res.jsonValue = { 275 {"@odata.id", certURL}, 276 {"@odata.type", "#Certificate.v1_0_0.Certificate"}, 277 {"@odata.context", 278 "/redfish/v1/$metadata#Certificate.Certificate"}, 279 {"Id", std::to_string(certId)}, 280 {"Name", name}, 281 {"Description", name}}; 282 for (const auto &property : properties) 283 { 284 if (property.first == "CertificateString") 285 { 286 asyncResp->res.jsonValue["CertificateString"] = ""; 287 const std::string *value = 288 std::get_if<std::string>(&property.second); 289 if (value) 290 { 291 asyncResp->res.jsonValue["CertificateString"] = 292 *value; 293 } 294 } 295 else if (property.first == "KeyUsage") 296 { 297 nlohmann::json &keyUsage = 298 asyncResp->res.jsonValue["KeyUsage"]; 299 keyUsage = nlohmann::json::array(); 300 const std::vector<std::string> *value = 301 std::get_if<std::vector<std::string>>( 302 &property.second); 303 if (value) 304 { 305 for (const std::string &usage : *value) 306 { 307 keyUsage.push_back(usage); 308 } 309 } 310 } 311 else if (property.first == "Issuer") 312 { 313 const std::string *value = 314 std::get_if<std::string>(&property.second); 315 if (value) 316 { 317 updateCertIssuerOrSubject( 318 asyncResp->res.jsonValue["Issuer"], *value); 319 } 320 } 321 else if (property.first == "Subject") 322 { 323 const std::string *value = 324 std::get_if<std::string>(&property.second); 325 if (value) 326 { 327 updateCertIssuerOrSubject( 328 asyncResp->res.jsonValue["Subject"], 329 *value); 330 } 331 } 332 else if (property.first == "ValidNotAfter") 333 { 334 const uint64_t *value = 335 std::get_if<uint64_t>(&property.second); 336 if (value) 337 { 338 std::time_t time = 339 static_cast<std::time_t>(*value); 340 asyncResp->res.jsonValue["ValidNotAfter"] = 341 crow::utility::getDateTime(time); 342 } 343 } 344 else if (property.first == "ValidNotBefore") 345 { 346 const uint64_t *value = 347 std::get_if<uint64_t>(&property.second); 348 if (value) 349 { 350 std::time_t time = 351 static_cast<std::time_t>(*value); 352 asyncResp->res.jsonValue["ValidNotBefore"] = 353 crow::utility::getDateTime(time); 354 } 355 } 356 } 357 asyncResp->res.addHeader("Location", certURL); 358 }, 359 service, objectPath, certs::dbusPropIntf, "GetAll", 360 certs::certPropIntf); 361 }, 362 certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, 363 "GetObject", objectPath, 364 std::array<const char *, 1>{certs::certPropIntf}); 365 } 366 367 using GetObjectType = 368 std::vector<std::pair<std::string, std::vector<std::string>>>; 369 370 /** 371 * Action to replace an existing certificate 372 */ 373 class CertificateActionsReplaceCertificate : public Node 374 { 375 public: 376 CertificateActionsReplaceCertificate(CrowApp &app) : 377 Node(app, "/redfish/v1/CertificateService/Actions/" 378 "CertificateService.ReplaceCertificate/") 379 { 380 entityPrivileges = { 381 {boost::beast::http::verb::get, {{"Login"}}}, 382 {boost::beast::http::verb::head, {{"Login"}}}, 383 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 384 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 385 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 386 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 387 } 388 389 private: 390 void doPost(crow::Response &res, const crow::Request &req, 391 const std::vector<std::string> ¶ms) override 392 { 393 std::string certificate; 394 nlohmann::json certificateUri; 395 std::optional<std::string> certificateType = "PEM"; 396 auto asyncResp = std::make_shared<AsyncResp>(res); 397 if (!json_util::readJson(req, asyncResp->res, "CertificateString", 398 certificate, "CertificateUri", certificateUri, 399 "CertificateType", certificateType)) 400 { 401 BMCWEB_LOG_ERROR << "Required parameters are missing"; 402 messages::internalError(asyncResp->res); 403 return; 404 } 405 406 if (!certificateType) 407 { 408 // should never happen, but it never hurts to be paranoid. 409 return; 410 } 411 if (certificateType != "PEM") 412 { 413 messages::actionParameterNotSupported( 414 asyncResp->res, "CertificateType", "ReplaceCertificate"); 415 return; 416 } 417 418 std::string certURI; 419 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 420 "@odata.id", certURI)) 421 { 422 messages::actionParameterMissing( 423 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 424 return; 425 } 426 427 if (!boost::starts_with( 428 certURI, 429 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 430 { 431 BMCWEB_LOG_ERROR << "Unsupported certificate URI" << certURI; 432 messages::actionParameterValueFormatError(asyncResp->res, certURI, 433 "CertificateUri", 434 "ReplaceCertificate"); 435 return; 436 } 437 438 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 439 long id = getIDFromURL(certURI); 440 if (id < 0) 441 { 442 messages::actionParameterValueFormatError(asyncResp->res, certURI, 443 "CertificateUri", 444 "ReplaceCertificate"); 445 return; 446 } 447 std::string objectPath; 448 std::string name; 449 if (boost::starts_with( 450 certURI, 451 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 452 { 453 objectPath = 454 std::string(certs::httpsObjectPath) + "/" + std::to_string(id); 455 name = "HTTPS certificate"; 456 } 457 else 458 { 459 messages::actionParameterNotSupported( 460 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 461 return; 462 } 463 464 std::shared_ptr<CertificateFile> certFile = 465 std::make_shared<CertificateFile>(certificate); 466 467 crow::connections::systemBus->async_method_call( 468 [asyncResp, objectPath, certFile, id, certURI, name]( 469 const boost::system::error_code ec, const GetObjectType &resp) { 470 if (ec) 471 { 472 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 473 messages::internalError(asyncResp->res); 474 return; 475 } 476 if (resp.size() > 1 || resp.empty()) 477 { 478 BMCWEB_LOG_ERROR << "Invalid number of objects found " 479 << resp.size(); 480 messages::internalError(asyncResp->res); 481 return; 482 } 483 const std::string &service = resp.begin()->first; 484 crow::connections::systemBus->async_method_call( 485 [asyncResp, certFile, objectPath, certURI, id, 486 name](const boost::system::error_code ec) { 487 if (ec) 488 { 489 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 490 messages::internalError(asyncResp->res); 491 return; 492 } 493 getCertificateProperties(asyncResp, objectPath, id, 494 certURI, name); 495 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 496 << certFile->getCertFilePath(); 497 }, 498 service, objectPath, certs::certReplaceIntf, "Replace", 499 certFile->getCertFilePath()); 500 }, 501 certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, 502 "GetObject", objectPath, 503 std::array<std::string, 1>({certs::certReplaceIntf})); 504 } 505 }; // CertificateActionsReplaceCertificate 506 507 /** 508 * Certificate resource describes a certificate used to prove the identity 509 * of a component, account or service. 510 */ 511 class HTTPSCertificate : public Node 512 { 513 public: 514 template <typename CrowApp> 515 HTTPSCertificate(CrowApp &app) : 516 Node(app, 517 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" 518 "<str>/", 519 std::string()) 520 { 521 entityPrivileges = { 522 {boost::beast::http::verb::get, {{"Login"}}}, 523 {boost::beast::http::verb::head, {{"Login"}}}, 524 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 525 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 526 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 527 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 528 } 529 530 void doGet(crow::Response &res, const crow::Request &req, 531 const std::vector<std::string> ¶ms) override 532 { 533 auto asyncResp = std::make_shared<AsyncResp>(res); 534 if (params.size() != 1) 535 { 536 messages::internalError(asyncResp->res); 537 return; 538 } 539 long id = getIDFromURL(req.url); 540 541 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id); 542 std::string certURL = 543 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 544 std::to_string(id); 545 std::string objectPath = certs::httpsObjectPath; 546 objectPath += "/"; 547 objectPath += std::to_string(id); 548 getCertificateProperties(asyncResp, objectPath, id, certURL, 549 "HTTPS Certificate"); 550 } 551 552 }; // namespace redfish 553 554 /** 555 * Collection of HTTPS certificates 556 */ 557 class HTTPSCertificateCollection : public Node 558 { 559 public: 560 template <typename CrowApp> 561 HTTPSCertificateCollection(CrowApp &app) : 562 Node(app, 563 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 564 { 565 entityPrivileges = { 566 {boost::beast::http::verb::get, {{"Login"}}}, 567 {boost::beast::http::verb::head, {{"Login"}}}, 568 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 569 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 570 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 571 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 572 } 573 void doGet(crow::Response &res, const crow::Request &req, 574 const std::vector<std::string> ¶ms) override 575 { 576 res.jsonValue = { 577 {"@odata.id", 578 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, 579 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 580 {"@odata.context", 581 "/redfish/v1/" 582 "$metadata#CertificateCollection.CertificateCollection"}, 583 {"Name", "HTTPS Certificates Collection"}, 584 {"Description", "A Collection of HTTPS certificate instances"}}; 585 auto asyncResp = std::make_shared<AsyncResp>(res); 586 crow::connections::systemBus->async_method_call( 587 [asyncResp](const boost::system::error_code ec, 588 const GetObjectType &resp) { 589 if (ec) 590 { 591 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 592 messages::internalError(asyncResp->res); 593 return; 594 } 595 if (resp.size() > 1 || resp.empty()) 596 { 597 BMCWEB_LOG_ERROR << "Invalid number of objects found " 598 << resp.size(); 599 messages::internalError(asyncResp->res); 600 return; 601 } 602 const std::string &service = resp.begin()->first; 603 crow::connections::systemBus->async_method_call( 604 [asyncResp](const boost::system::error_code ec, 605 const ManagedObjectType &certs) { 606 if (ec) 607 { 608 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 609 messages::internalError(asyncResp->res); 610 return; 611 } 612 nlohmann::json &members = 613 asyncResp->res.jsonValue["Members"]; 614 members = nlohmann::json::array(); 615 for (const auto &cert : certs) 616 { 617 long id = getIDFromURL(cert.first.str); 618 if (id != -1) 619 { 620 members.push_back( 621 {{"@odata.id", 622 "/redfish/v1/Managers/bmc/" 623 "NetworkProtocol/HTTPS/Certificates/" + 624 std::to_string(id)}}); 625 } 626 } 627 asyncResp->res.jsonValue["Members@odata.count"] = 628 members.size(); 629 }, 630 service, certs::httpsObjectPath, certs::dbusObjManagerIntf, 631 "GetManagedObjects"); 632 }, 633 certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, 634 "GetObject", certs::httpsObjectPath, 635 std::array<const char *, 1>{certs::certInstallIntf}); 636 } 637 638 void doPost(crow::Response &res, const crow::Request &req, 639 const std::vector<std::string> ¶ms) override 640 { 641 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 642 auto asyncResp = std::make_shared<AsyncResp>(res); 643 asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, 644 {"Description", "HTTPS Certificate"}}; 645 646 std::shared_ptr<CertificateFile> certFile = 647 std::make_shared<CertificateFile>(req.body); 648 649 crow::connections::systemBus->async_method_call( 650 [asyncResp, certFile](const boost::system::error_code ec, 651 const GetObjectType &resp) { 652 if (ec) 653 { 654 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 655 messages::internalError(asyncResp->res); 656 return; 657 } 658 if (resp.size() > 1 || resp.empty()) 659 { 660 BMCWEB_LOG_ERROR << "Invalid number of objects found " 661 << resp.size(); 662 messages::internalError(asyncResp->res); 663 return; 664 } 665 const std::string &service = resp.begin()->first; 666 crow::connections::systemBus->async_method_call( 667 [asyncResp, certFile](const boost::system::error_code ec) { 668 if (ec) 669 { 670 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 671 messages::internalError(asyncResp->res); 672 return; 673 } 674 // TODO: Issue#84 supporting only 1 certificate 675 long certId = 1; 676 std::string certURL = 677 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/" 678 "Certificates/" + 679 std::to_string(certId); 680 std::string objectPath = 681 std::string(certs::httpsObjectPath) + "/" + 682 std::to_string(certId); 683 getCertificateProperties(asyncResp, objectPath, certId, 684 certURL, "HTTPS Certificate"); 685 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 686 << certFile->getCertFilePath(); 687 }, 688 service, certs::httpsObjectPath, certs::certInstallIntf, 689 "Install", certFile->getCertFilePath()); 690 }, 691 certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, 692 "GetObject", certs::httpsObjectPath, 693 std::array<const char *, 1>{certs::certInstallIntf}); 694 } 695 }; // HTTPSCertificateCollection 696 697 /** 698 * @brief Retrieve the certificates installed list and append to the response 699 * 700 * @param[in] asyncResp Shared pointer to the response message 701 * @param[in] certURL Path of the certificate object 702 * @param[in] path Path of the D-Bus service object 703 * @return None 704 */ 705 static void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp, 706 const std::string &certURL, 707 const std::string &path) 708 { 709 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL 710 << " Path=" << path; 711 crow::connections::systemBus->async_method_call( 712 [asyncResp, path, certURL](const boost::system::error_code ec, 713 const GetObjectType &resp) { 714 if (ec) 715 { 716 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 717 messages::internalError(asyncResp->res); 718 return; 719 } 720 if (resp.size() > 1 || resp.empty()) 721 { 722 BMCWEB_LOG_ERROR << "Invalid number of objects found " 723 << resp.size(); 724 messages::internalError(asyncResp->res); 725 return; 726 } 727 const std::string &service = resp.begin()->first; 728 crow::connections::systemBus->async_method_call( 729 [asyncResp, certURL](const boost::system::error_code ec, 730 const ManagedObjectType &certs) { 731 if (ec) 732 { 733 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 734 messages::internalError(asyncResp->res); 735 return; 736 } 737 nlohmann::json &links = 738 asyncResp->res.jsonValue["Links"]["Certificates"]; 739 for (auto &cert : certs) 740 { 741 long id = getIDFromURL(cert.first.str); 742 if (id != -1) 743 { 744 links.push_back( 745 {{"@odata.id", certURL + std::to_string(id)}}); 746 } 747 } 748 asyncResp->res 749 .jsonValue["Links"]["Certificates@odata.count"] = 750 links.size(); 751 }, 752 service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); 753 }, 754 certs::mapperBusName, certs::mapperObjectPath, certs::mapperIntf, 755 "GetObject", path, std::array<std::string, 0>()); 756 } 757 758 /** 759 * The certificate location schema defines a resource that an administrator 760 * can use in order to locate all certificates installed on a given service. 761 */ 762 class CertificateLocations : public Node 763 { 764 public: 765 template <typename CrowApp> 766 CertificateLocations(CrowApp &app) : 767 Node(app, "/redfish/v1/CertificateService/CertificateLocations/") 768 { 769 entityPrivileges = { 770 {boost::beast::http::verb::get, {{"Login"}}}, 771 {boost::beast::http::verb::head, {{"Login"}}}, 772 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 773 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 774 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 775 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 776 } 777 778 private: 779 void doGet(crow::Response &res, const crow::Request &req, 780 const std::vector<std::string> ¶ms) override 781 { 782 res.jsonValue = { 783 {"@odata.id", 784 "/redfish/v1/CertificateService/CertificateLocations"}, 785 {"@odata.type", 786 "#CertificateLocations.v1_0_0.CertificateLocations"}, 787 {"@odata.context", 788 "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"}, 789 {"Name", "Certificate Locations"}, 790 {"Id", "CertificateLocations"}, 791 {"Description", 792 "Defines a resource that an administrator can use in order to " 793 "locate all certificates installed on a given service"}}; 794 auto asyncResp = std::make_shared<AsyncResp>(res); 795 nlohmann::json &links = 796 asyncResp->res.jsonValue["Links"]["Certificates"]; 797 links = nlohmann::json::array(); 798 getCertificateLocations( 799 asyncResp, 800 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", 801 certs::httpsObjectPath); 802 } 803 }; // CertificateLocations 804 } // namespace redfish 805