| #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" | |
| namespace redfish { | |
| // Interfaces which imply a D-Bus object represents a ComponentIntegrity | |
| inline constexpr std::array<std::string_view, 3> componentIntegrityInterfaces = | |
| {"xyz.openbmc_project.Attestation.ComponentIntegrity", | |
| "xyz.openbmc_project.Attestation.MeasurementSet", | |
| "xyz.openbmc_project.Attestation.IdentityAuthentication"}; | |
| inline constexpr std::array<std::string_view, 1> networkAdapterInterfaces = { | |
| "xyz.openbmc_project.Inventory.Item.NetworkAdapter"}; | |
| inline 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"}; | |
| // Interfaces which imply a D-Bus object is a generic inventory component whose | |
| // parent chassis is resolved through its contained_by association. | |
| inline constexpr std::array<std::string_view, 2> componentItemInterfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Component", | |
| "xyz.openbmc_project.Configuration.MCTPUSBDevice"}; | |
| inline constexpr std::array<std::string_view, 1> driveInterfaces = { | |
| "xyz.openbmc_project.Inventory.Item.Drive"}; | |
| 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, Drive, 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; | |
| } | |
| // TODO(b/552633100): The contained_by -> chassis id resolution below | |
| // is duplicated in trusted_component.h. Factor it out into a shared | |
| // helper under redfish_core/include/utils/ once the new file is | |
| // plumbed through copybara. | |
| if (redfish::dbus_utils::findInterfacesInServiceMap( | |
| object, componentItemInterfaces)) { | |
| const std::string containedByPath = | |
| protectedComponentObjPath.str + "/contained_by"; | |
| dbus_utils::getAssociationEndPoints( | |
| containedByPath, requestContext, | |
| [asyncResp, protectedComponentObjPath]( | |
| const boost::system::error_code& e, | |
| const dbus::utility::MapperEndPoints& endpoints) { | |
| std::string chassisId; | |
| if (!e && !endpoints.empty()) { | |
| sdbusplus::message::object_path boardPath(endpoints.at(0)); | |
| chassisId = boardPath.filename(); | |
| } | |
| if (chassisId.empty()) { | |
| chassisId = | |
| protectedComponentObjPath.parent_path().filename(); | |
| } | |
| nlohmann::json& jValue = | |
| asyncResp->res.jsonValue["Links"]["ComponentsProtected"]; | |
| if (!jValue.is_array()) { | |
| jValue = nlohmann::json::array(); | |
| } | |
| nlohmann::json link; | |
| link["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisId); | |
| jValue.push_back(std::move(link)); | |
| }); | |
| return; | |
| } | |
| // handle Drives: parent resource can only be Chassis. | |
| if (redfish::dbus_utils::findInterfacesInServiceMap( | |
| object, driveInterfaces)) { | |
| 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, "Drives", 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 = gmtime_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_ |