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 *ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap"; 33 constexpr char const *httpsServiceName = 34 "xyz.openbmc_project.Certs.Manager.Server.Https"; 35 constexpr char const *ldapServiceName = 36 "xyz.openbmc_project.Certs.Manager.Client.Ldap"; 37 } // namespace certs 38 39 /** 40 * The Certificate schema defines a Certificate Service which represents the 41 * actions available to manage certificates and links to where certificates 42 * are installed. 43 */ 44 class CertificateService : public Node 45 { 46 public: 47 CertificateService(CrowApp &app) : 48 Node(app, "/redfish/v1/CertificateService/") 49 { 50 // TODO: Issue#61 No entries are available for Certificate 51 // sevice at https://www.dmtf.org/standards/redfish 52 // "redfish standard registries". Need to modify after DMTF 53 // publish Privilege details for certificate service 54 entityPrivileges = { 55 {boost::beast::http::verb::get, {{"Login"}}}, 56 {boost::beast::http::verb::head, {{"Login"}}}, 57 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 58 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 59 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 60 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 61 } 62 63 private: 64 void doGet(crow::Response &res, const crow::Request &req, 65 const std::vector<std::string> ¶ms) override 66 { 67 res.jsonValue = { 68 {"@odata.type", "#CertificateService.v1_0_0.CertificateService"}, 69 {"@odata.id", "/redfish/v1/CertificateService"}, 70 {"@odata.context", 71 "/redfish/v1/$metadata#CertificateService.CertificateService"}, 72 {"Id", "CertificateService"}, 73 {"Name", "Certificate Service"}, 74 {"Description", "Actions available to manage certificates"}}; 75 res.jsonValue["CertificateLocations"] = { 76 {"@odata.id", 77 "/redfish/v1/CertificateService/CertificateLocations"}}; 78 res.jsonValue["Actions"]["#CertificateService.ReplaceCertificate"] = { 79 {"target", "/redfish/v1/CertificateService/Actions/" 80 "CertificateService.ReplaceCertificate"}, 81 {"CertificateType@Redfish.AllowableValues", {"PEM"}}}; 82 res.jsonValue["Actions"]["#CertificateService.GenerateCSR"] = { 83 {"target", "/redfish/v1/CertificateService/Actions/" 84 "CertificateService.GenerateCSR"}}; 85 res.end(); 86 } 87 }; // CertificateService 88 89 /** 90 * @brief Find the ID specified in the URL 91 * Finds the numbers specified after the last "/" in the URL and returns. 92 * @param[in] path URL 93 * @return -1 on failure and number on success 94 */ 95 long getIDFromURL(const std::string_view url) 96 { 97 std::size_t found = url.rfind("/"); 98 if (found == std::string::npos) 99 { 100 return -1; 101 } 102 if ((found + 1) < url.length()) 103 { 104 char *endPtr; 105 std::string_view str = url.substr(found + 1); 106 long value = std::strtol(str.data(), &endPtr, 10); 107 if (endPtr != str.end()) 108 { 109 return -1; 110 } 111 return value; 112 } 113 return -1; 114 } 115 116 /** 117 * Class to create a temporary certificate file for uploading to system 118 */ 119 class CertificateFile 120 { 121 public: 122 CertificateFile() = delete; 123 CertificateFile(const CertificateFile &) = delete; 124 CertificateFile &operator=(const CertificateFile &) = delete; 125 CertificateFile(CertificateFile &&) = delete; 126 CertificateFile &operator=(CertificateFile &&) = delete; 127 CertificateFile(const std::string &certString) 128 { 129 char dirTemplate[] = "/tmp/Certs.XXXXXX"; 130 char *tempDirectory = mkdtemp(dirTemplate); 131 if (tempDirectory) 132 { 133 certDirectory = tempDirectory; 134 certificateFile = certDirectory / "cert.pem"; 135 std::ofstream out(certificateFile, std::ofstream::out | 136 std::ofstream::binary | 137 std::ofstream::trunc); 138 out << certString; 139 out.close(); 140 BMCWEB_LOG_DEBUG << "Creating certificate file" << certificateFile; 141 } 142 } 143 ~CertificateFile() 144 { 145 if (std::filesystem::exists(certDirectory)) 146 { 147 BMCWEB_LOG_DEBUG << "Removing certificate file" << certificateFile; 148 try 149 { 150 std::filesystem::remove_all(certDirectory); 151 } 152 catch (const std::filesystem::filesystem_error &e) 153 { 154 BMCWEB_LOG_ERROR << "Failed to remove temp directory" 155 << certDirectory; 156 } 157 } 158 } 159 std::string getCertFilePath() 160 { 161 return certificateFile; 162 } 163 164 private: 165 std::filesystem::path certificateFile; 166 std::filesystem::path certDirectory; 167 }; 168 169 static std::unique_ptr<sdbusplus::bus::match::match> csrMatcher; 170 /** 171 * @brief Read data from CSR D-bus object and set to response 172 * 173 * @param[in] asyncResp Shared pointer to the response message 174 * @param[in] certURI Link to certifiate collection URI 175 * @param[in] service D-Bus service name 176 * @param[in] certObjPath certificate D-Bus object path 177 * @param[in] csrObjPath CSR D-Bus object path 178 * @return None 179 */ 180 static void getCSR(const std::shared_ptr<AsyncResp> &asyncResp, 181 const std::string &certURI, const std::string &service, 182 const std::string &certObjPath, 183 const std::string &csrObjPath) 184 { 185 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath 186 << " CSRObjectPath=" << csrObjPath 187 << " service=" << service; 188 crow::connections::systemBus->async_method_call( 189 [asyncResp, certURI](const boost::system::error_code ec, 190 const std::string &csr) { 191 if (ec) 192 { 193 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 194 messages::internalError(asyncResp->res); 195 return; 196 } 197 if (csr.empty()) 198 { 199 BMCWEB_LOG_ERROR << "CSR read is empty"; 200 messages::internalError(asyncResp->res); 201 return; 202 } 203 asyncResp->res.jsonValue["CSRString"] = csr; 204 asyncResp->res.jsonValue["CertificateCollection"] = { 205 {"@odata.id", certURI}}; 206 }, 207 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR"); 208 } 209 210 /** 211 * Action to Generate CSR 212 */ 213 class CertificateActionGenerateCSR : public Node 214 { 215 public: 216 CertificateActionGenerateCSR(CrowApp &app) : 217 Node(app, "/redfish/v1/CertificateService/Actions/" 218 "CertificateService.GenerateCSR/") 219 { 220 entityPrivileges = { 221 {boost::beast::http::verb::get, {{"Login"}}}, 222 {boost::beast::http::verb::head, {{"Login"}}}, 223 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 224 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 225 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 226 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 227 } 228 229 private: 230 void doPost(crow::Response &res, const crow::Request &req, 231 const std::vector<std::string> ¶ms) override 232 { 233 static const int RSA_KEY_BIT_LENGTH = 2048; 234 auto asyncResp = std::make_shared<AsyncResp>(res); 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 = RSA_KEY_BIT_LENGTH; 253 std::optional<std::string> optKeyCurveId = "prime256v1"; 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::readJson( 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 if this 277 // is not set on a post so not allowing the user to set value 278 if (*optChallengePassword != "") 279 { 280 messages::actionParameterNotSupported(asyncResp->res, "GenerateCSR", 281 "ChallengePassword"); 282 return; 283 } 284 285 std::string certURI; 286 if (!redfish::json_util::readJson(certificateCollection, asyncResp->res, 287 "@odata.id", certURI)) 288 { 289 return; 290 } 291 292 std::string objectPath; 293 std::string service; 294 if (boost::starts_with( 295 certURI, 296 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 297 { 298 objectPath = certs::httpsObjectPath; 299 service = certs::httpsServiceName; 300 } 301 else 302 { 303 messages::actionParameterNotSupported( 304 asyncResp->res, "CertificateCollection", "GenerateCSR"); 305 return; 306 } 307 308 // supporting only EC and RSA algorithm 309 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA") 310 { 311 messages::actionParameterNotSupported( 312 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR"); 313 return; 314 } 315 316 // supporting only 2048 key bit length for RSA algorithm due to time 317 // consumed in generating private key 318 if (*optKeyPairAlgorithm == "RSA" && 319 *optKeyBitLength != RSA_KEY_BIT_LENGTH) 320 { 321 messages::propertyValueNotInList(asyncResp->res, 322 std::to_string(*optKeyBitLength), 323 "KeyBitLength"); 324 return; 325 } 326 327 // validate KeyUsage supporting only 1 type based on URL 328 if (boost::starts_with( 329 certURI, 330 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 331 { 332 if (optKeyUsage->size() == 0) 333 { 334 optKeyUsage->push_back("ServerAuthentication"); 335 } 336 else if (optKeyUsage->size() == 1) 337 { 338 if ((*optKeyUsage)[0] != "ServerAuthentication") 339 { 340 messages::propertyValueNotInList( 341 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 342 return; 343 } 344 } 345 else 346 { 347 messages::actionParameterNotSupported( 348 asyncResp->res, "KeyUsage", "GenerateCSR"); 349 return; 350 } 351 } 352 353 // Only allow one CSR matcher at a time so setting retry time-out and 354 // timer expiry to 10 seconds for now. 355 static const int TIME_OUT = 10; 356 if (csrMatcher) 357 { 358 res.addHeader("Retry-After", std::to_string(TIME_OUT)); 359 messages::serviceTemporarilyUnavailable(asyncResp->res, 360 std::to_string(TIME_OUT)); 361 return; 362 } 363 364 // Make this static so it survives outside this method 365 static boost::asio::steady_timer timeout(*req.ioService); 366 timeout.expires_after(std::chrono::seconds(TIME_OUT)); 367 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 368 csrMatcher = nullptr; 369 if (ec) 370 { 371 // operation_aborted is expected if timer is canceled before 372 // completion. 373 if (ec != boost::asio::error::operation_aborted) 374 { 375 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 376 } 377 return; 378 } 379 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 380 messages::internalError(asyncResp->res); 381 }); 382 383 // create a matcher to wait on CSR object 384 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 385 std::string match("type='signal'," 386 "interface='org.freedesktop.DBus.ObjectManager'," 387 "path='" + 388 objectPath + 389 "'," 390 "member='InterfacesAdded'"); 391 csrMatcher = std::make_unique<sdbusplus::bus::match::match>( 392 *crow::connections::systemBus, match, 393 [asyncResp, service, objectPath, 394 certURI](sdbusplus::message::message &m) { 395 boost::system::error_code ec; 396 timeout.cancel(ec); 397 if (ec) 398 { 399 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 400 csrMatcher = nullptr; 401 } 402 if (m.is_method_error()) 403 { 404 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 405 messages::internalError(asyncResp->res); 406 return; 407 } 408 std::vector<std::pair< 409 std::string, std::vector<std::pair< 410 std::string, std::variant<std::string>>>>> 411 interfacesProperties; 412 sdbusplus::message::object_path csrObjectPath; 413 m.read(csrObjectPath, interfacesProperties); 414 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 415 for (auto &interface : interfacesProperties) 416 { 417 if (interface.first == "xyz.openbmc_project.Certs.CSR") 418 { 419 getCSR(asyncResp, certURI, service, objectPath, 420 csrObjectPath.str); 421 break; 422 } 423 } 424 }); 425 crow::connections::systemBus->async_method_call( 426 [asyncResp](const boost::system::error_code ec, 427 const std::string &path) { 428 if (ec) 429 { 430 BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message(); 431 messages::internalError(asyncResp->res); 432 return; 433 } 434 }, 435 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 436 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 437 commonName, *optContactPerson, country, *optEmail, *optGivenName, 438 *optInitials, *optKeyBitLength, *optKeyCurveId, 439 *optKeyPairAlgorithm, *optKeyUsage, organization, 440 organizationalUnit, state, *optSurname, *optUnstructuredName); 441 } 442 }; // CertificateActionGenerateCSR 443 444 /** 445 * @brief Parse and update Certficate Issue/Subject property 446 * 447 * @param[in] asyncResp Shared pointer to the response message 448 * @param[in] str Issuer/Subject value in key=value pairs 449 * @param[in] type Issuer/Subject 450 * @return None 451 */ 452 static void updateCertIssuerOrSubject(nlohmann::json &out, 453 const std::string_view value) 454 { 455 // example: O=openbmc-project.xyz,CN=localhost 456 std::string_view::iterator i = value.begin(); 457 while (i != value.end()) 458 { 459 std::string_view::iterator tokenBegin = i; 460 while (i != value.end() && *i != '=') 461 { 462 i++; 463 } 464 if (i == value.end()) 465 { 466 break; 467 } 468 const std::string_view key(tokenBegin, i - tokenBegin); 469 i++; 470 tokenBegin = i; 471 while (i != value.end() && *i != ',') 472 { 473 i++; 474 } 475 const std::string_view val(tokenBegin, i - tokenBegin); 476 if (key == "L") 477 { 478 out["City"] = val; 479 } 480 else if (key == "CN") 481 { 482 out["CommonName"] = val; 483 } 484 else if (key == "C") 485 { 486 out["Country"] = val; 487 } 488 else if (key == "O") 489 { 490 out["Organization"] = val; 491 } 492 else if (key == "OU") 493 { 494 out["OrganizationalUnit"] = val; 495 } 496 else if (key == "ST") 497 { 498 out["State"] = val; 499 } 500 // skip comma character 501 if (i != value.end()) 502 { 503 i++; 504 } 505 } 506 } 507 508 /** 509 * @brief Retrieve the certificates properties and append to the response 510 * message 511 * 512 * @param[in] asyncResp Shared pointer to the response message 513 * @param[in] objectPath Path of the D-Bus service object 514 * @param[in] certId Id of the certificate 515 * @param[in] certURL URL of the certificate object 516 * @param[in] name name of the certificate 517 * @return None 518 */ 519 static void getCertificateProperties( 520 const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath, 521 const std::string &service, long certId, const std::string &certURL, 522 const std::string &name) 523 { 524 using PropertyType = 525 std::variant<std::string, uint64_t, std::vector<std::string>>; 526 using PropertiesMap = boost::container::flat_map<std::string, PropertyType>; 527 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 528 << " certId=" << certId << " certURl=" << certURL; 529 crow::connections::systemBus->async_method_call( 530 [asyncResp, certURL, certId, name](const boost::system::error_code ec, 531 const PropertiesMap &properties) { 532 if (ec) 533 { 534 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 535 messages::resourceNotFound(asyncResp->res, name, 536 std::to_string(certId)); 537 return; 538 } 539 asyncResp->res.jsonValue = { 540 {"@odata.id", certURL}, 541 {"@odata.type", "#Certificate.v1_0_0.Certificate"}, 542 {"@odata.context", 543 "/redfish/v1/$metadata#Certificate.Certificate"}, 544 {"Id", std::to_string(certId)}, 545 {"Name", name}, 546 {"Description", name}}; 547 for (const auto &property : properties) 548 { 549 if (property.first == "CertificateString") 550 { 551 asyncResp->res.jsonValue["CertificateString"] = ""; 552 const std::string *value = 553 std::get_if<std::string>(&property.second); 554 if (value) 555 { 556 asyncResp->res.jsonValue["CertificateString"] = *value; 557 } 558 } 559 else if (property.first == "KeyUsage") 560 { 561 nlohmann::json &keyUsage = 562 asyncResp->res.jsonValue["KeyUsage"]; 563 keyUsage = nlohmann::json::array(); 564 const std::vector<std::string> *value = 565 std::get_if<std::vector<std::string>>(&property.second); 566 if (value) 567 { 568 for (const std::string &usage : *value) 569 { 570 keyUsage.push_back(usage); 571 } 572 } 573 } 574 else if (property.first == "Issuer") 575 { 576 const std::string *value = 577 std::get_if<std::string>(&property.second); 578 if (value) 579 { 580 updateCertIssuerOrSubject( 581 asyncResp->res.jsonValue["Issuer"], *value); 582 } 583 } 584 else if (property.first == "Subject") 585 { 586 const std::string *value = 587 std::get_if<std::string>(&property.second); 588 if (value) 589 { 590 updateCertIssuerOrSubject( 591 asyncResp->res.jsonValue["Subject"], *value); 592 } 593 } 594 else if (property.first == "ValidNotAfter") 595 { 596 const uint64_t *value = 597 std::get_if<uint64_t>(&property.second); 598 if (value) 599 { 600 std::time_t time = static_cast<std::time_t>(*value); 601 asyncResp->res.jsonValue["ValidNotAfter"] = 602 crow::utility::getDateTime(time); 603 } 604 } 605 else if (property.first == "ValidNotBefore") 606 { 607 const uint64_t *value = 608 std::get_if<uint64_t>(&property.second); 609 if (value) 610 { 611 std::time_t time = static_cast<std::time_t>(*value); 612 asyncResp->res.jsonValue["ValidNotBefore"] = 613 crow::utility::getDateTime(time); 614 } 615 } 616 } 617 asyncResp->res.addHeader("Location", certURL); 618 }, 619 service, objectPath, certs::dbusPropIntf, "GetAll", 620 certs::certPropIntf); 621 } 622 623 using GetObjectType = 624 std::vector<std::pair<std::string, std::vector<std::string>>>; 625 626 /** 627 * Action to replace an existing certificate 628 */ 629 class CertificateActionsReplaceCertificate : public Node 630 { 631 public: 632 CertificateActionsReplaceCertificate(CrowApp &app) : 633 Node(app, "/redfish/v1/CertificateService/Actions/" 634 "CertificateService.ReplaceCertificate/") 635 { 636 entityPrivileges = { 637 {boost::beast::http::verb::get, {{"Login"}}}, 638 {boost::beast::http::verb::head, {{"Login"}}}, 639 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 640 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 641 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 642 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 643 } 644 645 private: 646 void doPost(crow::Response &res, const crow::Request &req, 647 const std::vector<std::string> ¶ms) override 648 { 649 std::string certificate; 650 nlohmann::json certificateUri; 651 std::optional<std::string> certificateType = "PEM"; 652 auto asyncResp = std::make_shared<AsyncResp>(res); 653 if (!json_util::readJson(req, asyncResp->res, "CertificateString", 654 certificate, "CertificateUri", certificateUri, 655 "CertificateType", certificateType)) 656 { 657 BMCWEB_LOG_ERROR << "Required parameters are missing"; 658 messages::internalError(asyncResp->res); 659 return; 660 } 661 662 if (!certificateType) 663 { 664 // should never happen, but it never hurts to be paranoid. 665 return; 666 } 667 if (certificateType != "PEM") 668 { 669 messages::actionParameterNotSupported( 670 asyncResp->res, "CertificateType", "ReplaceCertificate"); 671 return; 672 } 673 674 std::string certURI; 675 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 676 "@odata.id", certURI)) 677 { 678 messages::actionParameterMissing( 679 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 680 return; 681 } 682 683 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 684 long id = getIDFromURL(certURI); 685 if (id < 0) 686 { 687 messages::actionParameterValueFormatError(asyncResp->res, certURI, 688 "CertificateUri", 689 "ReplaceCertificate"); 690 return; 691 } 692 std::string objectPath; 693 std::string name; 694 std::string service; 695 if (boost::starts_with( 696 certURI, 697 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 698 { 699 objectPath = 700 std::string(certs::httpsObjectPath) + "/" + std::to_string(id); 701 name = "HTTPS certificate"; 702 service = certs::httpsServiceName; 703 } 704 else if (boost::starts_with( 705 certURI, "/redfish/v1/AccountService/LDAP/Certificates/")) 706 { 707 objectPath = 708 std::string(certs::ldapObjectPath) + "/" + std::to_string(id); 709 name = "LDAP certificate"; 710 service = certs::ldapServiceName; 711 } 712 else 713 { 714 messages::actionParameterNotSupported( 715 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 716 return; 717 } 718 719 std::shared_ptr<CertificateFile> certFile = 720 std::make_shared<CertificateFile>(certificate); 721 crow::connections::systemBus->async_method_call( 722 [asyncResp, certFile, objectPath, service, certURI, id, 723 name](const boost::system::error_code ec) { 724 if (ec) 725 { 726 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 727 messages::resourceNotFound(asyncResp->res, name, 728 std::to_string(id)); 729 return; 730 } 731 getCertificateProperties(asyncResp, objectPath, service, id, 732 certURI, name); 733 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 734 << certFile->getCertFilePath(); 735 }, 736 service, objectPath, certs::certReplaceIntf, "Replace", 737 certFile->getCertFilePath()); 738 } 739 }; // CertificateActionsReplaceCertificate 740 741 /** 742 * Certificate resource describes a certificate used to prove the identity 743 * of a component, account or service. 744 */ 745 class HTTPSCertificate : public Node 746 { 747 public: 748 template <typename CrowApp> 749 HTTPSCertificate(CrowApp &app) : 750 Node(app, 751 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" 752 "<str>/", 753 std::string()) 754 { 755 entityPrivileges = { 756 {boost::beast::http::verb::get, {{"Login"}}}, 757 {boost::beast::http::verb::head, {{"Login"}}}, 758 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 759 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 760 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 761 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 762 } 763 764 void doGet(crow::Response &res, const crow::Request &req, 765 const std::vector<std::string> ¶ms) override 766 { 767 auto asyncResp = std::make_shared<AsyncResp>(res); 768 if (params.size() != 1) 769 { 770 messages::internalError(asyncResp->res); 771 return; 772 } 773 long id = getIDFromURL(req.url); 774 775 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id); 776 std::string certURL = 777 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 778 std::to_string(id); 779 std::string objectPath = certs::httpsObjectPath; 780 objectPath += "/"; 781 objectPath += std::to_string(id); 782 getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName, 783 id, certURL, "HTTPS Certificate"); 784 } 785 786 }; // namespace redfish 787 788 /** 789 * Collection of HTTPS certificates 790 */ 791 class HTTPSCertificateCollection : public Node 792 { 793 public: 794 template <typename CrowApp> 795 HTTPSCertificateCollection(CrowApp &app) : 796 Node(app, 797 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 798 { 799 entityPrivileges = { 800 {boost::beast::http::verb::get, {{"Login"}}}, 801 {boost::beast::http::verb::head, {{"Login"}}}, 802 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 803 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 804 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 805 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 806 } 807 void doGet(crow::Response &res, const crow::Request &req, 808 const std::vector<std::string> ¶ms) override 809 { 810 res.jsonValue = { 811 {"@odata.id", 812 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, 813 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 814 {"@odata.context", 815 "/redfish/v1/" 816 "$metadata#CertificateCollection.CertificateCollection"}, 817 {"Name", "HTTPS Certificates Collection"}, 818 {"Description", "A Collection of HTTPS certificate instances"}}; 819 auto asyncResp = std::make_shared<AsyncResp>(res); 820 crow::connections::systemBus->async_method_call( 821 [asyncResp](const boost::system::error_code ec, 822 const ManagedObjectType &certs) { 823 if (ec) 824 { 825 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 826 messages::internalError(asyncResp->res); 827 return; 828 } 829 nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 830 members = nlohmann::json::array(); 831 for (const auto &cert : certs) 832 { 833 long id = getIDFromURL(cert.first.str); 834 if (id >= 0) 835 { 836 members.push_back( 837 {{"@odata.id", 838 "/redfish/v1/Managers/bmc/" 839 "NetworkProtocol/HTTPS/Certificates/" + 840 std::to_string(id)}}); 841 } 842 } 843 asyncResp->res.jsonValue["Members@odata.count"] = 844 members.size(); 845 }, 846 certs::httpsServiceName, certs::httpsObjectPath, 847 certs::dbusObjManagerIntf, "GetManagedObjects"); 848 } 849 850 void doPost(crow::Response &res, const crow::Request &req, 851 const std::vector<std::string> ¶ms) override 852 { 853 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 854 auto asyncResp = std::make_shared<AsyncResp>(res); 855 asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, 856 {"Description", "HTTPS Certificate"}}; 857 858 std::shared_ptr<CertificateFile> certFile = 859 std::make_shared<CertificateFile>(req.body); 860 861 crow::connections::systemBus->async_method_call( 862 [asyncResp, certFile](const boost::system::error_code ec) { 863 if (ec) 864 { 865 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 866 messages::internalError(asyncResp->res); 867 return; 868 } 869 // TODO: Issue#84 supporting only 1 certificate 870 long certId = 1; 871 std::string certURL = 872 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/" 873 "Certificates/" + 874 std::to_string(certId); 875 std::string objectPath = std::string(certs::httpsObjectPath) + 876 "/" + std::to_string(certId); 877 getCertificateProperties(asyncResp, objectPath, 878 certs::httpsServiceName, certId, 879 certURL, "HTTPS Certificate"); 880 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 881 << certFile->getCertFilePath(); 882 }, 883 certs::httpsServiceName, certs::httpsObjectPath, 884 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 885 } 886 }; // HTTPSCertificateCollection 887 888 /** 889 * The certificate location schema defines a resource that an administrator 890 * can use in order to locate all certificates installed on a given service. 891 */ 892 class CertificateLocations : public Node 893 { 894 public: 895 template <typename CrowApp> 896 CertificateLocations(CrowApp &app) : 897 Node(app, "/redfish/v1/CertificateService/CertificateLocations/") 898 { 899 entityPrivileges = { 900 {boost::beast::http::verb::get, {{"Login"}}}, 901 {boost::beast::http::verb::head, {{"Login"}}}, 902 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 903 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 904 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 905 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 906 } 907 908 private: 909 void doGet(crow::Response &res, const crow::Request &req, 910 const std::vector<std::string> ¶ms) override 911 { 912 res.jsonValue = { 913 {"@odata.id", 914 "/redfish/v1/CertificateService/CertificateLocations"}, 915 {"@odata.type", 916 "#CertificateLocations.v1_0_0.CertificateLocations"}, 917 {"@odata.context", 918 "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"}, 919 {"Name", "Certificate Locations"}, 920 {"Id", "CertificateLocations"}, 921 {"Description", 922 "Defines a resource that an administrator can use in order to " 923 "locate all certificates installed on a given service"}}; 924 auto asyncResp = std::make_shared<AsyncResp>(res); 925 nlohmann::json &links = 926 asyncResp->res.jsonValue["Links"]["Certificates"]; 927 links = nlohmann::json::array(); 928 getCertificateLocations( 929 asyncResp, 930 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", 931 certs::httpsObjectPath, certs::httpsServiceName); 932 getCertificateLocations(asyncResp, 933 "/redfish/v1/AccountService/LDAP/Certificates/", 934 certs::ldapObjectPath, certs::ldapServiceName); 935 } 936 /** 937 * @brief Retrieve the certificates installed list and append to the 938 * response 939 * 940 * @param[in] asyncResp Shared pointer to the response message 941 * @param[in] certURL Path of the certificate object 942 * @param[in] path Path of the D-Bus service object 943 * @return None 944 */ 945 void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp, 946 const std::string &certURL, 947 const std::string &path, 948 const std::string &service) 949 { 950 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL 951 << " Path=" << path << " service= " << service; 952 crow::connections::systemBus->async_method_call( 953 [asyncResp, certURL](const boost::system::error_code ec, 954 const ManagedObjectType &certs) { 955 if (ec) 956 { 957 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 958 messages::internalError(asyncResp->res); 959 return; 960 } 961 nlohmann::json &links = 962 asyncResp->res.jsonValue["Links"]["Certificates"]; 963 for (auto &cert : certs) 964 { 965 long id = getIDFromURL(cert.first.str); 966 if (id >= 0) 967 { 968 links.push_back( 969 {{"@odata.id", certURL + std::to_string(id)}}); 970 } 971 } 972 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] = 973 links.size(); 974 }, 975 service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); 976 } 977 }; // CertificateLocations 978 979 /** 980 * Collection of LDAP certificates 981 */ 982 class LDAPCertificateCollection : public Node 983 { 984 public: 985 template <typename CrowApp> 986 LDAPCertificateCollection(CrowApp &app) : 987 Node(app, "/redfish/v1/AccountService/LDAP/Certificates/") 988 { 989 entityPrivileges = { 990 {boost::beast::http::verb::get, {{"Login"}}}, 991 {boost::beast::http::verb::head, {{"Login"}}}, 992 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 993 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 994 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 995 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 996 } 997 void doGet(crow::Response &res, const crow::Request &req, 998 const std::vector<std::string> ¶ms) override 999 { 1000 res.jsonValue = { 1001 {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"}, 1002 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1003 {"@odata.context", 1004 "/redfish/v1/" 1005 "$metadata#CertificateCollection.CertificateCollection"}, 1006 {"Name", "LDAP Certificates Collection"}, 1007 {"Description", "A Collection of LDAP certificate instances"}}; 1008 auto asyncResp = std::make_shared<AsyncResp>(res); 1009 crow::connections::systemBus->async_method_call( 1010 [asyncResp](const boost::system::error_code ec, 1011 const ManagedObjectType &certs) { 1012 if (ec) 1013 { 1014 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1015 messages::internalError(asyncResp->res); 1016 return; 1017 } 1018 nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 1019 members = nlohmann::json::array(); 1020 for (const auto &cert : certs) 1021 { 1022 long id = getIDFromURL(cert.first.str); 1023 if (id >= 0) 1024 { 1025 members.push_back( 1026 {{"@odata.id", "/redfish/v1/AccountService/" 1027 "LDAP/Certificates/" + 1028 std::to_string(id)}}); 1029 } 1030 } 1031 asyncResp->res.jsonValue["Members@odata.count"] = 1032 members.size(); 1033 }, 1034 certs::ldapServiceName, certs::ldapObjectPath, 1035 certs::dbusObjManagerIntf, "GetManagedObjects"); 1036 } 1037 1038 void doPost(crow::Response &res, const crow::Request &req, 1039 const std::vector<std::string> ¶ms) override 1040 { 1041 std::shared_ptr<CertificateFile> certFile = 1042 std::make_shared<CertificateFile>(req.body); 1043 auto asyncResp = std::make_shared<AsyncResp>(res); 1044 crow::connections::systemBus->async_method_call( 1045 [asyncResp, certFile](const boost::system::error_code ec) { 1046 if (ec) 1047 { 1048 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1049 messages::internalError(asyncResp->res); 1050 return; 1051 } 1052 //// TODO: Issue#84 supporting only 1 certificate 1053 long certId = 1; 1054 std::string certURL = 1055 "/redfish/v1/AccountService/LDAP/Certificates/" + 1056 std::to_string(certId); 1057 std::string objectPath = std::string(certs::ldapObjectPath) + 1058 "/" + std::to_string(certId); 1059 getCertificateProperties(asyncResp, objectPath, 1060 certs::ldapServiceName, certId, 1061 certURL, "LDAP Certificate"); 1062 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1063 << certFile->getCertFilePath(); 1064 }, 1065 certs::ldapServiceName, certs::ldapObjectPath, 1066 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1067 } 1068 }; // LDAPCertificateCollection 1069 1070 /** 1071 * Certificate resource describes a certificate used to prove the identity 1072 * of a component, account or service. 1073 */ 1074 class LDAPCertificate : public Node 1075 { 1076 public: 1077 template <typename CrowApp> 1078 LDAPCertificate(CrowApp &app) : 1079 Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/", 1080 std::string()) 1081 { 1082 entityPrivileges = { 1083 {boost::beast::http::verb::get, {{"Login"}}}, 1084 {boost::beast::http::verb::head, {{"Login"}}}, 1085 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1086 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1087 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1088 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1089 } 1090 1091 void doGet(crow::Response &res, const crow::Request &req, 1092 const std::vector<std::string> ¶ms) override 1093 { 1094 auto asyncResp = std::make_shared<AsyncResp>(res); 1095 long id = getIDFromURL(req.url); 1096 if (id < 0) 1097 { 1098 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1099 messages::internalError(asyncResp->res); 1100 return; 1101 } 1102 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id); 1103 std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" + 1104 std::to_string(id); 1105 std::string objectPath = certs::ldapObjectPath; 1106 objectPath += "/"; 1107 objectPath += std::to_string(id); 1108 getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName, 1109 id, certURL, "LDAP Certificate"); 1110 } 1111 }; // LDAPCertificate 1112 } // namespace redfish 1113