blob: 9fff9b54a7f6523f7c2f035b01d3c807790c3e7b [file] [log] [blame]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_COMPONENT_INTEGRITY_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_COMPONENT_INTEGRITY_H_
/*
// Copyright (c) 2024 Google
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include <algorithm>
#include <array>
#include <ctime>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <regex> // NOLINT
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "boost/container/flat_map.hpp" // NOLINT
#include "boost/system/error_code.hpp" // NOLINT
#include "boost/url/format.hpp" // NOLINT
#include "app.hpp"
#include "http_request.hpp"
#include "logging.hpp"
#include "parsing.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "chassis_utils.hpp"
#include "collection.hpp"
#include "dbus_utils.hpp"
#include "json_utils.hpp"
#include "system_utils.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish {
// Interfaces which imply a D-Bus object represents a ComponentIntegrity
constexpr std::array<std::string_view, 3> componentIntegrityInterfaces = {
"xyz.openbmc_project.Attestation.ComponentIntegrity",
"xyz.openbmc_project.Attestation.MeasurementSet",
"xyz.openbmc_project.Attestation.IdentityAuthentication"};
constexpr std::array<std::string_view, 1> networkAdapterInterfaces = {
"xyz.openbmc_project.Inventory.Item.NetworkAdapter"};
constexpr std::array<std::string_view, 3> processorsInterfaces = {
"xyz.openbmc_project.Inventory.Item.Cpu",
"xyz.openbmc_project.Inventory.Item.Accelerator",
"xyz.openbmc_project.Configuration.Processor"};
constexpr char const* spdmSecurityTechnologyType =
"xyz.openbmc_project.Attestation.ComponentIntegrity.SecurityTechnologyType."
"SPDM";
constexpr char const* tpmSecurityTechnologyType =
"xyz.openbmc_project.Attestation.ComponentIntegrity.SecurityTechnologyType."
"TPM";
constexpr char const* oemSecurityTechnologyType =
"xyz.openbmc_project.Attestation.ComponentIntegrity.SecurityTechnologyType."
"OEM";
constexpr char const* successVerificationStatus =
"xyz.openbmc_project.Attestation.IdentityAuthentication.VerificationStatus."
"Success";
constexpr char const* attestationComponentIntegrityIntf =
"xyz.openbmc_project.Attestation.ComponentIntegrity";
constexpr char const* attestationIntegrityAuthIntf =
"xyz.openbmc_project.Attestation.IdentityAuthentication";
constexpr char const* attestationMeasurementSetIntf =
"xyz.openbmc_project.Attestation.MeasurementSet";
inline std::string getTrustedComponent(const std::string& dbusPath) {
std::string chassis;
std::string component;
// Map D-Bus object path to the Redfish URI
// Example targetComponentURI
// "/xyz/openbmc_project/Chassis/chassis01/TrustedComponent/tc01"
// which should maps to
// "/redfish/v1/Chassis/chassis01/TrustedComponent/tc01"
// These regexes derived on the rules here:
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
static const std::regex validPath(
"/xyz/openbmc_project/Chassis/([A-Za-z0-9_]+)/TrustedComponents/"
"([A-Za-z0-9_]+)");
std::smatch matches;
if (regex_match(dbusPath, matches, validPath)) {
chassis = matches[1];
component = matches[2];
} else {
BMCWEB_LOG_ERROR << "Can't parse TrustedComponent object path:" << dbusPath;
return "";
}
boost::urls::url uri = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassis, "TrustedComponents", component);
std::string trustedComponent(uri.c_str());
return trustedComponent;
}
inline std::optional<nlohmann::json> getChassisCert(
const std::string& dbusPath) {
nlohmann::json::object_t certObj;
std::string chassis;
std::string cert;
// Map D-Bus object to Redfish URI
// Example chassis cert object path:
// "/xyz/openbmc_project/certs/chassis/chassis01/cert01"
// which should maps to:
// "/redfish/v1/Chassis/chassis01/Certificates/cert01"
// These regexes derived on the rules here:
// https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
static const std::regex validPath(
"/xyz/openbmc_project/certs/chassis/([A-Za-z0-9_]+)/([A-Za-z0-9_]+)");
std::smatch matches;
if (regex_match(dbusPath, matches, validPath)) {
chassis = matches[1];
cert = matches[2];
} else {
BMCWEB_LOG_ERROR << "Can't parse DBus certificate object path:" << dbusPath;
return std::nullopt;
}
certObj["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", chassis, "Certificates", cert);
return {std::move(certObj)};
}
inline void getProtectedComponentsInfo(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& currentObjPath, const boost::system::error_code& ec,
const dbus::utility::MapperEndPoints& protectedComponentObjPaths) {
if (ec) {
if (ec.value() != EBADR) {
BMCWEB_LOG_ERROR << "DBUS response error " << ec;
messages::internalError(asyncResp->res);
}
return;
}
if (protectedComponentObjPaths.empty()) {
return;
}
managedStore::ManagedObjectStoreContext requestContext(asyncResp);
for (const auto& objpath : protectedComponentObjPaths) {
sdbusplus::message::object_path protectedComponentObjPath(objpath);
const std::string objName = protectedComponentObjPath.filename();
if (objName.empty()) {
BMCWEB_LOG_WARNING << "Malformed protected component object path "
<< protectedComponentObjPath.str << " for "
<< currentObjPath;
continue;
}
managedStore::GetManagedObjectStore()->getDbusObject(
objpath, {}, requestContext,
[asyncResp, protectedComponentObjPath, currentObjPath, requestContext,
objName](const boost::system::error_code& ec2,
const dbus::utility::MapperGetObject& object) {
if (ec2) {
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& jValue =
asyncResp->res.jsonValue["Links"]["ComponentsProtected"];
if (!jValue.is_array()) {
// Create the array if it was empty
jValue = nlohmann::json::array();
}
// Link can be Chassis or Board, Processor, and NetworkAdapters
// handle Chassis or Board case
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces)) {
nlohmann::json link;
link["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", objName);
jValue.push_back(std::move(link));
return;
}
// handle NetworkAdaptors: parent resource can only be Chassis.
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, networkAdapterInterfaces)) {
const std::string parentObj =
protectedComponentObjPath.parent_path().filename();
if (parentObj.empty()) {
BMCWEB_LOG_WARNING << "Malformed protectedComponent object path "
<< protectedComponentObjPath.str << " for "
<< currentObjPath;
return;
}
nlohmann::json link;
link["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "Chassis", parentObj, "NetworkAdapters",
objName);
jValue.push_back(std::move(link));
return;
}
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, processorsInterfaces)) {
std::string parentPath =
protectedComponentObjPath.parent_path().str;
std::string parentObj =
protectedComponentObjPath.parent_path().filename();
// e.g.,
// /xyz/openbmc_project/inventory/system/board/BoardName/system1/chassis/motherboard/cpu0
// filter out chassis|motherboard in object path.
while (parentObj == "motherboard" || parentObj == "chassis") {
sdbusplus::message::object_path tempObjPath(parentPath);
parentPath = tempObjPath.parent_path().str;
parentObj = tempObjPath.parent_path().filename();
}
if (parentObj.empty()) {
BMCWEB_LOG_WARNING << "Malformed protectedComponentObjPath "
<< protectedComponentObjPath.str << " for "
<< currentObjPath;
return;
}
managedStore::GetManagedObjectStore()->getDbusObject(
parentPath, {}, requestContext,
[asyncResp, objName, parentObj](
const boost::system::error_code& ec2,
const dbus::utility::MapperGetObject& object) {
if (ec2) {
BMCWEB_LOG_ERROR << "DBUS response error";
messages::internalError(asyncResp->res);
return;
}
nlohmann::json& jValue =
asyncResp->res.jsonValue["Links"]["ComponentsProtected"];
if (!jValue.is_array()) {
// Create the array if it was empty
jValue = nlohmann::json::array();
}
// handle Processor: parent resource has to be Systems as
// OpenBMC only search for Processor under Systems though
// Redfish SPEC allows Processors to be modeled under Chassis
// starting from DSP2046_2022.3.pdf.
std::string parentResourceType;
std::string parentResourceName;
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces)) {
parentResourceType = "Systems";
parentResourceName = "system";
} else if (redfish::dbus_utils::findInterfacesInServiceMap(
object,
redfish::system_utils::systemInterfaces)) {
parentResourceType = "Systems";
parentResourceName = parentObj;
} else {
BMCWEB_LOG_ERROR << "Parent resource type not "
"supported for Processor resource";
messages::internalError(asyncResp->res);
return;
}
nlohmann::json link;
link["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", parentResourceType, parentResourceName,
"Processors", objName);
jValue.push_back(std::move(link));
return;
});
return;
} // end of Processor interfaces handling
// Unsupported resource type
BMCWEB_LOG_ERROR << "Unsupported ComponentsProtected resource for "
<< protectedComponentObjPath.str;
messages::internalError(asyncResp->res);
return;
});
}
}
/**
* @brief Fill out ComponentIntegrity related info by
* requesting data from the given D-Bus object.
*
* @param[in,out] aResp Async HTTP response.
* @param[in] service D-Bus service to query.
* @param[in] objPath D-Bus object to query.
*/
inline void getComponentIntegrityData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& service,
const std::string& objPath) {
BMCWEB_LOG_DEBUG << "Get ComponentIntegrity Data";
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, objPath, attestationComponentIntegrityIntf, context,
[objPath, aResp{aResp}, context](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& properties) {
if (ec) {
BMCWEB_LOG_DEBUG << "DBUS response error";
messages::internalError(aResp->res);
return;
}
const bool* enabled = nullptr;
const std::string* type = nullptr;
const std::string* typeVersion = nullptr;
const std::uint64_t* lastUpdated = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties, "Enabled", enabled,
"Type", type, "TypeVersion", typeVersion, "LastUpdated",
lastUpdated);
if (!success) {
messages::internalError(aResp->res);
return;
}
if (enabled != nullptr) {
if (*enabled) {
aResp->res.jsonValue["ComponentIntegrityEnabled"] = true;
} else {
aResp->res.jsonValue["ComponentIntegrityEnabled"] = false;
}
} else {
messages::internalError(aResp->res);
return;
}
if ((type != nullptr) && !type->empty()) {
if (*type == spdmSecurityTechnologyType) {
aResp->res.jsonValue["ComponentIntegrityType"] = "SPDM";
aResp->res.jsonValue["Description"] =
"SPDM-capable RoT physically attached to a device.";
} else if (*type == tpmSecurityTechnologyType) {
aResp->res.jsonValue["ComponentIntegrityType"] = "TPM";
aResp->res.jsonValue["Description"] =
"TPM physically attached to a device.";
} else if (*type == oemSecurityTechnologyType) {
aResp->res.jsonValue["ComponentIntegrityType"] = "OEM";
aResp->res.jsonValue["Description"] =
"OEM-specific RoT physically attached to a device.";
} else {
messages::internalError(aResp->res);
return;
}
}
if ((typeVersion != nullptr) && !typeVersion->empty()) {
aResp->res.jsonValue["ComponentIntegrityTypeVersion"] = *typeVersion;
}
if ((lastUpdated != nullptr) && *lastUpdated != 0) {
time_t ts = static_cast<time_t>(*lastUpdated);
struct tm tminfo_r;
const struct tm* tminfo = localtime_r(&ts, &tminfo_r);
/**
* The date of component update in ISO 8601 format as
* YYYYMMDDThhmmssZ. Where 'T' is a literal character to separate
* date fields from time and 'Z' is a literal character to indicate
* UTC.
*/
char iso8601[40]; // NOLINT
std::strftime(static_cast<char*>(iso8601), sizeof(iso8601),
"%Y%m%dT%H%M%SZ", tminfo);
aResp->res.jsonValue["LastUpdated"] =
std::string(static_cast<char*>(iso8601));
}
// Get trusted components info from associations.
std::string targetComponentPath = objPath + "/reporting";
dbus_utils::getAssociationEndPoints(
targetComponentPath, context,
[aResp](const boost::system::error_code& e,
const dbus::utility::MapperEndPoints&
nodeTrustedComponentList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
// One ComponentIntegrity object should be associated with one
// TrustedComponent object
if (nodeTrustedComponentList.size() == 1) {
const std::string& componentURI =
nodeTrustedComponentList.at(0);
std::string targetComponentURI =
getTrustedComponent(componentURI);
if (!targetComponentURI.empty()) {
aResp->res.jsonValue["TargetComponentURI"] =
targetComponentURI;
} else {
BMCWEB_LOG_ERROR << "Can't parse TargetComponentURI : "
<< componentURI;
messages::internalError(aResp->res);
return;
}
} else {
BMCWEB_LOG_ERROR
<< "Unexpected TargetComponent #objs (expecting 1): "
<< nodeTrustedComponentList.size();
messages::internalError(aResp->res);
return;
}
});
// Get protected components info from associations.
std::string protectedComponentPath = objPath + "/authenticating";
dbus_utils::getAssociationEndPoints(
protectedComponentPath, context,
std::bind_front(getProtectedComponentsInfo, aResp, objPath));
});
}
/**
* @brief Fill out {ComponentIntegrityId}#/SPDM/IdentityAuthentication
* related info by requesting data from the given D-Bus object.
*
* @param[in,out] aResp Async HTTP response.
* @param[in] service D-Bus service to query.
* @param[in] objPath D-Bus object to query.
*/
inline void getSPDMAuthenticationData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp, const std::string& service,
const std::string& objPath) {
BMCWEB_LOG_DEBUG
<< "Get {ComponentIntegrityId}#/SPDM/IdentyAuthentication Data";
managedStore::ManagedObjectStoreContext context(aResp);
managedStore::GetManagedObjectStore()->getAllProperties(
service, objPath, attestationIntegrityAuthIntf, context,
[objPath, aResp{aResp}, context](
const boost::system::error_code& ec,
const std::vector<std::pair<
std::string, dbus::utility::DbusVariantType>>& properties) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error!";
messages::internalError(aResp->res);
return;
}
const std::string* respStatus = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), properties,
"ResponderVerificationStatus", respStatus);
if (!success) {
messages::internalError(aResp->res);
return;
}
if (respStatus != nullptr && !respStatus->empty()) {
if (*respStatus == successVerificationStatus) {
aResp->res
.jsonValue["SPDM"]["IdentityAuthentication"]
["ResponderAuthentication"]["VerificationStatus"] =
"Success";
} else {
aResp->res
.jsonValue["SPDM"]["IdentityAuthentication"]
["ResponderAuthentication"]["VerificationStatus"] =
"Failed";
}
}
// Get associated certs objects.
std::string reqCertPath = objPath + "/requester_identified_by";
dbus_utils::getAssociationEndPoints(
reqCertPath, context,
[aResp](const boost::system::error_code& e,
const dbus::utility::MapperEndPoints& nodeReqCertList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
nlohmann::json& json = aResp->res.jsonValue;
// One ComponentIntegrity object should be associated with one
// requester certificate object or none
if (nodeReqCertList.empty()) {
return;
}
if (nodeReqCertList.size() > 1) {
BMCWEB_LOG_ERROR << "More than one requester cert provided!";
messages::internalError(aResp->res);
return;
}
// There is one valid cert, handle it
const std::string& certPath = nodeReqCertList.at(0);
std::optional<nlohmann::json> reqCertObj =
getChassisCert(certPath);
if (reqCertObj != std::nullopt) {
json["SPDM"]["IdentityAuthentication"]
["RequesterAuthentication"]["ProvidedCertificate"] =
*reqCertObj;
} else {
// Requester Cert is not required, no need to report error.
BMCWEB_LOG_DEBUG << "Unable to parse requester certPath: "
<< certPath;
}
});
// Get associated response certs objects.
std::string respCertPath = objPath + "/responder_identified_by";
dbus_utils::getAssociationEndPoints(
respCertPath, context,
[aResp](const boost::system::error_code& e,
const dbus::utility::MapperEndPoints& nodeRespCertList) {
if (e) {
if (e.value() != EBADR) {
messages::internalError(aResp->res);
return;
}
// No resource found.
return;
}
nlohmann::json& json = aResp->res.jsonValue;
// One ComponentIntegrity object should be associated with one
// responder certificate object
if (nodeRespCertList.size() != 1) {
BMCWEB_LOG_ERROR
<< "Unexpected Responder Cert #objs (expecting 1): "
<< nodeRespCertList.size();
messages::internalError(aResp->res);
return;
}
const std::string& certPath = nodeRespCertList.at(0);
std::optional<nlohmann::json> respCertObj =
getChassisCert(certPath);
if (respCertObj != std::nullopt) {
json["SPDM"]["IdentityAuthentication"]
["ResponderAuthentication"]["ComponentCertificate"] =
*respCertObj;
} else {
BMCWEB_LOG_ERROR << "Unable to parse responder certPath: "
<< certPath;
messages::internalError(aResp->res);
return;
}
});
});
}
inline void getComponentData(
const std::shared_ptr<bmcweb::AsyncResp>& aResp,
const std::string& objectPath,
const dbus::utility::MapperServiceMap& serviceMap) {
for (const auto& [serviceName, interfaceList] : serviceMap) {
for (const auto& interface : interfaceList) {
if (interface == attestationComponentIntegrityIntf) {
getComponentIntegrityData(aResp, serviceName, objectPath);
} else if (interface == attestationIntegrityAuthIntf) {
getSPDMAuthenticationData(aResp, serviceName, objectPath);
}
}
}
}
inline void addSPDMGetSignedMeasurementsLinks(nlohmann::json& getMeasurement,
const std::string& componentId) {
getMeasurement["target"] = crow::utility::urlFromPieces(
"redfish", "v1", "ComponentIntegrity", componentId, "Actions",
"ComponentIntegrity.SPDMGetSignedMeasurements");
getMeasurement["@Redfish.ActionInfo"] = crow::utility::urlFromPieces(
"redfish", "v1", "ComponentIntegrity", componentId,
"SPDMGetSignedMeasurementsActionInfo");
}
/**
* Find the D-Bus object representing the requested ComponentIntegrity,
* and call the handler with the results. If matching object is not
* found, add 404 error to response and don't call the handler.
*
* @param[in,out] resp Async HTTP response.
* @param[in] processorId Redfish Processor Id.
* @param[in] handler Callback to continue processing request upon
* successfully finding object.
*/
template <typename Handler>
inline void getComponentObject(const std::shared_ptr<bmcweb::AsyncResp>& resp,
const std::string& componentId,
Handler&& handler) {
BMCWEB_LOG_DEBUG << "Get available system component integrity resources.";
// GetSubTree on all interfaces which provide info about a
// component_integrity.
managedStore::ManagedObjectStoreContext context(resp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/ComponentIntegrity/", 0,
componentIntegrityInterfaces, context,
[resp, componentId, handler](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
messages::internalError(resp->res);
return;
}
for (const auto& [objectPath, serviceMap] : subtree) {
// Ignore any objects which don't end with our desired
// componentintegrity name
if (!objectPath.ends_with(componentId)) {
continue;
}
bool found = false;
// Filter out objects that don't have the
// ComponentIntegrity-specific interfaces to make sure we can return
// 404 on non-ComponentIntegrity
for (const auto& [serviceName, interfaceList] : serviceMap) {
if (std::find_first_of(interfaceList.begin(), interfaceList.end(),
componentIntegrityInterfaces.begin(),
componentIntegrityInterfaces.end()) !=
interfaceList.end()) {
found = true;
break;
}
}
if (!found) {
continue;
}
// Matching component found
resp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "ComponentIntegrity", componentId);
resp->res.jsonValue["Name"] = "Component Integrity";
resp->res.jsonValue["Id"] = componentId;
resp->res.jsonValue["SPDM"]["Requester"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "Managers", "bmc");
addSPDMGetSignedMeasurementsLinks(
resp->res
.jsonValue["Actions"]
["#ComponentIntegrity.SPDMGetSignedMeasurements"],
componentId);
handler(objectPath, serviceMap);
return;
}
messages::resourceNotFound(resp->res, "ComponentIntegrity",
componentId);
});
}
inline void handleComponentIntegrityCollectionGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ComponentIntegrityCollection.ComponentIntegrityCollection";
asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/ComponentIntegrity";
asyncResp->res.jsonValue["Name"] = "Component Integrity Collection";
asyncResp->res.jsonValue["Description"] = "Collection of Component Integrity";
collection_util::getCollectionMembers(
asyncResp, boost::urls::url("/redfish/v1/ComponentIntegrity"),
componentIntegrityInterfaces, "/xyz/openbmc_project/ComponentIntegrity");
}
inline void handleComponentIntegrityGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& componentId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.addHeader(boost::beast::http::field::link,
"</redfish/v1/JsonSchemas/ComponentIntegrity/"
"ComponentIntegrity.json>; rel=describedby");
asyncResp->res.jsonValue["@odata.type"] =
"#ComponentIntegrity.v1_2_0.ComponentIntegrity";
getComponentObject(asyncResp, componentId,
std::bind_front(getComponentData, asyncResp));
}
inline void handleSPDMGetSignedMeasurementsActionInfoGet(
crow::App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& componentId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue["@odata.type"] = "#ActionInfo.v1_3_0.ActionInfo";
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "ComponentIntegrity", componentId,
"SPDMGetSignedMeasurementsActionInfo");
asyncResp->res.jsonValue["Name"] = "SPDMGetSignedMeasurements Action Info";
asyncResp->res.jsonValue["Id"] = "SPDMGetSignedMeasurementsActionInfo";
nlohmann::json::array_t parameters;
nlohmann::json::object_t indices;
nlohmann::json::object_t nonce;
nlohmann::json::object_t slotId;
indices["Name"] = "MeasurementIndices";
indices["Required"] = false;
indices["DataType"] = "NumberArray";
nonce["Name"] = "Nonce";
nonce["Required"] = false;
nonce["DataType"] = "String";
slotId["Name"] = "SlotId";
slotId["Required"] = false;
slotId["DataType"] = "Number";
parameters.emplace_back(std::move(indices));
parameters.emplace_back(std::move(nonce));
parameters.emplace_back(std::move(slotId));
asyncResp->res.jsonValue["Parameters"] = std::move(parameters);
}
inline void handleComponentIntegritySPDMGetSignedMeasurementsActionPost(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& componentId) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
BMCWEB_LOG_DEBUG << "Post ComponentIntegrity SPDMGetSignedMeasurements.";
// GetSubTree on interfaces which provide info about certificate.
constexpr std::array<std::string_view, 1> interfaces = {
attestationMeasurementSetIntf};
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getSubTree(
"/xyz/openbmc_project/ComponentIntegrity/", 0, interfaces, context,
[asyncResp, req, componentId](
const boost::system::error_code& ec,
const dbus::utility::MapperGetSubTreeResponse& subtree) {
if (ec) {
BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
messages::internalError(asyncResp->res);
return;
}
std::string ciPath =
std::string("/xyz/openbmc_project/ComponentIntegrity/") +
componentId;
nlohmann::json reqJson;
JsonParseResult ret = parseRequestAsJson(req, reqJson);
if (ret != JsonParseResult::Success) {
// We did not receive JSON request, proceed as it is RAW data
BMCWEB_LOG_ERROR << "Parse json request failed!";
messages::internalError(asyncResp->res);
return;
}
// All fields below are optional by DMTF DSP2046_2022.3.pdf
// default nonce empty, libspdm will generate one.
std::optional<std::string> optNonce = "";
// default 0 indicates using slot 0.
std::optional<size_t> optSlotId = 0;
// default 255(0xff) indicates requesting all measurement blocks.
std::optional<std::vector<size_t>> optMeasurementIndices =
std::vector<size_t>{255};
// readJsonPatch will move on when some fields are missing, we only
// use default value for missing fields.
if (!json_util::readJsonPatch(
req, asyncResp->res, "SlotId", optSlotId, "MeasurementIndices",
optMeasurementIndices, "Nonce", optNonce)) {
BMCWEB_LOG_DEBUG << "Using default only for missing params!";
}
for (const auto& [objectPath, serviceMap] : subtree) {
// Ignore any objects which don't match component integrity
if (objectPath.find(ciPath) == std::string::npos) {
continue;
}
// Should only match one service and one object
for (const auto& [serviceName, interfaceList] : serviceMap) {
// SPDMGetSignedMeasurements Response
managedStore::GetManagedObjectStore()
->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp](const boost::system::error_code& e,
const sdbusplus::message::object_path& certPath,
const std::string& hashAlg,
const std::string& pubkey,
const std::string& signedMeasurements,
const std::string& signAlg,
const std::string& version) {
if (e) {
BMCWEB_LOG_ERROR << "DBUS response error: "
<< e.message();
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#ComponentIntegrity.v1_0_0."
"SPDMGetSignedMeasurementsResponse";
asyncResp->res.jsonValue["Version"] = version;
asyncResp->res.jsonValue["HashingAlgorithm"] = hashAlg;
asyncResp->res.jsonValue["SigningAlgorithm"] = signAlg;
asyncResp->res.jsonValue["SignedMeasurements"] =
signedMeasurements;
asyncResp->res.jsonValue["PublicKey"] = pubkey;
// handle Device Certificate
std::optional<nlohmann::json> certObj =
getChassisCert(certPath);
if (certObj != std::nullopt) {
asyncResp->res.jsonValue["Certificate"] = *certObj;
} else {
BMCWEB_LOG_ERROR << "Unable to parse certPath: "
<< std::string(certPath);
messages::internalError(asyncResp->res);
return;
}
},
serviceName, ciPath,
"xyz.openbmc_project.Attestation.MeasurementSet",
"SPDMGetSignedMeasurements", *optMeasurementIndices,
*optNonce, *optSlotId);
}
}
});
}
inline void requestRoutesComponentIntegrityCollection(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/ComponentIntegrity/")
.privileges(redfish::privileges::getComponentIntegrityCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleComponentIntegrityCollectionGet, std::ref(app)));
}
inline void requestRoutesComponentIntegrity(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/ComponentIntegrity/<str>/")
.privileges(redfish::privileges::getComponentIntegrity)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleComponentIntegrityGet, std::ref(app)));
}
inline void requestRoutesComponentIntegritySPDMGetSignedMeasurementsActionInfo(
App& app) {
BMCWEB_ROUTE(app,
"/redfish/v1/ComponentIntegrity/<str>/"
"SPDMGetSignedMeasurementsActionInfo")
.privileges(redfish::privileges::getActionInfo)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleSPDMGetSignedMeasurementsActionInfoGet, std::ref(app)));
}
inline void requestRoutesComponentIntegritySPDMGetSignedMeasurementsAction(
App& app) {
BMCWEB_ROUTE(app,
"/redfish/v1/ComponentIntegrity/<str>/Actions/"
"ComponentIntegrity.SPDMGetSignedMeasurements/")
.privileges(redfish::privileges::getComponentIntegrity)
.methods(boost::beast::http::verb::post)(std::bind_front(
handleComponentIntegritySPDMGetSignedMeasurementsActionPost,
std::ref(app)));
}
} // namespace redfish
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_COMPONENT_INTEGRITY_H_