| /* |
| // 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 |