blob: 4ac3762d0f8dfc0e891931b98ee61e10b0c1d815 [file] [log] [blame]
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001#pragma once
2
John Edward Broadbent7e860f12021-04-08 15:57:16 -07003#include <app.hpp>
Iwona Klimaszewskae6604b12019-10-23 00:52:55 +02004#include <boost/convert.hpp>
5#include <boost/convert/strtol.hpp>
Jiaqing Zhao90d2d1e2022-04-13 17:01:57 +08006#include <boost/system/linux_error.hpp>
Ed Tanous168e20c2021-12-13 14:39:53 -08007#include <dbus_utility.hpp>
Ed Tanous45ca1b82022-03-25 13:07:27 -07008#include <query.hpp>
Ed Tanoused398212021-06-09 17:05:54 -07009#include <registries/privilege_registry.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -050010
Marri Devender Rao5968cae2019-01-21 10:27:12 -060011namespace redfish
12{
13namespace certs
14{
Gunnar Mills1214b7e2020-06-04 10:11:30 -050015constexpr char const* httpsObjectPath =
Marri Devender Rao5968cae2019-01-21 10:27:12 -060016 "/xyz/openbmc_project/certs/server/https";
Gunnar Mills1214b7e2020-06-04 10:11:30 -050017constexpr char const* certInstallIntf = "xyz.openbmc_project.Certs.Install";
18constexpr char const* certReplaceIntf = "xyz.openbmc_project.Certs.Replace";
19constexpr char const* objDeleteIntf = "xyz.openbmc_project.Object.Delete";
20constexpr char const* certPropIntf = "xyz.openbmc_project.Certs.Certificate";
21constexpr char const* dbusPropIntf = "org.freedesktop.DBus.Properties";
22constexpr char const* dbusObjManagerIntf = "org.freedesktop.DBus.ObjectManager";
23constexpr char const* ldapObjectPath = "/xyz/openbmc_project/certs/client/ldap";
24constexpr char const* httpsServiceName =
Marri Devender Rao37cce912019-02-20 01:05:22 -060025 "xyz.openbmc_project.Certs.Manager.Server.Https";
Gunnar Mills1214b7e2020-06-04 10:11:30 -050026constexpr char const* ldapServiceName =
Marri Devender Rao37cce912019-02-20 01:05:22 -060027 "xyz.openbmc_project.Certs.Manager.Client.Ldap";
Gunnar Mills1214b7e2020-06-04 10:11:30 -050028constexpr char const* authorityServiceName =
Marri Devender Raocfcd5f62019-05-17 08:34:37 -050029 "xyz.openbmc_project.Certs.Manager.Authority.Ldap";
Gunnar Mills1214b7e2020-06-04 10:11:30 -050030constexpr char const* authorityObjectPath =
Marri Devender Raocfcd5f62019-05-17 08:34:37 -050031 "/xyz/openbmc_project/certs/authority/ldap";
Marri Devender Rao5968cae2019-01-21 10:27:12 -060032} // namespace certs
33
34/**
35 * The Certificate schema defines a Certificate Service which represents the
36 * actions available to manage certificates and links to where certificates
37 * are installed.
38 */
Marri Devender Rao5968cae2019-01-21 10:27:12 -060039
John Edward Broadbent7e860f12021-04-08 15:57:16 -070040// TODO: Issue#61 No entries are available for Certificate
41// service at https://www.dmtf.org/standards/redfish
42// "redfish standard registries". Need to modify after DMTF
43// publish Privilege details for certificate service
44
45inline void requestRoutesCertificateService(App& app)
46{
47 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/")
Ed Tanoused398212021-06-09 17:05:54 -070048 .privileges(redfish::privileges::getCertificateService)
Ed Tanous45ca1b82022-03-25 13:07:27 -070049 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
50 const std::shared_ptr<
51 bmcweb::AsyncResp>&
52 asyncResp) {
53 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
54 {
55 return;
56 }
Ed Tanous14766872022-03-15 10:44:42 -070057
58 asyncResp->res.jsonValue["@odata.type"] =
59 "#CertificateService.v1_0_0.CertificateService";
60 asyncResp->res.jsonValue["@odata.id"] =
61 "/redfish/v1/CertificateService";
62 asyncResp->res.jsonValue["Id"] = "CertificateService";
63 asyncResp->res.jsonValue["Name"] = "Certificate Service";
64 asyncResp->res.jsonValue["Description"] =
65 "Actions available to manage certificates";
Abhishek Patel72048782021-06-02 09:53:24 -050066 // /redfish/v1/CertificateService/CertificateLocations is something
67 // only ConfigureManager can access then only display when the user
68 // has permissions ConfigureManager
69 Privileges effectiveUserPrivileges =
70 redfish::getUserPrivileges(req.userRole);
71 if (isOperationAllowedWithPrivileges({{"ConfigureManager"}},
72 effectiveUserPrivileges))
73 {
Ed Tanous14766872022-03-15 10:44:42 -070074 asyncResp->res.jsonValue["CertificateLocations"]["@odata.id"] =
75 "/redfish/v1/CertificateService/CertificateLocations";
Abhishek Patel72048782021-06-02 09:53:24 -050076 }
George Liu0fda0f12021-11-16 10:06:17 +080077 asyncResp->res
78 .jsonValue["Actions"]
79 ["#CertificateService.ReplaceCertificate"] = {
80 {"target",
81 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate"},
82 {"CertificateType@Redfish.AllowableValues", {"PEM"}}};
Abhishek Patel72048782021-06-02 09:53:24 -050083 asyncResp->res
84 .jsonValue["Actions"]["#CertificateService.GenerateCSR"] = {
George Liu0fda0f12021-11-16 10:06:17 +080085 {"target",
86 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR"}};
Abhishek Patel72048782021-06-02 09:53:24 -050087 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -070088} // requestRoutesCertificateService
Marri Devender Rao37cce912019-02-20 01:05:22 -060089
Marri Devender Rao5968cae2019-01-21 10:27:12 -060090/**
91 * @brief Find the ID specified in the URL
92 * Finds the numbers specified after the last "/" in the URL and returns.
93 * @param[in] path URL
94 * @return -1 on failure and number on success
95 */
Ed Tanous23a21a12020-07-25 04:45:05 +000096inline long getIDFromURL(const std::string_view url)
Marri Devender Rao5968cae2019-01-21 10:27:12 -060097{
Ed Tanousf23b7292020-10-15 09:41:17 -070098 std::size_t found = url.rfind('/');
Marri Devender Rao5968cae2019-01-21 10:27:12 -060099 if (found == std::string::npos)
100 {
101 return -1;
102 }
Iwona Klimaszewskae6604b12019-10-23 00:52:55 +0200103
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600104 if ((found + 1) < url.length())
105 {
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600106 std::string_view str = url.substr(found + 1);
Iwona Klimaszewskae6604b12019-10-23 00:52:55 +0200107
108 return boost::convert<long>(str, boost::cnv::strtol()).value_or(-1);
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600109 }
Iwona Klimaszewskae6604b12019-10-23 00:52:55 +0200110
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600111 return -1;
112}
113
zhanghch058d1b46d2021-04-01 11:18:24 +0800114inline std::string getCertificateFromReqBody(
115 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
116 const crow::Request& req)
Kowalski, Kamil58eb2382019-08-12 11:54:31 +0200117{
118 nlohmann::json reqJson = nlohmann::json::parse(req.body, nullptr, false);
119
120 if (reqJson.is_discarded())
121 {
122 // We did not receive JSON request, proceed as it is RAW data
123 return req.body;
124 }
125
126 std::string certificate;
127 std::optional<std::string> certificateType = "PEM";
128
Willy Tu15ed6782021-12-14 11:03:16 -0800129 if (!json_util::readJsonPatch(req, asyncResp->res, "CertificateString",
130 certificate, "CertificateType",
131 certificateType))
Kowalski, Kamil58eb2382019-08-12 11:54:31 +0200132 {
133 BMCWEB_LOG_ERROR << "Required parameters are missing";
134 messages::internalError(asyncResp->res);
Ed Tanousabb93cd2021-09-02 14:34:57 -0700135 return {};
Kowalski, Kamil58eb2382019-08-12 11:54:31 +0200136 }
137
138 if (*certificateType != "PEM")
139 {
140 messages::propertyValueNotInList(asyncResp->res, *certificateType,
141 "CertificateType");
Ed Tanousabb93cd2021-09-02 14:34:57 -0700142 return {};
Kowalski, Kamil58eb2382019-08-12 11:54:31 +0200143 }
144
145 return certificate;
146}
147
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600148/**
149 * Class to create a temporary certificate file for uploading to system
150 */
151class CertificateFile
152{
153 public:
154 CertificateFile() = delete;
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500155 CertificateFile(const CertificateFile&) = delete;
156 CertificateFile& operator=(const CertificateFile&) = delete;
157 CertificateFile(CertificateFile&&) = delete;
158 CertificateFile& operator=(CertificateFile&&) = delete;
159 CertificateFile(const std::string& certString)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600160 {
Ed Tanous72d52d22020-10-12 07:46:27 -0700161 std::array<char, 18> dirTemplate = {'/', 't', 'm', 'p', '/', 'C',
Ed Tanous52074382020-09-28 19:16:18 -0700162 'e', 'r', 't', 's', '.', 'X',
163 'X', 'X', 'X', 'X', 'X', '\0'};
164 char* tempDirectory = mkdtemp(dirTemplate.data());
Ed Tanouse662eae2022-01-25 10:39:19 -0800165 if (tempDirectory != nullptr)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600166 {
167 certDirectory = tempDirectory;
168 certificateFile = certDirectory / "cert.pem";
169 std::ofstream out(certificateFile, std::ofstream::out |
170 std::ofstream::binary |
171 std::ofstream::trunc);
172 out << certString;
173 out.close();
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800174 BMCWEB_LOG_DEBUG << "Creating certificate file"
175 << certificateFile.string();
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600176 }
177 }
178 ~CertificateFile()
179 {
180 if (std::filesystem::exists(certDirectory))
181 {
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800182 BMCWEB_LOG_DEBUG << "Removing certificate file"
183 << certificateFile.string();
Ed Tanous23a21a12020-07-25 04:45:05 +0000184 std::error_code ec;
185 std::filesystem::remove_all(certDirectory, ec);
186 if (ec)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600187 {
188 BMCWEB_LOG_ERROR << "Failed to remove temp directory"
Ed Tanous8cc8ede2022-02-28 10:20:59 -0800189 << certDirectory.string();
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600190 }
191 }
192 }
193 std::string getCertFilePath()
194 {
195 return certificateFile;
196 }
197
198 private:
199 std::filesystem::path certificateFile;
200 std::filesystem::path certDirectory;
201};
202
Marri Devender Rao30215812019-03-18 08:59:21 -0500203static std::unique_ptr<sdbusplus::bus::match::match> csrMatcher;
204/**
205 * @brief Read data from CSR D-bus object and set to response
206 *
207 * @param[in] asyncResp Shared pointer to the response message
208 * @param[in] certURI Link to certifiate collection URI
209 * @param[in] service D-Bus service name
210 * @param[in] certObjPath certificate D-Bus object path
211 * @param[in] csrObjPath CSR D-Bus object path
212 * @return None
213 */
zhanghch058d1b46d2021-04-01 11:18:24 +0800214static void getCSR(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500215 const std::string& certURI, const std::string& service,
216 const std::string& certObjPath,
217 const std::string& csrObjPath)
Marri Devender Rao30215812019-03-18 08:59:21 -0500218{
219 BMCWEB_LOG_DEBUG << "getCSR CertObjectPath" << certObjPath
220 << " CSRObjectPath=" << csrObjPath
221 << " service=" << service;
222 crow::connections::systemBus->async_method_call(
223 [asyncResp, certURI](const boost::system::error_code ec,
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500224 const std::string& csr) {
Marri Devender Rao30215812019-03-18 08:59:21 -0500225 if (ec)
226 {
227 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
228 messages::internalError(asyncResp->res);
229 return;
230 }
231 if (csr.empty())
232 {
233 BMCWEB_LOG_ERROR << "CSR read is empty";
234 messages::internalError(asyncResp->res);
235 return;
236 }
237 asyncResp->res.jsonValue["CSRString"] = csr;
Ed Tanous14766872022-03-15 10:44:42 -0700238 asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] =
239 certURI;
Marri Devender Rao30215812019-03-18 08:59:21 -0500240 },
241 service, csrObjPath, "xyz.openbmc_project.Certs.CSR", "CSR");
242}
243
244/**
245 * Action to Generate CSR
246 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700247inline void requestRoutesCertificateActionGenerateCSR(App& app)
Marri Devender Rao30215812019-03-18 08:59:21 -0500248{
George Liu0fda0f12021-11-16 10:06:17 +0800249 BMCWEB_ROUTE(
250 app,
251 "/redfish/v1/CertificateService/Actions/CertificateService.GenerateCSR/")
Abhishek Patel5344ab82021-07-31 17:42:09 -0500252 .privileges(redfish::privileges::postCertificateService)
George Liu0fda0f12021-11-16 10:06:17 +0800253 .methods(
254 boost::beast::http::verb::
Ed Tanous45ca1b82022-03-25 13:07:27 -0700255 post)([&app](
256 const crow::Request& req,
257 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
258 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
259 {
260 return;
261 }
George Liu0fda0f12021-11-16 10:06:17 +0800262 static const int rsaKeyBitLength = 2048;
Marri Devender Rao30215812019-03-18 08:59:21 -0500263
George Liu0fda0f12021-11-16 10:06:17 +0800264 // Required parameters
265 std::string city;
266 std::string commonName;
267 std::string country;
268 std::string organization;
269 std::string organizationalUnit;
270 std::string state;
271 nlohmann::json certificateCollection;
zhanghch058d1b46d2021-04-01 11:18:24 +0800272
George Liu0fda0f12021-11-16 10:06:17 +0800273 // Optional parameters
274 std::optional<std::vector<std::string>> optAlternativeNames =
275 std::vector<std::string>();
276 std::optional<std::string> optContactPerson = "";
277 std::optional<std::string> optChallengePassword = "";
278 std::optional<std::string> optEmail = "";
279 std::optional<std::string> optGivenName = "";
280 std::optional<std::string> optInitials = "";
281 std::optional<int64_t> optKeyBitLength = rsaKeyBitLength;
282 std::optional<std::string> optKeyCurveId = "secp384r1";
283 std::optional<std::string> optKeyPairAlgorithm = "EC";
284 std::optional<std::vector<std::string>> optKeyUsage =
285 std::vector<std::string>();
286 std::optional<std::string> optSurname = "";
287 std::optional<std::string> optUnstructuredName = "";
Willy Tu15ed6782021-12-14 11:03:16 -0800288 if (!json_util::readJsonAction(
George Liu0fda0f12021-11-16 10:06:17 +0800289 req, asyncResp->res, "City", city, "CommonName", commonName,
290 "ContactPerson", optContactPerson, "Country", country,
291 "Organization", organization, "OrganizationalUnit",
292 organizationalUnit, "State", state, "CertificateCollection",
293 certificateCollection, "AlternativeNames",
294 optAlternativeNames, "ChallengePassword",
295 optChallengePassword, "Email", optEmail, "GivenName",
296 optGivenName, "Initials", optInitials, "KeyBitLength",
297 optKeyBitLength, "KeyCurveId", optKeyCurveId,
298 "KeyPairAlgorithm", optKeyPairAlgorithm, "KeyUsage",
299 optKeyUsage, "Surname", optSurname, "UnstructuredName",
300 optUnstructuredName))
301 {
302 return;
303 }
304
305 // bmcweb has no way to store or decode a private key challenge
306 // password, which will likely cause bmcweb to crash on startup
307 // if this is not set on a post so not allowing the user to set
308 // value
Ed Tanous26f69762022-01-25 09:49:11 -0800309 if (!optChallengePassword->empty())
George Liu0fda0f12021-11-16 10:06:17 +0800310 {
311 messages::actionParameterNotSupported(
312 asyncResp->res, "GenerateCSR", "ChallengePassword");
313 return;
314 }
315
316 std::string certURI;
317 if (!redfish::json_util::readJson(certificateCollection,
318 asyncResp->res, "@odata.id",
319 certURI))
320 {
321 return;
322 }
323
324 std::string objectPath;
325 std::string service;
326 if (boost::starts_with(
327 certURI,
328 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
329 {
330 objectPath = certs::httpsObjectPath;
331 service = certs::httpsServiceName;
332 }
333 else if (boost::starts_with(
334 certURI,
335 "/redfish/v1/AccountService/LDAP/Certificates"))
336 {
337 objectPath = certs::ldapObjectPath;
338 service = certs::ldapServiceName;
339 }
340 else
341 {
342 messages::actionParameterNotSupported(
343 asyncResp->res, "CertificateCollection", "GenerateCSR");
344 return;
345 }
346
347 // supporting only EC and RSA algorithm
348 if (*optKeyPairAlgorithm != "EC" && *optKeyPairAlgorithm != "RSA")
349 {
350 messages::actionParameterNotSupported(
351 asyncResp->res, "KeyPairAlgorithm", "GenerateCSR");
352 return;
353 }
354
355 // supporting only 2048 key bit length for RSA algorithm due to
356 // time consumed in generating private key
357 if (*optKeyPairAlgorithm == "RSA" &&
358 *optKeyBitLength != rsaKeyBitLength)
359 {
360 messages::propertyValueNotInList(
361 asyncResp->res, std::to_string(*optKeyBitLength),
362 "KeyBitLength");
363 return;
364 }
365
366 // validate KeyUsage supporting only 1 type based on URL
367 if (boost::starts_with(
368 certURI,
369 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates"))
370 {
Ed Tanous26f69762022-01-25 09:49:11 -0800371 if (optKeyUsage->empty())
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700372 {
George Liu0fda0f12021-11-16 10:06:17 +0800373 optKeyUsage->push_back("ServerAuthentication");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700374 }
George Liu0fda0f12021-11-16 10:06:17 +0800375 else if (optKeyUsage->size() == 1)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700376 {
George Liu0fda0f12021-11-16 10:06:17 +0800377 if ((*optKeyUsage)[0] != "ServerAuthentication")
378 {
379 messages::propertyValueNotInList(
380 asyncResp->res, (*optKeyUsage)[0], "KeyUsage");
381 return;
382 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700383 }
384 else
385 {
386 messages::actionParameterNotSupported(
George Liu0fda0f12021-11-16 10:06:17 +0800387 asyncResp->res, "KeyUsage", "GenerateCSR");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700388 return;
389 }
George Liu0fda0f12021-11-16 10:06:17 +0800390 }
391 else if (boost::starts_with(
392 certURI,
393 "/redfish/v1/AccountService/LDAP/Certificates"))
394 {
Ed Tanous26f69762022-01-25 09:49:11 -0800395 if (optKeyUsage->empty())
George Liu0fda0f12021-11-16 10:06:17 +0800396 {
397 optKeyUsage->push_back("ClientAuthentication");
398 }
399 else if (optKeyUsage->size() == 1)
400 {
401 if ((*optKeyUsage)[0] != "ClientAuthentication")
402 {
403 messages::propertyValueNotInList(
404 asyncResp->res, (*optKeyUsage)[0], "KeyUsage");
405 return;
406 }
407 }
408 else
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700409 {
410 messages::actionParameterNotSupported(
George Liu0fda0f12021-11-16 10:06:17 +0800411 asyncResp->res, "KeyUsage", "GenerateCSR");
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700412 return;
413 }
George Liu0fda0f12021-11-16 10:06:17 +0800414 }
Marri Devender Rao30215812019-03-18 08:59:21 -0500415
George Liu0fda0f12021-11-16 10:06:17 +0800416 // Only allow one CSR matcher at a time so setting retry
417 // time-out and timer expiry to 10 seconds for now.
418 static const int timeOut = 10;
419 if (csrMatcher)
420 {
421 messages::serviceTemporarilyUnavailable(
422 asyncResp->res, std::to_string(timeOut));
423 return;
424 }
Marri Devender Rao30215812019-03-18 08:59:21 -0500425
George Liu0fda0f12021-11-16 10:06:17 +0800426 // Make this static so it survives outside this method
427 static boost::asio::steady_timer timeout(*req.ioService);
428 timeout.expires_after(std::chrono::seconds(timeOut));
429 timeout.async_wait(
430 [asyncResp](const boost::system::error_code& ec) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700431 csrMatcher = nullptr;
432 if (ec)
433 {
434 // operation_aborted is expected if timer is canceled
435 // before completion.
436 if (ec != boost::asio::error::operation_aborted)
437 {
438 BMCWEB_LOG_ERROR << "Async_wait failed " << ec;
439 }
440 return;
441 }
442 BMCWEB_LOG_ERROR << "Timed out waiting for Generating CSR";
443 messages::internalError(asyncResp->res);
444 });
445
George Liu0fda0f12021-11-16 10:06:17 +0800446 // create a matcher to wait on CSR object
447 BMCWEB_LOG_DEBUG << "create matcher with path " << objectPath;
448 std::string match("type='signal',"
449 "interface='org.freedesktop.DBus.ObjectManager',"
450 "path='" +
451 objectPath +
452 "',"
453 "member='InterfacesAdded'");
454 csrMatcher = std::make_unique<sdbusplus::bus::match::match>(
455 *crow::connections::systemBus, match,
456 [asyncResp, service, objectPath,
457 certURI](sdbusplus::message::message& m) {
458 timeout.cancel();
459 if (m.is_method_error())
460 {
461 BMCWEB_LOG_ERROR << "Dbus method error!!!";
462 messages::internalError(asyncResp->res);
463 return;
464 }
Ed Tanousb9d36b42022-02-26 21:42:46 -0800465
466 dbus::utility::DBusInteracesMap interfacesProperties;
467
George Liu0fda0f12021-11-16 10:06:17 +0800468 sdbusplus::message::object_path csrObjectPath;
469 m.read(csrObjectPath, interfacesProperties);
470 BMCWEB_LOG_DEBUG << "CSR object added" << csrObjectPath.str;
471 for (auto& interface : interfacesProperties)
472 {
473 if (interface.first == "xyz.openbmc_project.Certs.CSR")
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700474 {
George Liu0fda0f12021-11-16 10:06:17 +0800475 getCSR(asyncResp, certURI, service, objectPath,
476 csrObjectPath.str);
477 break;
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700478 }
George Liu0fda0f12021-11-16 10:06:17 +0800479 }
480 });
481 crow::connections::systemBus->async_method_call(
Ed Tanous914e2d52022-01-07 11:38:34 -0800482 [asyncResp](const boost::system::error_code ec,
George Liu0fda0f12021-11-16 10:06:17 +0800483 const std::string&) {
484 if (ec)
485 {
486 BMCWEB_LOG_ERROR << "DBUS response error: "
487 << ec.message();
488 messages::internalError(asyncResp->res);
489 return;
490 }
491 },
492 service, objectPath, "xyz.openbmc_project.Certs.CSR.Create",
493 "GenerateCSR", *optAlternativeNames, *optChallengePassword,
494 city, commonName, *optContactPerson, country, *optEmail,
495 *optGivenName, *optInitials, *optKeyBitLength, *optKeyCurveId,
496 *optKeyPairAlgorithm, *optKeyUsage, organization,
497 organizationalUnit, state, *optSurname, *optUnstructuredName);
498 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700499} // requestRoutesCertificateActionGenerateCSR
Marri Devender Rao30215812019-03-18 08:59:21 -0500500
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600501/**
Gunnar Mills4e0453b2020-07-08 14:00:30 -0500502 * @brief Parse and update Certificate Issue/Subject property
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600503 *
504 * @param[in] asyncResp Shared pointer to the response message
505 * @param[in] str Issuer/Subject value in key=value pairs
506 * @param[in] type Issuer/Subject
507 * @return None
508 */
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500509static void updateCertIssuerOrSubject(nlohmann::json& out,
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600510 const std::string_view value)
511{
512 // example: O=openbmc-project.xyz,CN=localhost
513 std::string_view::iterator i = value.begin();
514 while (i != value.end())
515 {
516 std::string_view::iterator tokenBegin = i;
517 while (i != value.end() && *i != '=')
518 {
Manojkiran Eda17a897d2020-09-12 15:31:58 +0530519 ++i;
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600520 }
521 if (i == value.end())
522 {
523 break;
524 }
Ed Tanous271584a2019-07-09 16:24:22 -0700525 const std::string_view key(tokenBegin,
526 static_cast<size_t>(i - tokenBegin));
Manojkiran Eda17a897d2020-09-12 15:31:58 +0530527 ++i;
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600528 tokenBegin = i;
529 while (i != value.end() && *i != ',')
530 {
Manojkiran Eda17a897d2020-09-12 15:31:58 +0530531 ++i;
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600532 }
Ed Tanous271584a2019-07-09 16:24:22 -0700533 const std::string_view val(tokenBegin,
534 static_cast<size_t>(i - tokenBegin));
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600535 if (key == "L")
536 {
537 out["City"] = val;
538 }
539 else if (key == "CN")
540 {
541 out["CommonName"] = val;
542 }
543 else if (key == "C")
544 {
545 out["Country"] = val;
546 }
547 else if (key == "O")
548 {
549 out["Organization"] = val;
550 }
551 else if (key == "OU")
552 {
553 out["OrganizationalUnit"] = val;
554 }
555 else if (key == "ST")
556 {
557 out["State"] = val;
558 }
559 // skip comma character
560 if (i != value.end())
561 {
Manojkiran Eda17a897d2020-09-12 15:31:58 +0530562 ++i;
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600563 }
564 }
565}
566
567/**
568 * @brief Retrieve the certificates properties and append to the response
569 * message
570 *
571 * @param[in] asyncResp Shared pointer to the response message
572 * @param[in] objectPath Path of the D-Bus service object
573 * @param[in] certId Id of the certificate
574 * @param[in] certURL URL of the certificate object
575 * @param[in] name name of the certificate
576 * @return None
577 */
578static void getCertificateProperties(
zhanghch058d1b46d2021-04-01 11:18:24 +0800579 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
580 const std::string& objectPath, const std::string& service, long certId,
581 const std::string& certURL, const std::string& name)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600582{
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600583 BMCWEB_LOG_DEBUG << "getCertificateProperties Path=" << objectPath
584 << " certId=" << certId << " certURl=" << certURL;
585 crow::connections::systemBus->async_method_call(
Ed Tanousb9d36b42022-02-26 21:42:46 -0800586 [asyncResp, certURL, certId,
587 name](const boost::system::error_code ec,
588 const dbus::utility::DBusPropertiesMap& properties) {
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600589 if (ec)
590 {
591 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
Marri Devender Rao8aae75a2019-07-16 03:26:50 -0500592 messages::resourceNotFound(asyncResp->res, name,
593 std::to_string(certId));
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600594 return;
595 }
Ed Tanous14766872022-03-15 10:44:42 -0700596 asyncResp->res.jsonValue["@odata.id"] = certURL;
597 asyncResp->res.jsonValue["@odata.type"] =
598 "#Certificate.v1_0_0.Certificate";
599 asyncResp->res.jsonValue["Id"] = std::to_string(certId);
600 asyncResp->res.jsonValue["Name"] = name;
601 asyncResp->res.jsonValue["Description"] = name;
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500602 for (const auto& property : properties)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600603 {
Marri Devender Rao37cce912019-02-20 01:05:22 -0600604 if (property.first == "CertificateString")
605 {
606 asyncResp->res.jsonValue["CertificateString"] = "";
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500607 const std::string* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600608 std::get_if<std::string>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800609 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600610 {
611 asyncResp->res.jsonValue["CertificateString"] = *value;
612 }
613 }
614 else if (property.first == "KeyUsage")
615 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500616 nlohmann::json& keyUsage =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600617 asyncResp->res.jsonValue["KeyUsage"];
618 keyUsage = nlohmann::json::array();
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500619 const std::vector<std::string>* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600620 std::get_if<std::vector<std::string>>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800621 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600622 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500623 for (const std::string& usage : *value)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600624 {
625 keyUsage.push_back(usage);
626 }
627 }
628 }
629 else if (property.first == "Issuer")
630 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500631 const std::string* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600632 std::get_if<std::string>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800633 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600634 {
635 updateCertIssuerOrSubject(
636 asyncResp->res.jsonValue["Issuer"], *value);
637 }
638 }
639 else if (property.first == "Subject")
640 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500641 const std::string* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600642 std::get_if<std::string>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800643 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600644 {
645 updateCertIssuerOrSubject(
646 asyncResp->res.jsonValue["Subject"], *value);
647 }
648 }
649 else if (property.first == "ValidNotAfter")
650 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500651 const uint64_t* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600652 std::get_if<uint64_t>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800653 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600654 {
Marri Devender Rao37cce912019-02-20 01:05:22 -0600655 asyncResp->res.jsonValue["ValidNotAfter"] =
Nan Zhou1d8782e2021-11-29 22:23:18 -0800656 crow::utility::getDateTimeUint(*value);
Marri Devender Rao37cce912019-02-20 01:05:22 -0600657 }
658 }
659 else if (property.first == "ValidNotBefore")
660 {
Gunnar Mills1214b7e2020-06-04 10:11:30 -0500661 const uint64_t* value =
Marri Devender Rao37cce912019-02-20 01:05:22 -0600662 std::get_if<uint64_t>(&property.second);
Ed Tanouse662eae2022-01-25 10:39:19 -0800663 if (value != nullptr)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600664 {
Marri Devender Rao37cce912019-02-20 01:05:22 -0600665 asyncResp->res.jsonValue["ValidNotBefore"] =
Nan Zhou1d8782e2021-11-29 22:23:18 -0800666 crow::utility::getDateTimeUint(*value);
Marri Devender Rao37cce912019-02-20 01:05:22 -0600667 }
668 }
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600669 }
Marri Devender Rao37cce912019-02-20 01:05:22 -0600670 asyncResp->res.addHeader("Location", certURL);
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600671 },
Marri Devender Rao37cce912019-02-20 01:05:22 -0600672 service, objectPath, certs::dbusPropIntf, "GetAll",
673 certs::certPropIntf);
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600674}
675
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600676/**
677 * Action to replace an existing certificate
678 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700679inline void requestRoutesCertificateActionsReplaceCertificate(App& app)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600680{
George Liu0fda0f12021-11-16 10:06:17 +0800681 BMCWEB_ROUTE(
682 app,
683 "/redfish/v1/CertificateService/Actions/CertificateService.ReplaceCertificate/")
Ed Tanoused398212021-06-09 17:05:54 -0700684 .privileges(redfish::privileges::postCertificateService)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700685 .methods(
686 boost::beast::http::verb::
Ed Tanous45ca1b82022-03-25 13:07:27 -0700687 post)([&app](
688 const crow::Request& req,
689 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
690 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
691 {
692 return;
693 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700694 std::string certificate;
695 nlohmann::json certificateUri;
696 std::optional<std::string> certificateType = "PEM";
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600697
Willy Tu15ed6782021-12-14 11:03:16 -0800698 if (!json_util::readJsonAction(req, asyncResp->res,
699 "CertificateString", certificate,
700 "CertificateUri", certificateUri,
701 "CertificateType", certificateType))
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700702 {
703 BMCWEB_LOG_ERROR << "Required parameters are missing";
704 messages::internalError(asyncResp->res);
705 return;
706 }
zhanghch058d1b46d2021-04-01 11:18:24 +0800707
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700708 if (!certificateType)
709 {
710 // should never happen, but it never hurts to be paranoid.
711 return;
712 }
713 if (certificateType != "PEM")
714 {
715 messages::actionParameterNotSupported(
716 asyncResp->res, "CertificateType", "ReplaceCertificate");
717 return;
718 }
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600719
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700720 std::string certURI;
721 if (!redfish::json_util::readJson(certificateUri, asyncResp->res,
722 "@odata.id", certURI))
723 {
724 messages::actionParameterMissing(
725 asyncResp->res, "ReplaceCertificate", "CertificateUri");
726 return;
727 }
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600728
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700729 BMCWEB_LOG_INFO << "Certificate URI to replace" << certURI;
730 long id = getIDFromURL(certURI);
731 if (id < 0)
732 {
733 messages::actionParameterValueFormatError(
734 asyncResp->res, certURI, "CertificateUri",
735 "ReplaceCertificate");
736 return;
737 }
738 std::string objectPath;
739 std::string name;
740 std::string service;
George Liu0fda0f12021-11-16 10:06:17 +0800741 if (boost::starts_with(
742 certURI,
743 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/"))
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700744 {
745 objectPath = std::string(certs::httpsObjectPath) + "/" +
746 std::to_string(id);
747 name = "HTTPS certificate";
748 service = certs::httpsServiceName;
749 }
750 else if (boost::starts_with(
751 certURI,
752 "/redfish/v1/AccountService/LDAP/Certificates/"))
753 {
754 objectPath = std::string(certs::ldapObjectPath) + "/" +
755 std::to_string(id);
756 name = "LDAP certificate";
757 service = certs::ldapServiceName;
758 }
759 else if (boost::starts_with(
760 certURI,
761 "/redfish/v1/Managers/bmc/Truststore/Certificates/"))
762 {
763 objectPath = std::string(certs::authorityObjectPath) + "/" +
764 std::to_string(id);
765 name = "TrustStore certificate";
766 service = certs::authorityServiceName;
767 }
768 else
769 {
770 messages::actionParameterNotSupported(
771 asyncResp->res, "CertificateUri", "ReplaceCertificate");
772 return;
773 }
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600774
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700775 std::shared_ptr<CertificateFile> certFile =
776 std::make_shared<CertificateFile>(certificate);
777 crow::connections::systemBus->async_method_call(
778 [asyncResp, certFile, objectPath, service, certURI, id,
779 name](const boost::system::error_code ec) {
780 if (ec)
781 {
782 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
Jiaqing Zhao90d2d1e2022-04-13 17:01:57 +0800783 if (ec.value() ==
784 boost::system::linux_error::bad_request_descriptor)
785 {
786 messages::resourceNotFound(asyncResp->res, name,
787 std::to_string(id));
788 return;
789 }
790 messages::internalError(asyncResp->res);
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700791 return;
792 }
793 getCertificateProperties(asyncResp, objectPath, service, id,
794 certURI, name);
795 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
796 << certFile->getCertFilePath();
797 },
798 service, objectPath, certs::certReplaceIntf, "Replace",
799 certFile->getCertFilePath());
800 });
801} // requestRoutesCertificateActionsReplaceCertificate
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600802
803/**
804 * Certificate resource describes a certificate used to prove the identity
805 * of a component, account or service.
806 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700807
808inline void requestRoutesHTTPSCertificate(App& app)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600809{
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700810 BMCWEB_ROUTE(
811 app,
812 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -0700813 .privileges(redfish::privileges::getCertificate)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700814 .methods(
815 boost::beast::http::verb::
Ed Tanous45ca1b82022-03-25 13:07:27 -0700816 get)([&app](const crow::Request& req,
817 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
818 const std::string& param) -> void {
819 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
820 {
821 return;
822 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700823 if (param.empty())
824 {
825 messages::internalError(asyncResp->res);
826 return;
827 }
828 long id = getIDFromURL(req.url);
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600829
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700830 BMCWEB_LOG_DEBUG << "HTTPSCertificate::doGet ID="
831 << std::to_string(id);
832 std::string certURL =
833 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
834 std::to_string(id);
835 std::string objectPath = certs::httpsObjectPath;
836 objectPath += "/";
837 objectPath += std::to_string(id);
838 getCertificateProperties(asyncResp, objectPath,
839 certs::httpsServiceName, id, certURL,
840 "HTTPS Certificate");
841 });
842}
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600843
844/**
845 * Collection of HTTPS certificates
846 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700847inline void requestRoutesHTTPSCertificateCollection(App& app)
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600848{
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700849 BMCWEB_ROUTE(app,
850 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -0700851 .privileges(redfish::privileges::getCertificateCollection)
Ed Tanous45ca1b82022-03-25 13:07:27 -0700852 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
853 const std::shared_ptr<
854 bmcweb::AsyncResp>&
855 asyncResp) {
856 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
857 {
858 return;
859 }
Ed Tanous14766872022-03-15 10:44:42 -0700860
861 asyncResp->res.jsonValue["@odata.id"] =
862 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates";
863 asyncResp->res.jsonValue["@odata.type"] =
864 "#CertificateCollection.CertificateCollection";
865 asyncResp->res.jsonValue["Name"] = "HTTPS Certificates Collection";
866 asyncResp->res.jsonValue["Description"] =
867 "A Collection of HTTPS certificate instances";
zhanghch058d1b46d2021-04-01 11:18:24 +0800868
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700869 crow::connections::systemBus->async_method_call(
870 [asyncResp](const boost::system::error_code ec,
Ed Tanous711ac7a2021-12-20 09:34:41 -0800871 const dbus::utility::ManagedObjectType& certs) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700872 if (ec)
Marri Devender Rao37cce912019-02-20 01:05:22 -0600873 {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700874 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
875 messages::internalError(asyncResp->res);
876 return;
Marri Devender Rao37cce912019-02-20 01:05:22 -0600877 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700878 nlohmann::json& members =
879 asyncResp->res.jsonValue["Members"];
880 members = nlohmann::json::array();
881 for (const auto& cert : certs)
882 {
883 long id = getIDFromURL(cert.first.str);
884 if (id >= 0)
885 {
Ed Tanous14766872022-03-15 10:44:42 -0700886 nlohmann::json::object_t member;
887 member["@odata.id"] =
888 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
889 std::to_string(id);
890 members.push_back(std::move(member));
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700891 }
892 }
893 asyncResp->res.jsonValue["Members@odata.count"] =
894 members.size();
895 },
896 certs::httpsServiceName, certs::httpsObjectPath,
897 certs::dbusObjManagerIntf, "GetManagedObjects");
898 });
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600899
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700900 BMCWEB_ROUTE(app,
901 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -0700902 .privileges(redfish::privileges::postCertificateCollection)
George Liu0fda0f12021-11-16 10:06:17 +0800903 .methods(
904 boost::beast::http::verb::
Ed Tanous45ca1b82022-03-25 13:07:27 -0700905 post)([&app](
906 const crow::Request& req,
907 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
908 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
909 {
910 return;
911 }
George Liu0fda0f12021-11-16 10:06:17 +0800912 BMCWEB_LOG_DEBUG << "HTTPSCertificateCollection::doPost";
zhanghch058d1b46d2021-04-01 11:18:24 +0800913
Ed Tanous14766872022-03-15 10:44:42 -0700914 asyncResp->res.jsonValue["Name"] = "HTTPS Certificate";
915 asyncResp->res.jsonValue["Description"] = "HTTPS Certificate";
Marri Devender Rao5968cae2019-01-21 10:27:12 -0600916
George Liu0fda0f12021-11-16 10:06:17 +0800917 std::string certFileBody =
918 getCertificateFromReqBody(asyncResp, req);
Kowalski, Kamil58eb2382019-08-12 11:54:31 +0200919
George Liu0fda0f12021-11-16 10:06:17 +0800920 if (certFileBody.empty())
921 {
922 BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
923 messages::unrecognizedRequestBody(asyncResp->res);
924 return;
925 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700926
George Liu0fda0f12021-11-16 10:06:17 +0800927 std::shared_ptr<CertificateFile> certFile =
928 std::make_shared<CertificateFile>(certFileBody);
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700929
George Liu0fda0f12021-11-16 10:06:17 +0800930 crow::connections::systemBus->async_method_call(
931 [asyncResp, certFile](const boost::system::error_code ec,
932 const std::string& objectPath) {
933 if (ec)
934 {
935 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
936 messages::internalError(asyncResp->res);
937 return;
938 }
939 long certId = getIDFromURL(objectPath);
940 if (certId < 0)
941 {
942 BMCWEB_LOG_ERROR << "Invalid objectPath value"
943 << objectPath;
944 messages::internalError(asyncResp->res);
945 return;
946 }
947 std::string certURL =
948 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/" +
949 std::to_string(certId);
950 getCertificateProperties(asyncResp, objectPath,
951 certs::httpsServiceName, certId,
952 certURL, "HTTPS Certificate");
953 BMCWEB_LOG_DEBUG << "HTTPS certificate install file="
954 << certFile->getCertFilePath();
955 },
956 certs::httpsServiceName, certs::httpsObjectPath,
957 certs::certInstallIntf, "Install", certFile->getCertFilePath());
958 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700959} // requestRoutesHTTPSCertificateCollection
960
961/**
962 * @brief Retrieve the certificates installed list and append to the
963 * response
964 *
965 * @param[in] asyncResp Shared pointer to the response message
966 * @param[in] certURL Path of the certificate object
967 * @param[in] path Path of the D-Bus service object
968 * @return None
969 */
Ed Tanous4f48d5f2021-06-21 08:27:45 -0700970inline void
971 getCertificateLocations(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
972 const std::string& certURL, const std::string& path,
973 const std::string& service)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700974{
975 BMCWEB_LOG_DEBUG << "getCertificateLocations URI=" << certURL
976 << " Path=" << path << " service= " << service;
977 crow::connections::systemBus->async_method_call(
978 [asyncResp, certURL](const boost::system::error_code ec,
Ed Tanous711ac7a2021-12-20 09:34:41 -0800979 const dbus::utility::ManagedObjectType& certs) {
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700980 if (ec)
981 {
982 BMCWEB_LOG_WARNING
983 << "Certificate collection query failed: " << ec
984 << ", skipping " << certURL;
985 return;
986 }
987 nlohmann::json& links =
988 asyncResp->res.jsonValue["Links"]["Certificates"];
Ed Tanous9eb808c2022-01-25 10:19:23 -0800989 for (const auto& cert : certs)
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700990 {
991 long id = getIDFromURL(cert.first.str);
992 if (id >= 0)
Zbigniew Kurzynski656ec7e2019-09-30 18:43:28 +0200993 {
Ed Tanous14766872022-03-15 10:44:42 -0700994 nlohmann::json::object_t link;
995 link["@odata.id"] = certURL + std::to_string(id);
996 links.push_back(std::move(link));
Zbigniew Kurzynski656ec7e2019-09-30 18:43:28 +0200997 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -0700998 }
999 asyncResp->res.jsonValue["Links"]["Certificates@odata.count"] =
1000 links.size();
1001 },
1002 service, path, certs::dbusObjManagerIntf, "GetManagedObjects");
1003}
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001004
1005/**
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001006 * The certificate location schema defines a resource that an administrator
1007 * can use in order to locate all certificates installed on a given service.
1008 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001009inline void requestRoutesCertificateLocations(App& app)
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001010{
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001011 BMCWEB_ROUTE(app, "/redfish/v1/CertificateService/CertificateLocations/")
Ed Tanoused398212021-06-09 17:05:54 -07001012 .privileges(redfish::privileges::getCertificateLocations)
Ed Tanous45ca1b82022-03-25 13:07:27 -07001013 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
1014 const std::shared_ptr<
1015 bmcweb::AsyncResp>&
1016 asyncResp) {
1017 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1018 {
1019 return;
1020 }
Ed Tanous14766872022-03-15 10:44:42 -07001021 asyncResp->res.jsonValue["@odata.id"] =
1022 "/redfish/v1/CertificateService/CertificateLocations";
1023 asyncResp->res.jsonValue["@odata.type"] =
1024 "#CertificateLocations.v1_0_0.CertificateLocations";
1025 asyncResp->res.jsonValue["Name"] = "Certificate Locations";
1026 asyncResp->res.jsonValue["Id"] = "CertificateLocations";
1027 asyncResp->res.jsonValue["Description"] =
1028 "Defines a resource that an administrator can use in order to "
1029 "locate all certificates installed on a given service";
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001030
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001031 nlohmann::json& links =
1032 asyncResp->res.jsonValue["Links"]["Certificates"];
1033 links = nlohmann::json::array();
1034 getCertificateLocations(
1035 asyncResp,
1036 "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates/",
1037 certs::httpsObjectPath, certs::httpsServiceName);
1038 getCertificateLocations(
1039 asyncResp, "/redfish/v1/AccountService/LDAP/Certificates/",
1040 certs::ldapObjectPath, certs::ldapServiceName);
1041 getCertificateLocations(
1042 asyncResp, "/redfish/v1/Managers/bmc/Truststore/Certificates/",
1043 certs::authorityObjectPath, certs::authorityServiceName);
1044 });
1045}
1046// requestRoutesCertificateLocations
Marri Devender Rao37cce912019-02-20 01:05:22 -06001047
1048/**
1049 * Collection of LDAP certificates
1050 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001051inline void requestRoutesLDAPCertificateCollection(App& app)
Marri Devender Rao37cce912019-02-20 01:05:22 -06001052{
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001053 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -07001054 .privileges(redfish::privileges::getCertificateCollection)
Ed Tanous45ca1b82022-03-25 13:07:27 -07001055 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
1056 const std::shared_ptr<
1057 bmcweb::AsyncResp>&
1058 asyncResp) {
1059 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1060 {
1061 return;
1062 }
Ed Tanous14766872022-03-15 10:44:42 -07001063
1064 asyncResp->res.jsonValue["@odata.id"] =
1065 "/redfish/v1/AccountService/LDAP/Certificates";
1066 asyncResp->res.jsonValue["@odata.type"] =
1067 "#CertificateCollection.CertificateCollection";
1068 asyncResp->res.jsonValue["Name"] = "LDAP Certificates Collection";
1069 asyncResp->res.jsonValue["Description"] =
1070 "A Collection of LDAP certificate instances";
zhanghch058d1b46d2021-04-01 11:18:24 +08001071
George Liu0fda0f12021-11-16 10:06:17 +08001072 crow::connections::systemBus->async_method_call(
1073 [asyncResp](const boost::system::error_code ec,
Ed Tanous711ac7a2021-12-20 09:34:41 -08001074 const dbus::utility::ManagedObjectType& certs) {
George Liu0fda0f12021-11-16 10:06:17 +08001075 nlohmann::json& members =
1076 asyncResp->res.jsonValue["Members"];
1077 nlohmann::json& count =
1078 asyncResp->res.jsonValue["Members@odata.count"];
1079 members = nlohmann::json::array();
1080 count = 0;
1081 if (ec)
1082 {
1083 BMCWEB_LOG_WARNING << "LDAP certificate query failed: "
1084 << ec;
1085 return;
1086 }
1087 for (const auto& cert : certs)
1088 {
1089 long id = getIDFromURL(cert.first.str);
1090 if (id >= 0)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001091 {
Ed Tanous14766872022-03-15 10:44:42 -07001092 nlohmann::json::object_t member;
1093 member["@odata.id"] =
1094 "/redfish/v1/AccountService/LDAP/Certificates/" +
1095 std::to_string(id);
1096 members.push_back(std::move(member));
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001097 }
George Liu0fda0f12021-11-16 10:06:17 +08001098 }
1099 count = members.size();
1100 },
1101 certs::ldapServiceName, certs::ldapObjectPath,
1102 certs::dbusObjManagerIntf, "GetManagedObjects");
1103 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001104
1105 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -07001106 .privileges(redfish::privileges::postCertificateCollection)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001107 .methods(boost::beast::http::verb::post)(
Ed Tanous45ca1b82022-03-25 13:07:27 -07001108 [&app](const crow::Request& req,
1109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1110 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1111 {
1112 return;
1113 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001114 std::string certFileBody =
1115 getCertificateFromReqBody(asyncResp, req);
1116
1117 if (certFileBody.empty())
Marri Devender Rao37cce912019-02-20 01:05:22 -06001118 {
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001119 BMCWEB_LOG_ERROR
1120 << "Cannot get certificate from request body.";
1121 messages::unrecognizedRequestBody(asyncResp->res);
Marri Devender Rao37cce912019-02-20 01:05:22 -06001122 return;
1123 }
Marri Devender Rao37cce912019-02-20 01:05:22 -06001124
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001125 std::shared_ptr<CertificateFile> certFile =
1126 std::make_shared<CertificateFile>(certFileBody);
zhanghch058d1b46d2021-04-01 11:18:24 +08001127
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001128 crow::connections::systemBus->async_method_call(
1129 [asyncResp, certFile](const boost::system::error_code ec,
1130 const std::string& objectPath) {
1131 if (ec)
1132 {
1133 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1134 messages::internalError(asyncResp->res);
1135 return;
1136 }
1137 long certId = getIDFromURL(objectPath);
1138 if (certId < 0)
1139 {
1140 BMCWEB_LOG_ERROR << "Invalid objectPath value"
1141 << objectPath;
1142 messages::internalError(asyncResp->res);
1143 return;
1144 }
1145 std::string certURL =
1146 "/redfish/v1/AccountService/LDAP/Certificates/" +
1147 std::to_string(certId);
1148 getCertificateProperties(asyncResp, objectPath,
1149 certs::ldapServiceName, certId,
1150 certURL, "LDAP Certificate");
1151 BMCWEB_LOG_DEBUG << "LDAP certificate install file="
1152 << certFile->getCertFilePath();
1153 },
1154 certs::ldapServiceName, certs::ldapObjectPath,
1155 certs::certInstallIntf, "Install",
1156 certFile->getCertFilePath());
1157 });
1158} // requestRoutesLDAPCertificateCollection
Marri Devender Rao37cce912019-02-20 01:05:22 -06001159
1160/**
1161 * Certificate resource describes a certificate used to prove the identity
1162 * of a component, account or service.
1163 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001164inline void requestRoutesLDAPCertificate(App& app)
Marri Devender Rao37cce912019-02-20 01:05:22 -06001165{
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001166 BMCWEB_ROUTE(app, "/redfish/v1/AccountService/LDAP/Certificates/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -07001167 .privileges(redfish::privileges::getCertificate)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001168 .methods(boost::beast::http::verb::get)(
Ed Tanous45ca1b82022-03-25 13:07:27 -07001169 [&app](const crow::Request& req,
1170 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1171 const std::string&) {
1172 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1173 {
1174 return;
1175 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001176 long id = getIDFromURL(req.url);
1177 if (id < 0)
1178 {
1179 BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1180 messages::internalError(asyncResp->res);
1181 return;
1182 }
1183 BMCWEB_LOG_DEBUG << "LDAP Certificate ID="
1184 << std::to_string(id);
1185 std::string certURL =
1186 "/redfish/v1/AccountService/LDAP/Certificates/" +
1187 std::to_string(id);
1188 std::string objectPath = certs::ldapObjectPath;
1189 objectPath += "/";
1190 objectPath += std::to_string(id);
1191 getCertificateProperties(asyncResp, objectPath,
1192 certs::ldapServiceName, id, certURL,
1193 "LDAP Certificate");
1194 });
1195} // requestRoutesLDAPCertificate
Marri Devender Raocfcd5f62019-05-17 08:34:37 -05001196/**
1197 * Collection of TrustStoreCertificate certificates
1198 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001199inline void requestRoutesTrustStoreCertificateCollection(App& app)
Marri Devender Raocfcd5f62019-05-17 08:34:37 -05001200{
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001201 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -07001202 .privileges(redfish::privileges::getCertificate)
Ed Tanous45ca1b82022-03-25 13:07:27 -07001203 .methods(boost::beast::http::verb::get)([&app](const crow::Request& req,
1204 const std::shared_ptr<
1205 bmcweb::AsyncResp>&
1206 asyncResp) {
1207 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1208 {
1209 return;
1210 }
Ed Tanous14766872022-03-15 10:44:42 -07001211
1212 asyncResp->res.jsonValue["@odata.id"] =
1213 "/redfish/v1/Managers/bmc/Truststore/Certificates/";
1214 asyncResp->res.jsonValue["@odata.type"] =
1215 "#CertificateCollection.CertificateCollection";
1216 asyncResp->res.jsonValue["Name"] =
1217 "TrustStore Certificates Collection";
1218 asyncResp->res.jsonValue["Description"] =
1219 "A Collection of TrustStore certificate instances";
zhanghch058d1b46d2021-04-01 11:18:24 +08001220
George Liu0fda0f12021-11-16 10:06:17 +08001221 crow::connections::systemBus->async_method_call(
1222 [asyncResp](const boost::system::error_code ec,
Ed Tanous711ac7a2021-12-20 09:34:41 -08001223 const dbus::utility::ManagedObjectType& certs) {
George Liu0fda0f12021-11-16 10:06:17 +08001224 if (ec)
1225 {
1226 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1227 messages::internalError(asyncResp->res);
1228 return;
1229 }
1230 nlohmann::json& members =
1231 asyncResp->res.jsonValue["Members"];
1232 members = nlohmann::json::array();
1233 for (const auto& cert : certs)
1234 {
1235 long id = getIDFromURL(cert.first.str);
1236 if (id >= 0)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001237 {
Ed Tanous14766872022-03-15 10:44:42 -07001238 nlohmann::json::object_t member;
1239 member["@odata.id"] =
1240 "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1241 std::to_string(id);
1242 members.push_back(std::move(member));
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001243 }
George Liu0fda0f12021-11-16 10:06:17 +08001244 }
1245 asyncResp->res.jsonValue["Members@odata.count"] =
1246 members.size();
1247 },
1248 certs::authorityServiceName, certs::authorityObjectPath,
1249 certs::dbusObjManagerIntf, "GetManagedObjects");
1250 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001251
1252 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/")
Ed Tanoused398212021-06-09 17:05:54 -07001253 .privileges(redfish::privileges::postCertificateCollection)
George Liu0fda0f12021-11-16 10:06:17 +08001254 .methods(
1255 boost::beast::http::verb::
Ed Tanous45ca1b82022-03-25 13:07:27 -07001256 post)([&app](
1257 const crow::Request& req,
1258 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
1259 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1260 {
1261 return;
1262 }
George Liu0fda0f12021-11-16 10:06:17 +08001263 std::string certFileBody =
1264 getCertificateFromReqBody(asyncResp, req);
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001265
George Liu0fda0f12021-11-16 10:06:17 +08001266 if (certFileBody.empty())
1267 {
1268 BMCWEB_LOG_ERROR << "Cannot get certificate from request body.";
1269 messages::unrecognizedRequestBody(asyncResp->res);
1270 return;
1271 }
Marri Devender Raocfcd5f62019-05-17 08:34:37 -05001272
George Liu0fda0f12021-11-16 10:06:17 +08001273 std::shared_ptr<CertificateFile> certFile =
1274 std::make_shared<CertificateFile>(certFileBody);
1275 crow::connections::systemBus->async_method_call(
1276 [asyncResp, certFile](const boost::system::error_code ec,
1277 const std::string& objectPath) {
1278 if (ec)
1279 {
1280 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
1281 messages::internalError(asyncResp->res);
1282 return;
1283 }
1284 long certId = getIDFromURL(objectPath);
1285 if (certId < 0)
1286 {
1287 BMCWEB_LOG_ERROR << "Invalid objectPath value"
1288 << objectPath;
1289 messages::internalError(asyncResp->res);
1290 return;
1291 }
1292 std::string certURL =
1293 "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1294 std::to_string(certId);
zhanghch058d1b46d2021-04-01 11:18:24 +08001295
George Liu0fda0f12021-11-16 10:06:17 +08001296 getCertificateProperties(
1297 asyncResp, objectPath, certs::authorityServiceName,
1298 certId, certURL, "TrustStore Certificate");
1299 BMCWEB_LOG_DEBUG << "TrustStore certificate install file="
1300 << certFile->getCertFilePath();
1301 },
1302 certs::authorityServiceName, certs::authorityObjectPath,
1303 certs::certInstallIntf, "Install", certFile->getCertFilePath());
1304 });
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001305} // requestRoutesTrustStoreCertificateCollection
Marri Devender Raocfcd5f62019-05-17 08:34:37 -05001306
1307/**
1308 * Certificate resource describes a certificate used to prove the identity
1309 * of a component, account or service.
1310 */
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001311inline void requestRoutesTrustStoreCertificate(App& app)
Marri Devender Raocfcd5f62019-05-17 08:34:37 -05001312{
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001313 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -07001314 .privileges(redfish::privileges::getCertificate)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001315 .methods(boost::beast::http::verb::get)(
Ed Tanous45ca1b82022-03-25 13:07:27 -07001316 [&app](const crow::Request& req,
1317 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1318 const std::string&) {
1319 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1320 {
1321 return;
1322 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001323 long id = getIDFromURL(req.url);
1324 if (id < 0)
Zbigniew Kurzynski07a60292019-09-17 15:56:16 +02001325 {
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001326 BMCWEB_LOG_ERROR << "Invalid url value" << req.url;
1327 messages::internalError(asyncResp->res);
Zbigniew Kurzynski07a60292019-09-17 15:56:16 +02001328 return;
1329 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001330 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doGet ID="
1331 << std::to_string(id);
1332 std::string certURL =
1333 "/redfish/v1/Managers/bmc/Truststore/Certificates/" +
1334 std::to_string(id);
1335 std::string objectPath = certs::authorityObjectPath;
1336 objectPath += "/";
1337 objectPath += std::to_string(id);
1338 getCertificateProperties(asyncResp, objectPath,
1339 certs::authorityServiceName, id,
1340 certURL, "TrustStore Certificate");
1341 });
1342
1343 BMCWEB_ROUTE(app, "/redfish/v1/Managers/bmc/Truststore/Certificates/<str>/")
Ed Tanoused398212021-06-09 17:05:54 -07001344 .privileges(redfish::privileges::deleteCertificate)
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001345 .methods(boost::beast::http::verb::delete_)(
Ed Tanous45ca1b82022-03-25 13:07:27 -07001346 [&app](const crow::Request& req,
1347 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
1348 const std::string& param) {
1349 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res))
1350 {
1351 return;
1352 }
John Edward Broadbent7e860f12021-04-08 15:57:16 -07001353 if (param.empty())
1354 {
1355 messages::internalError(asyncResp->res);
1356 return;
1357 }
1358
1359 long id = getIDFromURL(req.url);
1360 if (id < 0)
1361 {
1362 BMCWEB_LOG_ERROR << "Invalid url value: " << req.url;
1363 messages::resourceNotFound(asyncResp->res,
1364 "TrustStore Certificate",
1365 std::string(req.url));
1366 return;
1367 }
1368 BMCWEB_LOG_DEBUG << "TrustStoreCertificate::doDelete ID="
1369 << std::to_string(id);
1370 std::string certPath = certs::authorityObjectPath;
1371 certPath += "/";
1372 certPath += std::to_string(id);
1373
1374 crow::connections::systemBus->async_method_call(
1375 [asyncResp, id](const boost::system::error_code ec) {
1376 if (ec)
1377 {
1378 messages::resourceNotFound(asyncResp->res,
1379 "TrustStore Certificate",
1380 std::to_string(id));
1381 return;
1382 }
1383 BMCWEB_LOG_INFO << "Certificate deleted";
1384 asyncResp->res.result(
1385 boost::beast::http::status::no_content);
1386 },
1387 certs::authorityServiceName, certPath, certs::objDeleteIntf,
1388 "Delete");
1389 });
1390} // requestRoutesTrustStoreCertificate
Marri Devender Rao5968cae2019-01-21 10:27:12 -06001391} // namespace redfish