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