blob: a9d722223b4b6909d745dcaead05766ce4a91f3e [file] [log] [blame]
/*
// 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.
*/
#pragma once
#include "app.hpp"
#include "dbus_utility.hpp"
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "parsing.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/chassis_utils.hpp"
#include "utils/collection.hpp"
#include "utils/dbus_utils.hpp"
#include "utils/json_utils.hpp"
#include "utils/system_utils.hpp"
#include "http/parsing.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/system/error_code.hpp>
#include <boost/url/format.hpp>
#include <sdbusplus/asio/property.hpp>
#include <sdbusplus/message/native_types.hpp>
#include <sdbusplus/unpack_properties.hpp>
#include <sdbusplus/utility/dedup_variant.hpp>
#include <array>
#include <ctime>
#include <limits>
#include <regex>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>
#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, 2> processorsInterfaces = {
"xyz.openbmc_project.Inventory.Item.Cpu",
"xyz.openbmc_project.Inventory.Item.Accelerator"};
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
const static 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
const static 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;
}
// handle Processor: parent resource can be Systems or Chassis.
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();
}
std::string parentResourceType;
if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::chassis_utils::chassisInterfaces))
{
parentResourceType = "Chassis";
} else if (redfish::dbus_utils::findInterfacesInServiceMap(
object, redfish::system_utils::systemInterfaces))
{
parentResourceType = "Systems";
} 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, parentObj,
"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);
const struct tm* tminfo = localtime(&ts);
/**
* 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