| #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_ |