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 if (boost::starts_with( 302 certURI, "/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 time 323 // consumed in generating private key 324 if (*optKeyPairAlgorithm == "RSA" && 325 *optKeyBitLength != RSA_KEY_BIT_LENGTH) 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 (boost::starts_with( 335 certURI, 336 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates")) 337 { 338 if (optKeyUsage->size() == 0) 339 { 340 optKeyUsage->push_back("ServerAuthentication"); 341 } 342 else if (optKeyUsage->size() == 1) 343 { 344 if ((*optKeyUsage)[0] != "ServerAuthentication") 345 { 346 messages::propertyValueNotInList( 347 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 348 return; 349 } 350 } 351 else 352 { 353 messages::actionParameterNotSupported( 354 asyncResp->res, "KeyUsage", "GenerateCSR"); 355 return; 356 } 357 } 358 else if (boost::starts_with( 359 certURI, "/redfish/v1/AccountService/LDAP/Certificates")) 360 { 361 if (optKeyUsage->size() == 0) 362 { 363 optKeyUsage->push_back("ClientAuthentication"); 364 } 365 else if (optKeyUsage->size() == 1) 366 { 367 if ((*optKeyUsage)[0] != "ClientAuthentication") 368 { 369 messages::propertyValueNotInList( 370 asyncResp->res, (*optKeyUsage)[0], "KeyUsage"); 371 return; 372 } 373 } 374 else 375 { 376 messages::actionParameterNotSupported( 377 asyncResp->res, "KeyUsage", "GenerateCSR"); 378 return; 379 } 380 } 381 382 // Only allow one CSR matcher at a time so setting retry time-out and 383 // timer expiry to 10 seconds for now. 384 static const int TIME_OUT = 10; 385 if (csrMatcher) 386 { 387 res.addHeader("Retry-After", std::to_string(TIME_OUT)); 388 messages::serviceTemporarilyUnavailable(asyncResp->res, 389 std::to_string(TIME_OUT)); 390 return; 391 } 392 393 // Make this static so it survives outside this method 394 static boost::asio::steady_timer timeout(*req.ioService); 395 timeout.expires_after(std::chrono::seconds(TIME_OUT)); 396 timeout.async_wait([asyncResp](const boost::system::error_code &ec) { 397 csrMatcher = nullptr; 398 if (ec) 399 { 400 // operation_aborted is expected if timer is canceled before 401 // completion. 402 if (ec != boost::asio::error::operation_aborted) 403 { 404 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 405 } 406 return; 407 } 408 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR"; 409 messages::internalError(asyncResp->res); 410 }); 411 412 // create a matcher to wait on CSR object 413 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath; 414 std::string match("type='signal'," 415 "interface='org.freedesktop.DBus.ObjectManager'," 416 "path='" + 417 objectPath + 418 "'," 419 "member='InterfacesAdded'"); 420 csrMatcher = std::make_unique<sdbusplus::bus::match::match>( 421 *crow::connections::systemBus, match, 422 [asyncResp, service, objectPath, 423 certURI](sdbusplus::message::message &m) { 424 boost::system::error_code ec; 425 timeout.cancel(ec); 426 if (ec) 427 { 428 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 429 csrMatcher = nullptr; 430 } 431 if (m.is_method_error()) 432 { 433 BMCWEB_LOG_ERROR << "Dbus method error!!!"; 434 messages::internalError(asyncResp->res); 435 return; 436 } 437 std::vector<std::pair< 438 std::string, std::vector<std::pair< 439 std::string, std::variant<std::string>>>>> 440 interfacesProperties; 441 sdbusplus::message::object_path csrObjectPath; 442 m.read(csrObjectPath, interfacesProperties); 443 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str; 444 for (auto &interface : interfacesProperties) 445 { 446 if (interface.first == "xyz.openbmc_project.Certs.CSR") 447 { 448 getCSR(asyncResp, certURI, service, objectPath, 449 csrObjectPath.str); 450 break; 451 } 452 } 453 }); 454 crow::connections::systemBus->async_method_call( 455 [asyncResp](const boost::system::error_code ec, 456 const std::string &path) { 457 if (ec) 458 { 459 BMCWEB_LOG_ERROR << "DBUS response error: " << ec.message(); 460 messages::internalError(asyncResp->res); 461 return; 462 } 463 }, 464 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create", 465 "GenerateCSR", *optAlternativeNames, *optChallengePassword, city, 466 commonName, *optContactPerson, country, *optEmail, *optGivenName, 467 *optInitials, *optKeyBitLength, *optKeyCurveId, 468 *optKeyPairAlgorithm, *optKeyUsage, organization, 469 organizationalUnit, state, *optSurname, *optUnstructuredName); 470 } 471 }; // CertificateActionGenerateCSR 472 473 /** 474 * @brief Parse and update Certficate Issue/Subject property 475 * 476 * @param[in] asyncResp Shared pointer to the response message 477 * @param[in] str Issuer/Subject value in key=value pairs 478 * @param[in] type Issuer/Subject 479 * @return None 480 */ 481 static void updateCertIssuerOrSubject(nlohmann::json &out, 482 const std::string_view value) 483 { 484 // example: O=openbmc-project.xyz,CN=localhost 485 std::string_view::iterator i = value.begin(); 486 while (i != value.end()) 487 { 488 std::string_view::iterator tokenBegin = i; 489 while (i != value.end() && *i != '=') 490 { 491 i++; 492 } 493 if (i == value.end()) 494 { 495 break; 496 } 497 const std::string_view key(tokenBegin, i - tokenBegin); 498 i++; 499 tokenBegin = i; 500 while (i != value.end() && *i != ',') 501 { 502 i++; 503 } 504 const std::string_view val(tokenBegin, i - tokenBegin); 505 if (key == "L") 506 { 507 out["City"] = val; 508 } 509 else if (key == "CN") 510 { 511 out["CommonName"] = val; 512 } 513 else if (key == "C") 514 { 515 out["Country"] = val; 516 } 517 else if (key == "O") 518 { 519 out["Organization"] = val; 520 } 521 else if (key == "OU") 522 { 523 out["OrganizationalUnit"] = val; 524 } 525 else if (key == "ST") 526 { 527 out["State"] = val; 528 } 529 // skip comma character 530 if (i != value.end()) 531 { 532 i++; 533 } 534 } 535 } 536 537 /** 538 * @brief Retrieve the certificates properties and append to the response 539 * message 540 * 541 * @param[in] asyncResp Shared pointer to the response message 542 * @param[in] objectPath Path of the D-Bus service object 543 * @param[in] certId Id of the certificate 544 * @param[in] certURL URL of the certificate object 545 * @param[in] name name of the certificate 546 * @return None 547 */ 548 static void getCertificateProperties( 549 const std::shared_ptr<AsyncResp> &asyncResp, const std::string &objectPath, 550 const std::string &service, long certId, const std::string &certURL, 551 const std::string &name) 552 { 553 using PropertyType = 554 std::variant<std::string, uint64_t, std::vector<std::string>>; 555 using PropertiesMap = boost::container::flat_map<std::string, PropertyType>; 556 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath 557 << " certId=" << certId << " certURl=" << certURL; 558 crow::connections::systemBus->async_method_call( 559 [asyncResp, certURL, certId, name](const boost::system::error_code ec, 560 const PropertiesMap &properties) { 561 if (ec) 562 { 563 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 564 messages::resourceNotFound(asyncResp->res, name, 565 std::to_string(certId)); 566 return; 567 } 568 asyncResp->res.jsonValue = { 569 {"@odata.id", certURL}, 570 {"@odata.type", "#Certificate.v1_0_0.Certificate"}, 571 {"@odata.context", 572 "/redfish/v1/$metadata#Certificate.Certificate"}, 573 {"Id", std::to_string(certId)}, 574 {"Name", name}, 575 {"Description", name}}; 576 for (const auto &property : properties) 577 { 578 if (property.first == "CertificateString") 579 { 580 asyncResp->res.jsonValue["CertificateString"] = ""; 581 const std::string *value = 582 std::get_if<std::string>(&property.second); 583 if (value) 584 { 585 asyncResp->res.jsonValue["CertificateString"] = *value; 586 } 587 } 588 else if (property.first == "KeyUsage") 589 { 590 nlohmann::json &keyUsage = 591 asyncResp->res.jsonValue["KeyUsage"]; 592 keyUsage = nlohmann::json::array(); 593 const std::vector<std::string> *value = 594 std::get_if<std::vector<std::string>>(&property.second); 595 if (value) 596 { 597 for (const std::string &usage : *value) 598 { 599 keyUsage.push_back(usage); 600 } 601 } 602 } 603 else if (property.first == "Issuer") 604 { 605 const std::string *value = 606 std::get_if<std::string>(&property.second); 607 if (value) 608 { 609 updateCertIssuerOrSubject( 610 asyncResp->res.jsonValue["Issuer"], *value); 611 } 612 } 613 else if (property.first == "Subject") 614 { 615 const std::string *value = 616 std::get_if<std::string>(&property.second); 617 if (value) 618 { 619 updateCertIssuerOrSubject( 620 asyncResp->res.jsonValue["Subject"], *value); 621 } 622 } 623 else if (property.first == "ValidNotAfter") 624 { 625 const uint64_t *value = 626 std::get_if<uint64_t>(&property.second); 627 if (value) 628 { 629 std::time_t time = static_cast<std::time_t>(*value); 630 asyncResp->res.jsonValue["ValidNotAfter"] = 631 crow::utility::getDateTime(time); 632 } 633 } 634 else if (property.first == "ValidNotBefore") 635 { 636 const uint64_t *value = 637 std::get_if<uint64_t>(&property.second); 638 if (value) 639 { 640 std::time_t time = static_cast<std::time_t>(*value); 641 asyncResp->res.jsonValue["ValidNotBefore"] = 642 crow::utility::getDateTime(time); 643 } 644 } 645 } 646 asyncResp->res.addHeader("Location", certURL); 647 }, 648 service, objectPath, certs::dbusPropIntf, "GetAll", 649 certs::certPropIntf); 650 } 651 652 using GetObjectType = 653 std::vector<std::pair<std::string, std::vector<std::string>>>; 654 655 /** 656 * Action to replace an existing certificate 657 */ 658 class CertificateActionsReplaceCertificate : public Node 659 { 660 public: 661 CertificateActionsReplaceCertificate(CrowApp &app) : 662 Node(app, "/redfish/v1/CertificateService/Actions/" 663 "CertificateService.ReplaceCertificate/") 664 { 665 entityPrivileges = { 666 {boost::beast::http::verb::get, {{"Login"}}}, 667 {boost::beast::http::verb::head, {{"Login"}}}, 668 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 669 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 670 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 671 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 672 } 673 674 private: 675 void doPost(crow::Response &res, const crow::Request &req, 676 const std::vector<std::string> ¶ms) override 677 { 678 std::string certificate; 679 nlohmann::json certificateUri; 680 std::optional<std::string> certificateType = "PEM"; 681 auto asyncResp = std::make_shared<AsyncResp>(res); 682 if (!json_util::readJson(req, asyncResp->res, "CertificateString", 683 certificate, "CertificateUri", certificateUri, 684 "CertificateType", certificateType)) 685 { 686 BMCWEB_LOG_ERROR << "Required parameters are missing"; 687 messages::internalError(asyncResp->res); 688 return; 689 } 690 691 if (!certificateType) 692 { 693 // should never happen, but it never hurts to be paranoid. 694 return; 695 } 696 if (certificateType != "PEM") 697 { 698 messages::actionParameterNotSupported( 699 asyncResp->res, "CertificateType", "ReplaceCertificate"); 700 return; 701 } 702 703 std::string certURI; 704 if (!redfish::json_util::readJson(certificateUri, asyncResp->res, 705 "@odata.id", certURI)) 706 { 707 messages::actionParameterMissing( 708 asyncResp->res, "ReplaceCertificate", "CertificateUri"); 709 return; 710 } 711 712 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI; 713 long id = getIDFromURL(certURI); 714 if (id < 0) 715 { 716 messages::actionParameterValueFormatError(asyncResp->res, certURI, 717 "CertificateUri", 718 "ReplaceCertificate"); 719 return; 720 } 721 std::string objectPath; 722 std::string name; 723 std::string service; 724 if (boost::starts_with( 725 certURI, 726 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")) 727 { 728 objectPath = 729 std::string(certs::httpsObjectPath) + "/" + std::to_string(id); 730 name = "HTTPS certificate"; 731 service = certs::httpsServiceName; 732 } 733 else if (boost::starts_with( 734 certURI, "/redfish/v1/AccountService/LDAP/Certificates/")) 735 { 736 objectPath = 737 std::string(certs::ldapObjectPath) + "/" + std::to_string(id); 738 name = "LDAP certificate"; 739 service = certs::ldapServiceName; 740 } 741 else 742 { 743 messages::actionParameterNotSupported( 744 asyncResp->res, "CertificateUri", "ReplaceCertificate"); 745 return; 746 } 747 748 std::shared_ptr<CertificateFile> certFile = 749 std::make_shared<CertificateFile>(certificate); 750 crow::connections::systemBus->async_method_call( 751 [asyncResp, certFile, objectPath, service, certURI, id, 752 name](const boost::system::error_code ec) { 753 if (ec) 754 { 755 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 756 messages::resourceNotFound(asyncResp->res, name, 757 std::to_string(id)); 758 return; 759 } 760 getCertificateProperties(asyncResp, objectPath, service, id, 761 certURI, name); 762 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 763 << certFile->getCertFilePath(); 764 }, 765 service, objectPath, certs::certReplaceIntf, "Replace", 766 certFile->getCertFilePath()); 767 } 768 }; // CertificateActionsReplaceCertificate 769 770 /** 771 * Certificate resource describes a certificate used to prove the identity 772 * of a component, account or service. 773 */ 774 class HTTPSCertificate : public Node 775 { 776 public: 777 template <typename CrowApp> 778 HTTPSCertificate(CrowApp &app) : 779 Node(app, 780 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" 781 "<str>/", 782 std::string()) 783 { 784 entityPrivileges = { 785 {boost::beast::http::verb::get, {{"Login"}}}, 786 {boost::beast::http::verb::head, {{"Login"}}}, 787 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 788 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 789 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 790 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 791 } 792 793 void doGet(crow::Response &res, const crow::Request &req, 794 const std::vector<std::string> ¶ms) override 795 { 796 auto asyncResp = std::make_shared<AsyncResp>(res); 797 if (params.size() != 1) 798 { 799 messages::internalError(asyncResp->res); 800 return; 801 } 802 long id = getIDFromURL(req.url); 803 804 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID=" << std::to_string(id); 805 std::string certURL = 806 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" + 807 std::to_string(id); 808 std::string objectPath = certs::httpsObjectPath; 809 objectPath += "/"; 810 objectPath += std::to_string(id); 811 getCertificateProperties(asyncResp, objectPath, certs::httpsServiceName, 812 id, certURL, "HTTPS Certificate"); 813 } 814 815 }; // namespace redfish 816 817 /** 818 * Collection of HTTPS certificates 819 */ 820 class HTTPSCertificateCollection : public Node 821 { 822 public: 823 template <typename CrowApp> 824 HTTPSCertificateCollection(CrowApp &app) : 825 Node(app, 826 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/") 827 { 828 entityPrivileges = { 829 {boost::beast::http::verb::get, {{"Login"}}}, 830 {boost::beast::http::verb::head, {{"Login"}}}, 831 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 832 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 833 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 834 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 835 } 836 void doGet(crow::Response &res, const crow::Request &req, 837 const std::vector<std::string> ¶ms) override 838 { 839 res.jsonValue = { 840 {"@odata.id", 841 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"}, 842 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 843 {"@odata.context", 844 "/redfish/v1/" 845 "$metadata#CertificateCollection.CertificateCollection"}, 846 {"Name", "HTTPS Certificates Collection"}, 847 {"Description", "A Collection of HTTPS certificate instances"}}; 848 auto asyncResp = std::make_shared<AsyncResp>(res); 849 crow::connections::systemBus->async_method_call( 850 [asyncResp](const boost::system::error_code ec, 851 const ManagedObjectType &certs) { 852 if (ec) 853 { 854 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 855 messages::internalError(asyncResp->res); 856 return; 857 } 858 nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 859 members = nlohmann::json::array(); 860 for (const auto &cert : certs) 861 { 862 long id = getIDFromURL(cert.first.str); 863 if (id >= 0) 864 { 865 members.push_back( 866 {{"@odata.id", 867 "/redfish/v1/Managers/bmc/" 868 "NetworkProtocol/HTTPS/Certificates/" + 869 std::to_string(id)}}); 870 } 871 } 872 asyncResp->res.jsonValue["Members@odata.count"] = 873 members.size(); 874 }, 875 certs::httpsServiceName, certs::httpsObjectPath, 876 certs::dbusObjManagerIntf, "GetManagedObjects"); 877 } 878 879 void doPost(crow::Response &res, const crow::Request &req, 880 const std::vector<std::string> ¶ms) override 881 { 882 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost"; 883 auto asyncResp = std::make_shared<AsyncResp>(res); 884 asyncResp->res.jsonValue = {{"Name", "HTTPS Certificate"}, 885 {"Description", "HTTPS Certificate"}}; 886 887 std::shared_ptr<CertificateFile> certFile = 888 std::make_shared<CertificateFile>(req.body); 889 890 crow::connections::systemBus->async_method_call( 891 [asyncResp, certFile](const boost::system::error_code ec) { 892 if (ec) 893 { 894 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 895 messages::internalError(asyncResp->res); 896 return; 897 } 898 // TODO: Issue#84 supporting only 1 certificate 899 long certId = 1; 900 std::string certURL = 901 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/" 902 "Certificates/" + 903 std::to_string(certId); 904 std::string objectPath = std::string(certs::httpsObjectPath) + 905 "/" + std::to_string(certId); 906 getCertificateProperties(asyncResp, objectPath, 907 certs::httpsServiceName, certId, 908 certURL, "HTTPS Certificate"); 909 BMCWEB_LOG_DEBUG << "HTTPS certificate install file=" 910 << certFile->getCertFilePath(); 911 }, 912 certs::httpsServiceName, certs::httpsObjectPath, 913 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 914 } 915 }; // HTTPSCertificateCollection 916 917 /** 918 * The certificate location schema defines a resource that an administrator 919 * can use in order to locate all certificates installed on a given service. 920 */ 921 class CertificateLocations : public Node 922 { 923 public: 924 template <typename CrowApp> 925 CertificateLocations(CrowApp &app) : 926 Node(app, "/redfish/v1/CertificateService/CertificateLocations/") 927 { 928 entityPrivileges = { 929 {boost::beast::http::verb::get, {{"Login"}}}, 930 {boost::beast::http::verb::head, {{"Login"}}}, 931 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 932 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 933 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 934 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 935 } 936 937 private: 938 void doGet(crow::Response &res, const crow::Request &req, 939 const std::vector<std::string> ¶ms) override 940 { 941 res.jsonValue = { 942 {"@odata.id", 943 "/redfish/v1/CertificateService/CertificateLocations"}, 944 {"@odata.type", 945 "#CertificateLocations.v1_0_0.CertificateLocations"}, 946 {"@odata.context", 947 "/redfish/v1/$metadata#CertificateLocations.CertificateLocations"}, 948 {"Name", "Certificate Locations"}, 949 {"Id", "CertificateLocations"}, 950 {"Description", 951 "Defines a resource that an administrator can use in order to " 952 "locate all certificates installed on a given service"}}; 953 auto asyncResp = std::make_shared<AsyncResp>(res); 954 nlohmann::json &links = 955 asyncResp->res.jsonValue["Links"]["Certificates"]; 956 links = nlohmann::json::array(); 957 getCertificateLocations( 958 asyncResp, 959 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/", 960 certs::httpsObjectPath, certs::httpsServiceName); 961 getCertificateLocations(asyncResp, 962 "/redfish/v1/AccountService/LDAP/Certificates/", 963 certs::ldapObjectPath, certs::ldapServiceName); 964 } 965 /** 966 * @brief Retrieve the certificates installed list and append to the 967 * response 968 * 969 * @param[in] asyncResp Shared pointer to the response message 970 * @param[in] certURL Path of the certificate object 971 * @param[in] path Path of the D-Bus service object 972 * @return None 973 */ 974 void getCertificateLocations(std::shared_ptr<AsyncResp> &asyncResp, 975 const std::string &certURL, 976 const std::string &path, 977 const std::string &service) 978 { 979 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL 980 << " Path=" << path << " service= " << service; 981 crow::connections::systemBus->async_method_call( 982 [asyncResp, certURL](const boost::system::error_code ec, 983 const ManagedObjectType &certs) { 984 if (ec) 985 { 986 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 987 messages::internalError(asyncResp->res); 988 return; 989 } 990 nlohmann::json &links = 991 asyncResp->res.jsonValue["Links"]["Certificates"]; 992 for (auto &cert : certs) 993 { 994 long id = getIDFromURL(cert.first.str); 995 if (id >= 0) 996 { 997 links.push_back( 998 {{"@odata.id", certURL + std::to_string(id)}}); 999 } 1000 } 1001 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] = 1002 links.size(); 1003 }, 1004 service, path, certs::dbusObjManagerIntf, "GetManagedObjects"); 1005 } 1006 }; // CertificateLocations 1007 1008 /** 1009 * Collection of LDAP certificates 1010 */ 1011 class LDAPCertificateCollection : public Node 1012 { 1013 public: 1014 template <typename CrowApp> 1015 LDAPCertificateCollection(CrowApp &app) : 1016 Node(app, "/redfish/v1/AccountService/LDAP/Certificates/") 1017 { 1018 entityPrivileges = { 1019 {boost::beast::http::verb::get, {{"Login"}}}, 1020 {boost::beast::http::verb::head, {{"Login"}}}, 1021 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1022 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1023 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1024 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1025 } 1026 void doGet(crow::Response &res, const crow::Request &req, 1027 const std::vector<std::string> ¶ms) override 1028 { 1029 res.jsonValue = { 1030 {"@odata.id", "/redfish/v1/AccountService/LDAP/Certificates"}, 1031 {"@odata.type", "#CertificateCollection.CertificateCollection"}, 1032 {"@odata.context", 1033 "/redfish/v1/" 1034 "$metadata#CertificateCollection.CertificateCollection"}, 1035 {"Name", "LDAP Certificates Collection"}, 1036 {"Description", "A Collection of LDAP certificate instances"}}; 1037 auto asyncResp = std::make_shared<AsyncResp>(res); 1038 crow::connections::systemBus->async_method_call( 1039 [asyncResp](const boost::system::error_code ec, 1040 const ManagedObjectType &certs) { 1041 if (ec) 1042 { 1043 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1044 messages::internalError(asyncResp->res); 1045 return; 1046 } 1047 nlohmann::json &members = asyncResp->res.jsonValue["Members"]; 1048 members = nlohmann::json::array(); 1049 for (const auto &cert : certs) 1050 { 1051 long id = getIDFromURL(cert.first.str); 1052 if (id >= 0) 1053 { 1054 members.push_back( 1055 {{"@odata.id", "/redfish/v1/AccountService/" 1056 "LDAP/Certificates/" + 1057 std::to_string(id)}}); 1058 } 1059 } 1060 asyncResp->res.jsonValue["Members@odata.count"] = 1061 members.size(); 1062 }, 1063 certs::ldapServiceName, certs::ldapObjectPath, 1064 certs::dbusObjManagerIntf, "GetManagedObjects"); 1065 } 1066 1067 void doPost(crow::Response &res, const crow::Request &req, 1068 const std::vector<std::string> ¶ms) override 1069 { 1070 std::shared_ptr<CertificateFile> certFile = 1071 std::make_shared<CertificateFile>(req.body); 1072 auto asyncResp = std::make_shared<AsyncResp>(res); 1073 crow::connections::systemBus->async_method_call( 1074 [asyncResp, certFile](const boost::system::error_code ec) { 1075 if (ec) 1076 { 1077 BMCWEB_LOG_ERROR << "DBUS response error: " << ec; 1078 messages::internalError(asyncResp->res); 1079 return; 1080 } 1081 //// TODO: Issue#84 supporting only 1 certificate 1082 long certId = 1; 1083 std::string certURL = 1084 "/redfish/v1/AccountService/LDAP/Certificates/" + 1085 std::to_string(certId); 1086 std::string objectPath = std::string(certs::ldapObjectPath) + 1087 "/" + std::to_string(certId); 1088 getCertificateProperties(asyncResp, objectPath, 1089 certs::ldapServiceName, certId, 1090 certURL, "LDAP Certificate"); 1091 BMCWEB_LOG_DEBUG << "LDAP certificate install file=" 1092 << certFile->getCertFilePath(); 1093 }, 1094 certs::ldapServiceName, certs::ldapObjectPath, 1095 certs::certInstallIntf, "Install", certFile->getCertFilePath()); 1096 } 1097 }; // LDAPCertificateCollection 1098 1099 /** 1100 * Certificate resource describes a certificate used to prove the identity 1101 * of a component, account or service. 1102 */ 1103 class LDAPCertificate : public Node 1104 { 1105 public: 1106 template <typename CrowApp> 1107 LDAPCertificate(CrowApp &app) : 1108 Node(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/", 1109 std::string()) 1110 { 1111 entityPrivileges = { 1112 {boost::beast::http::verb::get, {{"Login"}}}, 1113 {boost::beast::http::verb::head, {{"Login"}}}, 1114 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 1115 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 1116 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 1117 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 1118 } 1119 1120 void doGet(crow::Response &res, const crow::Request &req, 1121 const std::vector<std::string> ¶ms) override 1122 { 1123 auto asyncResp = std::make_shared<AsyncResp>(res); 1124 long id = getIDFromURL(req.url); 1125 if (id < 0) 1126 { 1127 BMCWEB_LOG_ERROR << "Invalid url value" << req.url; 1128 messages::internalError(asyncResp->res); 1129 return; 1130 } 1131 BMCWEB_LOG_DEBUG << "LDAP Certificate ID=" << std::to_string(id); 1132 std::string certURL = "/redfish/v1/AccountService/LDAP/Certificates/" + 1133 std::to_string(id); 1134 std::string objectPath = certs::ldapObjectPath; 1135 objectPath += "/"; 1136 objectPath += std::to_string(id); 1137 getCertificateProperties(asyncResp, objectPath, certs::ldapServiceName, 1138 id, certURL, "LDAP Certificate"); 1139 } 1140 }; // LDAPCertificate 1141 } // namespace redfish 1142