| #ifndef THIRD_PARTY_GBMCWEB_INCLUDE_GOOGLE_GOOGLE_SERVICE_ROOT_H_ | |
| #define THIRD_PARTY_GBMCWEB_INCLUDE_GOOGLE_GOOGLE_SERVICE_ROOT_H_ | |
| #include <array> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <filesystem> // NOLINT | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <regex> // NOLINT | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/status/statusor.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "app.hpp" | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "utility.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "error_messages.hpp" | |
| #include "query.hpp" | |
| #include "chassis_utils.hpp" | |
| #include "collection.hpp" | |
| #include "dbus_utils.hpp" | |
| #include "hex_utils.hpp" | |
| #include "json_utils.hpp" | |
| #include "location_utils.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "tlbmc/credentials/credential_manager.h" | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "sdbusplus/message/native_types.hpp" | |
| #include "sdbusplus/unpack_properties.hpp" | |
| #include <openssl/evp.h> | |
| namespace crow { | |
| namespace google_api { | |
| inline void handleGoogleV1Get( | |
| 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"] = | |
| "#GoogleServiceRoot.v1_0_0.GoogleServiceRoot"; | |
| asyncResp->res.jsonValue["@odata.id"] = "/google/v1"; | |
| asyncResp->res.jsonValue["Id"] = "Google Rest RootService"; | |
| asyncResp->res.jsonValue["Name"] = "Google Service Root"; | |
| asyncResp->res.jsonValue["Version"] = "1.0.0"; | |
| asyncResp->res.jsonValue["RootOfTrustCollection"]["@odata.id"] = | |
| "/google/v1/RootOfTrustCollection"; | |
| asyncResp->res.jsonValue["NVMe"]["@odata.id"] = "/google/v1/NVMe"; | |
| } | |
| inline void handleRootOfTrustCollectionGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.id"] = "/google/v1/RootOfTrustCollection"; | |
| asyncResp->res.jsonValue["@odata.type"] = | |
| "#RootOfTrustCollection.RootOfTrustCollection"; | |
| const std::array<std::string_view, 1> interfaces{ | |
| "xyz.openbmc_project.Control.Hoth"}; | |
| redfish::collection_util::getCollectionMembers( | |
| asyncResp, boost::urls::url("/google/v1/RootOfTrustCollection"), | |
| interfaces, "/xyz/openbmc_project"); | |
| } | |
| struct PcrQuoteParams { | |
| std::string pcrSelectionBase64; | |
| std::string nonceHex; | |
| }; | |
| // Helper struct to identify a resolved D-Bus object interface | |
| struct ResolvedEntity { | |
| std::string id; | |
| std::string service; | |
| std::string object; | |
| std::string interface; | |
| }; | |
| template <typename CommandT, typename HandlerFunc> | |
| inline void hothGetSubtreeCallback( | |
| const CommandT& command, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId, const HandlerFunc& entityHandler, | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| if (ec) { | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| for (const auto& [path, services] : subtree) { | |
| sdbusplus::message::object_path objPath(path); | |
| if (objPath.filename() != rotId || services.empty()) { | |
| continue; | |
| } | |
| ResolvedEntity resolvedEntity = { | |
| .id = rotId, | |
| .service = services[0].first, | |
| .object = path, | |
| .interface = "xyz.openbmc_project.Control.Hoth"}; | |
| entityHandler(command, asyncResp, resolvedEntity); | |
| return; | |
| } | |
| // Couldn't find an object with that name. return an error | |
| redfish::messages::resourceNotFound(asyncResp->res, "RootOfTrust", rotId); | |
| } | |
| template <typename CommandT, typename HandlerFunc> | |
| inline void resolveRoT(const CommandT& command, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId, HandlerFunc entityHandler) { | |
| constexpr std::array<std::string_view, 1> hothIfaces = { | |
| "xyz.openbmc_project.Control.Hoth"}; | |
| managedStore::ManagedObjectStoreContext requestContext(asyncResp); | |
| managedStore::GetManagedObjectStore()->getSubTree( | |
| "/xyz/openbmc_project", 0, hothIfaces, requestContext, | |
| [command, asyncResp, rotId, entityHandler = std::move(entityHandler)]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreeResponse& subtree) { | |
| hothGetSubtreeCallback(command, asyncResp, rotId, entityHandler, ec, | |
| subtree); | |
| }); | |
| } | |
| inline void populateRootOfTrustState( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const dbus::utility::DBusPropertiesMap& properties) { | |
| const uint32_t* bootloaderUpdateFailureCode = nullptr; | |
| const uint32_t* firmwareUpdateFailedMinor = nullptr; | |
| const uint32_t* firmwareUpdateFailureCode = nullptr; | |
| const uint32_t* payloadConfirmFailureCode = nullptr; | |
| const uint32_t* payloadUpdateFailureCode = nullptr; | |
| const uint32_t* resetFlags = nullptr; | |
| const uint32_t* roInfoStrikes = nullptr; | |
| const uint32_t* rwInfoStrikes = nullptr; | |
| const uint64_t* upTime = nullptr; | |
| const bool* hasPersistentPanicInfo = nullptr; | |
| const bool* hasValidAuthRecord = nullptr; | |
| const uint64_t* authRecordCapabilities = nullptr; | |
| const bool* secureBootEnforced = nullptr; | |
| const uint32_t* keyRotationVersion = nullptr; | |
| const uint16_t* keyRotationImageFamily = nullptr; | |
| const uint16_t* keyRotationImageFamilyVariant = nullptr; | |
| const uint32_t* keyRotationValidationMethod = nullptr; | |
| const uint32_t* keyRotationValidationKeyData = nullptr; | |
| const uint32_t* keyRotationValidationHashData = nullptr; | |
| const std::string* chipRevision = nullptr; | |
| const bool success = sdbusplus::unpackPropertiesNoThrow( | |
| redfish::dbus_utils::UnpackErrorPrinter(), properties, | |
| "BootloaderUpdateFailureCode", bootloaderUpdateFailureCode, | |
| "FirmwareUpdateFailedMinor", firmwareUpdateFailedMinor, | |
| "FirmwareUpdateFailureCode", firmwareUpdateFailureCode, | |
| "PayloadConfirmFailureCode", payloadConfirmFailureCode, | |
| "PayloadUpdateFailureCode", payloadUpdateFailureCode, "ResetFlags", | |
| resetFlags, "RoInfoStrikes", roInfoStrikes, "RwInfoStrikes", | |
| rwInfoStrikes, "UpTime", upTime, "HasPersistentPanicInfo", | |
| hasPersistentPanicInfo, "HasValidAuthRecord", hasValidAuthRecord, | |
| "AuthRecordCapabilities", authRecordCapabilities, "SecureBootEnforced", | |
| secureBootEnforced, "KeyRotationVersion", keyRotationVersion, | |
| "KeyRotationImageFamily", keyRotationImageFamily, | |
| "KeyRotationImageFamilyVariant", keyRotationImageFamilyVariant, | |
| "KeyRotationValidationMethod", keyRotationValidationMethod, | |
| "KeyRotationValidationKeyData", keyRotationValidationKeyData, | |
| "KeyRotationValidationHashData", keyRotationValidationHashData, | |
| "ChipRevision", chipRevision); | |
| if (!success) { | |
| BMCWEB_LOG_ERROR << "Failed to populate RootOfTrustState"; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| if (bootloaderUpdateFailureCode != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["BootloaderUpdateFailureCode"] = | |
| *bootloaderUpdateFailureCode; | |
| } | |
| if (firmwareUpdateFailedMinor != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["FirmwareUpdateFailedMinor"] = | |
| *firmwareUpdateFailedMinor; | |
| } | |
| if (firmwareUpdateFailureCode != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["FirmwareUpdateFailureCode"] = | |
| *firmwareUpdateFailureCode; | |
| } | |
| if (resetFlags != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["ResetFlags"] = *resetFlags; | |
| } | |
| if (roInfoStrikes != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["RoInfoStrikes"] = *roInfoStrikes; | |
| } | |
| if (rwInfoStrikes != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["RwInfoStrikes"] = *rwInfoStrikes; | |
| } | |
| if (upTime != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["UpTime"] = *upTime; | |
| } | |
| if (hasPersistentPanicInfo != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["HasPersistentPanicInfo"] = | |
| *hasPersistentPanicInfo; | |
| } | |
| if (payloadConfirmFailureCode != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["Payload"]["ConfirmFailureCode"] = | |
| *payloadConfirmFailureCode; | |
| } | |
| if (payloadUpdateFailureCode != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["Payload"]["UpdateFailureCode"] = | |
| *payloadUpdateFailureCode; | |
| } | |
| if (hasValidAuthRecord != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["Authorization"]["Valid"] = | |
| *hasValidAuthRecord; | |
| } | |
| if (authRecordCapabilities != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["Authorization"]["Capabilities"] = | |
| *authRecordCapabilities; | |
| } | |
| if (secureBootEnforced != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["SecureBootEnforced"] = | |
| *secureBootEnforced; | |
| } | |
| if (keyRotationVersion != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["KeyRotation"]["Version"] = | |
| *keyRotationVersion; | |
| } | |
| if (keyRotationImageFamily != nullptr) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["KeyRotation"]["ImageFamily"] = | |
| *keyRotationImageFamily; | |
| } | |
| if (keyRotationImageFamilyVariant != nullptr) { | |
| asyncResp->res | |
| .jsonValue["Status"]["Oem"]["KeyRotation"]["ImageFamilyVariant"] = | |
| *keyRotationImageFamilyVariant; | |
| } | |
| if (keyRotationValidationMethod != nullptr) { | |
| asyncResp->res | |
| .jsonValue["Status"]["Oem"]["KeyRotation"]["ValidationMethod"] = | |
| *keyRotationValidationMethod; | |
| } | |
| if (keyRotationValidationKeyData != nullptr) { | |
| asyncResp->res | |
| .jsonValue["Status"]["Oem"]["KeyRotation"]["ValidationKeyData"] = | |
| *keyRotationValidationKeyData; | |
| } | |
| if (keyRotationValidationHashData != nullptr) { | |
| asyncResp->res | |
| .jsonValue["Status"]["Oem"]["KeyRotation"]["ValidationHashData"] = | |
| *keyRotationValidationHashData; | |
| } | |
| if (chipRevision != nullptr && !chipRevision->empty()) { | |
| asyncResp->res.jsonValue["Status"]["Oem"]["ChipRevision"] = *chipRevision; | |
| } | |
| } | |
| inline void populateRootOfTrustEntity( | |
| const std::string& /*unused*/, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const ResolvedEntity& resolvedEntity) { | |
| asyncResp->res.jsonValue["@odata.type"] = "#RootOfTrust.v1_0_0.RootOfTrust"; | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "google", "v1", "RootOfTrustCollection", resolvedEntity.id); | |
| asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; | |
| asyncResp->res.jsonValue["Id"] = resolvedEntity.id; | |
| // Need to fix this later to a stabler property. | |
| asyncResp->res.jsonValue["Name"] = resolvedEntity.id; | |
| asyncResp->res.jsonValue["Description"] = "Google Root Of Trust"; | |
| #ifdef BMCWEB_ENABLE_ALLOW_ARBITRARY_COMMAND_TO_ROT | |
| asyncResp->res.jsonValue["Actions"]["#RootOfTrust.SendCommand"]["target"] = | |
| "/google/v1/RootOfTrustCollection/" + resolvedEntity.id + | |
| "/Actions/RootOfTrust.SendCommand"; | |
| #endif | |
| asyncResp->res | |
| .jsonValue["Actions"]["#RootOfTrust.GenerateSignedCSR"]["target"] = | |
| "/google/v1/RootOfTrustCollection/" + resolvedEntity.id + | |
| "/Actions/RootOfTrust.GenerateSignedCSR"; | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["ServiceLabel"] = | |
| resolvedEntity.id; | |
| asyncResp->res.jsonValue["Location"]["PartLocation"]["LocationType"] = | |
| "Embedded"; | |
| // Only link to RW firmware inventory. | |
| // | |
| // For USB-Hoth, FirmwareInventory URI is "hoth_${hothId}_rw". | |
| // For non-USB Hoth, FirmwareInventory URI is "hoth_rw". | |
| std::string hothFirmware = resolvedEntity.id == "Hoth" | |
| ? "hoth_rw" | |
| : "hoth_" + resolvedEntity.id + "_rw"; | |
| asyncResp->res.jsonValue["Links"]["ActiveSoftwareImage"]["@odata.id"] = | |
| "/redfish/v1/UpdateService/FirmwareInventory/" + hothFirmware; | |
| // Temporary hack to get the Hoth firmware family as "Model", by stripping | |
| // the USB suffix from hothId. | |
| // | |
| // Non-USB Hoth has resolvedEntity.id="Hoth" as model name. | |
| // | |
| // Once we have SoftwareInventory 1.9.0 supported everywhere, the firmware | |
| // family should reside there and the model here should be the chip's part | |
| // number. | |
| static std::regex hothIdSuffixRe("_[0-9_]+$"); | |
| asyncResp->res.jsonValue["Model"] = | |
| std::regex_replace(resolvedEntity.id, hothIdSuffixRe, ""); | |
| // Get Hoth properties from Dbus. | |
| managedStore::ManagedObjectStoreContext context(asyncResp); | |
| managedStore::GetManagedObjectStore()->getAllProperties( | |
| resolvedEntity.service, resolvedEntity.object, | |
| "xyz.openbmc_project.Control.Hoth.State", context, | |
| [asyncResp](const boost::system::error_code ec2, | |
| const dbus::utility::DBusPropertiesMap& properties) { | |
| if (ec2) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec2; | |
| return; | |
| } | |
| populateRootOfTrustState(asyncResp, properties); | |
| }); | |
| // Get Chassis Link for Devpath from DBus | |
| managedStore::ManagedObjectStoreContext context2(asyncResp); | |
| managedStore::GetManagedObjectStore()->getAssociatedSubTreePaths( | |
| sdbusplus::message::object_path(resolvedEntity.object) / "contained_by", | |
| sdbusplus::message::object_path("/xyz/openbmc_project/inventory"), 0, | |
| redfish::chassis_utils::chassisInterfaces, context2, | |
| [asyncResp]( | |
| const boost::system::error_code& ec, | |
| const dbus::utility::MapperGetSubTreePathsResponse& chassisPaths) { | |
| if (ec) { | |
| BMCWEB_LOG_DEBUG << "DBUS response error " << ec; | |
| return; | |
| } | |
| if (!chassisPaths.empty()) { | |
| asyncResp->res.jsonValue["RelatedItem"] = nlohmann::json::array(); | |
| } | |
| for (const auto& str : chassisPaths) { | |
| std::filesystem::path chassisPath(str); | |
| nlohmann::json::object_t relatedChassis; | |
| relatedChassis["@odata.id"] = crow::utility::urlFromPieces( | |
| "redfish", "v1", "Chassis", chassisPath.filename().c_str()); | |
| asyncResp->res.jsonValue["RelatedItem"].emplace_back(relatedChassis); | |
| } | |
| }); | |
| // Fill PartLocationContext | |
| redfish::location_util::getPartLocationContext( | |
| asyncResp, "/Location"_json_pointer, | |
| resolvedEntity.object + "/contained_by"); | |
| // Get PCR values from securityd | |
| managedStore::GetManagedObjectStore()->PostDbusCallWithObjectPath( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const std::string&)>( | |
| [asyncResp](const boost::system::error_code& ec, | |
| const std::string& pcrValuesJson) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "Failed to read PCR values: " << ec.message(); | |
| return; | |
| } | |
| nlohmann::json measurementsArray = | |
| nlohmann::json::parse(pcrValuesJson, nullptr, false); | |
| if (measurementsArray.is_discarded()) { | |
| BMCWEB_LOG_ERROR << "Failed to parse PCR JSON from D-Bus"; | |
| return; | |
| } | |
| asyncResp->res.jsonValue["TPM"]["MeasurementSet"]["Measurements"] = | |
| std::move(measurementsArray); | |
| }), | |
| "xyz.openbmc_project.securityd.TpmAttestationService", | |
| "/xyz/openbmc_project/securityd/tpm_attestation_service", | |
| "xyz.openbmc_project.securityd.TpmAttestationService", "ReadPcrValues", | |
| sdbusplus::message::object_path(resolvedEntity.object)); | |
| } | |
| inline void handleRootOfTrustGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& param) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| std::string emptyCommand; | |
| resolveRoT(emptyCommand, asyncResp, param, populateRootOfTrustEntity); | |
| } | |
| inline void invocationCallback( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const boost::system::error_code& ec, | |
| const std::vector<uint8_t>& responseBytes) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "RootOfTrust.Actions.SendCommand failed: " | |
| << ec.message(); | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["CommandResponse"] = bytesToHexString(responseBytes); | |
| } | |
| inline void invokeRoTCommand( | |
| const std::string& command, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const ResolvedEntity& resolvedEntity) { | |
| std::vector<uint8_t> bytes = hexStringToBytes(command); | |
| if (bytes.empty()) { | |
| BMCWEB_LOG_DEBUG << "Invalid command: " << command; | |
| redfish::messages::actionParameterValueTypeError(command, "Command", | |
| "SendCommand"); | |
| return; | |
| } | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| [asyncResp{asyncResp}](const boost::system::error_code& ec, | |
| const std::vector<uint8_t>& responseBytes) { | |
| invocationCallback(asyncResp, ec, responseBytes); | |
| }, | |
| resolvedEntity.service, resolvedEntity.object, resolvedEntity.interface, | |
| "SendHostCommand", bytes); | |
| } | |
| inline void getSignedMeasurementsInvocationCallback( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const boost::system::error_code& ec, const std::string& responseString) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "RootOfTrust.Actions.TPMGetSignedMeasurements failed: " | |
| << ec.message(); | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["SignedMeasurements"] = responseString; | |
| } | |
| inline void invokePcrQuoteSecuritydCommand( | |
| const PcrQuoteParams& command, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const ResolvedEntity& resolvedEntity) { | |
| managedStore::GetManagedObjectStore() | |
| ->PostDbusCallWithObjectPathAndTwoStrings( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const std::string&)>( | |
| [asyncResp{asyncResp}](const boost::system::error_code& ec, | |
| const std::string& responseString) { | |
| getSignedMeasurementsInvocationCallback(asyncResp, ec, | |
| responseString); | |
| }), | |
| "xyz.openbmc_project.securityd.TpmAttestationService", | |
| "/xyz/openbmc_project/securityd/tpm_attestation_service", | |
| "xyz.openbmc_project.securityd.TpmAttestationService", "GetPcrQuote", | |
| sdbusplus::message::object_path(resolvedEntity.object), | |
| command.pcrSelectionBase64, command.nonceHex); | |
| } | |
| inline void getEkCertChainInvocationCallback( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const boost::system::error_code& ec, const std::string& responseString) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "RootOfTrust.Certificates failed: " << ec.message(); | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.type"] = "#Certificate.v1_0_0.Certificate"; | |
| asyncResp->res.jsonValue["CertUsageTypes"] = | |
| nlohmann::json::array({"Device"}); | |
| asyncResp->res.jsonValue["CertificateType"] = "DICE_FEK"; | |
| asyncResp->res.jsonValue["CertString"] = responseString; | |
| } | |
| inline void invokeGetEkCertChainSecuritydCommand( | |
| const std::string& /*unused*/, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const ResolvedEntity& resolvedEntity) { | |
| managedStore::GetManagedObjectStore()->PostDbusCallWithObjectPath( | |
| asyncResp->strand_, | |
| absl::AnyInvocable<void(const boost::system::error_code&, | |
| const std::string&)>( | |
| [asyncResp{asyncResp}](const boost::system::error_code& ec, | |
| const std::string& responseString) { | |
| getEkCertChainInvocationCallback(asyncResp, ec, responseString); | |
| }), | |
| "xyz.openbmc_project.securityd.TpmAttestationService", | |
| "/xyz/openbmc_project/securityd/tpm_attestation_service", | |
| "xyz.openbmc_project.securityd.TpmAttestationService", "GetEkCertChain", | |
| sdbusplus::message::object_path(resolvedEntity.object)); | |
| } | |
| struct GenerateSignedCsrCommand { | |
| std::string certURI; | |
| std::string nonce; | |
| std::string pcrSelection; | |
| std::string csr; | |
| }; | |
| inline void invokeGenerateSignedCSRSecuritydCommand( | |
| const GenerateSignedCsrCommand& command, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const ResolvedEntity& resolvedEntity) { | |
| (void)resolvedEntity; | |
| managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe( | |
| asyncResp->strand_, | |
| [asyncResp, certURI = command.certURI]( | |
| const boost::system::error_code& ec, const std::string& csr_string, | |
| const std::string& certify_creation_attest, | |
| const std::string& fek_signature, const std::string& creation_data, | |
| const std::string& public_key, const std::string& csr_signature) { | |
| if (ec) { | |
| BMCWEB_LOG_ERROR << "RootOfTrust.Actions.GenerateSignedCSR failed: " | |
| << ec.message(); | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| asyncResp->res.jsonValue["CSRString"] = csr_string; | |
| asyncResp->res.jsonValue["CertificateCollection"]["@odata.id"] = | |
| certURI; | |
| asyncResp->res.jsonValue["TpmEndorsement"]["CertifyCreationAttest"] = | |
| certify_creation_attest; | |
| asyncResp->res.jsonValue["TpmEndorsement"]["FekSignature"] = | |
| fek_signature; | |
| asyncResp->res.jsonValue["TpmEndorsement"]["CreationData"] = | |
| creation_data; | |
| asyncResp->res.jsonValue["TpmEndorsement"]["PublicKey"] = public_key; | |
| asyncResp->res.jsonValue["TpmEndorsement"]["CSRSignature"] = | |
| csr_signature; | |
| }, | |
| "xyz.openbmc_project.securityd.TpmAttestationService", | |
| "/xyz/openbmc_project/securityd/tpm_attestation_service", | |
| "xyz.openbmc_project.securityd.TpmAttestationService", "SignCSR", | |
| command.nonce, command.pcrSelection, command.csr); | |
| } | |
| inline std::shared_ptr<milotic_tlbmc::CredentialManager>& | |
| getCredentialManager() { | |
| static std::shared_ptr<milotic_tlbmc::CredentialManager> credentialManager; | |
| return credentialManager; | |
| } | |
| inline void setupCredentialManager( | |
| std::shared_ptr<milotic_tlbmc::CredentialManager> credentialManager) { | |
| getCredentialManager() = std::move(credentialManager); | |
| } | |
| inline void handleGenerateSignedCSRPost( | |
| App& app, const crow::Request& request, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId) { | |
| if (!redfish::setUpRedfishRoute(app, request, asyncResp)) { | |
| return; | |
| } | |
| // This RPC is routed to securityd so only Hoth is supported. | |
| if (rotId != "Hoth" && rotId != "bmc_rot") { | |
| redfish::messages::actionParameterNotSupported( | |
| asyncResp->res, "GenerateSignedCSR", "RootOfTrust"); | |
| return; | |
| } | |
| std::string city; | |
| std::string commonName; | |
| std::string country; | |
| std::string organization; | |
| std::string organizationalUnit; | |
| std::string state; | |
| std::optional<std::vector<std::string>> optAlternativeNames; | |
| nlohmann::json certificateCollection; | |
| std::string nonce; | |
| std::string pcrSelection; | |
| if (!redfish::json_util::readJsonAction( | |
| request, asyncResp->res, "AlternativeNames", optAlternativeNames, | |
| "CertificateCollection", certificateCollection, "City", city, | |
| "CommonName", commonName, "Country", country, "Nonce", nonce, | |
| "Organization", organization, "OrganizationalUnit", | |
| organizationalUnit, "PCRSelection", pcrSelection, "State", state)) { | |
| return; | |
| } | |
| std::string certURI; | |
| if (!redfish::json_util::readJson(certificateCollection, asyncResp->res, | |
| "@odata.id", certURI)) { | |
| return; | |
| } | |
| if (certURI != "/redfish/v1/Managers/bmc/Certificates" && | |
| certURI != | |
| "/redfish/v1/Managers/bmc/NetworkProtocol/HTTPS/Certificates") { | |
| redfish::messages::actionParameterNotSupported( | |
| asyncResp->res, "CertificateCollection", "GenerateSignedCSR"); | |
| return; | |
| } | |
| milotic_tlbmc::CredentialManager::CsrParams csrParams{ | |
| .country = country, | |
| .state = state, | |
| .city = city, | |
| .organization = organization, | |
| .organizational_unit = organizationalUnit, | |
| .common_name = commonName, | |
| .alternative_names = | |
| optAlternativeNames.value_or(std::vector<std::string>()), | |
| }; | |
| std::shared_ptr<milotic_tlbmc::CredentialManager> credentialManager = | |
| getCredentialManager(); | |
| if (credentialManager == nullptr) { | |
| BMCWEB_LOG_ERROR << "CredentialManager is not initialized."; | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| absl::StatusOr<std::string> csr = credentialManager->GenerateCsr(csrParams); | |
| if (!csr.ok()) { | |
| BMCWEB_LOG_ERROR << "Failed to generate CSR: " << csr.status(); | |
| redfish::messages::internalError(asyncResp->res); | |
| return; | |
| } | |
| GenerateSignedCsrCommand command{ | |
| .certURI = std::move(certURI), | |
| .nonce = std::move(nonce), | |
| .pcrSelection = std::move(pcrSelection), | |
| .csr = std::move(*csr), | |
| }; | |
| resolveRoT(command, asyncResp, rotId, | |
| invokeGenerateSignedCSRSecuritydCommand); | |
| } | |
| #ifdef BMCWEB_ENABLE_ALLOW_ARBITRARY_COMMAND_TO_ROT | |
| inline void handleRoTSendCommandPost( | |
| App& app, const crow::Request& request, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId) { | |
| if (!redfish::setUpRedfishRoute(app, request, asyncResp)) { | |
| return; | |
| } | |
| std::string command; | |
| if (!redfish::json_util::readJsonAction(request, asyncResp->res, "Command", | |
| command)) { | |
| BMCWEB_LOG_DEBUG << "Missing property Command."; | |
| redfish::messages::actionParameterMissing(asyncResp->res, "SendCommand", | |
| "Command"); | |
| return; | |
| } | |
| resolveRoT(command, asyncResp, rotId, invokeRoTCommand); | |
| } | |
| #endif | |
| inline std::optional<std::string> processAndHashNonce( | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| std::string_view hexNonce) { | |
| std::vector<uint8_t> decodedNonce = hexStringToBytes(std::string(hexNonce)); | |
| if (decodedNonce.empty() && !hexNonce.empty()) { | |
| BMCWEB_LOG_DEBUG << "Failed to hex decode provided Nonce."; | |
| redfish::messages::actionParameterValueFormatError( | |
| asyncResp->res, hexNonce, "TPMGetSignedMeasurements", | |
| "Failed to hex decode provided Nonce."); | |
| return std::nullopt; | |
| } | |
| struct UserData { | |
| uint8_t magic[4] = {0x54, 0x50, 0x4D, 0x32}; | |
| uint8_t version[2] = {0x00, 0x00}; | |
| uint8_t reserved[2] = {0x00, 0x00}; | |
| uint8_t caller_nonce[32] = {}; | |
| } __attribute__((packed)); | |
| UserData userData = {}; | |
| if (decodedNonce.size() > sizeof(userData.caller_nonce)) { | |
| BMCWEB_LOG_DEBUG << "Caller nonce is longer than expected."; | |
| redfish::messages::actionParameterValueError( | |
| asyncResp->res, "TPMGetSignedMeasurements", | |
| "Caller nonce is longer than expected."); | |
| return std::nullopt; | |
| } | |
| std::memcpy(userData.caller_nonce, decodedNonce.data(), decodedNonce.size()); | |
| EVP_MD_CTX* context = EVP_MD_CTX_new(); | |
| if (context == nullptr) { | |
| BMCWEB_LOG_DEBUG << "Failed to create EVP_MD_CTX."; | |
| redfish::messages::internalError(asyncResp->res); | |
| return std::nullopt; | |
| } | |
| unsigned char hashResult[EVP_MAX_MD_SIZE]; | |
| unsigned int lengthOfHash = 0; | |
| if (EVP_DigestInit_ex(context, EVP_sha256(), nullptr) != 1 || | |
| EVP_DigestUpdate(context, &userData, sizeof(userData)) != 1 || | |
| EVP_DigestFinal_ex(context, hashResult, &lengthOfHash) != 1) { | |
| EVP_MD_CTX_free(context); | |
| BMCWEB_LOG_DEBUG << "Failed to hash PCR quote nonce."; | |
| redfish::messages::internalError(asyncResp->res); | |
| return std::nullopt; | |
| } | |
| EVP_MD_CTX_free(context); | |
| std::vector<uint8_t> hashBytes(hashResult, hashResult + lengthOfHash); | |
| return bytesToHexString(hashBytes); | |
| } | |
| inline void handleGetSignedMeasurementsPost( | |
| App& app, const crow::Request& request, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId) { | |
| if (!redfish::setUpRedfishRoute(app, request, asyncResp)) { | |
| return; | |
| } | |
| std::optional<std::string> nonce; | |
| std::optional<std::string> pcrSelection; | |
| if (!redfish::json_util::readJsonAction(request, asyncResp->res, "Nonce", | |
| nonce, "PCRSelection", | |
| pcrSelection)) { | |
| BMCWEB_LOG_DEBUG << "Failed to parse json action."; | |
| return; | |
| } | |
| if (!nonce) { | |
| BMCWEB_LOG_DEBUG << "Missing property Nonce."; | |
| redfish::messages::actionParameterMissing( | |
| asyncResp->res, "TPMGetSignedMeasurements", "Nonce"); | |
| return; | |
| } | |
| if (!pcrSelection) { | |
| BMCWEB_LOG_DEBUG << "Missing property PCRSelection."; | |
| redfish::messages::actionParameterMissing( | |
| asyncResp->res, "TPMGetSignedMeasurements", "PCRSelection"); | |
| return; | |
| } | |
| std::optional<std::string> processedNonce = | |
| processAndHashNonce(asyncResp, *nonce); | |
| if (!processedNonce) { | |
| BMCWEB_LOG_DEBUG << "Failed to decode or hash the client nonce."; | |
| return; | |
| } | |
| PcrQuoteParams quoteParams; | |
| quoteParams.nonceHex = std::move(*processedNonce); | |
| quoteParams.pcrSelectionBase64 = *pcrSelection; | |
| resolveRoT(quoteParams, asyncResp, rotId, invokePcrQuoteSecuritydCommand); | |
| } | |
| inline void handleRootOfTrustCertificatesGet( | |
| App& app, const crow::Request& req, | |
| const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, | |
| const std::string& rotId) { | |
| if (!redfish::setUpRedfishRoute(app, req, asyncResp)) { | |
| return; | |
| } | |
| asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces( | |
| "google", "v1", "RootOfTrustCollection", rotId, "Certificates"); | |
| asyncResp->res.jsonValue["Id"] = "Certificates"; | |
| asyncResp->res.jsonValue["Name"] = "Root Of Trust Certificates"; | |
| std::string emptyCommand; | |
| resolveRoT(emptyCommand, asyncResp, rotId, | |
| invokeGetEkCertChainSecuritydCommand); | |
| } | |
| inline void requestRoutes(App& app) { | |
| BMCWEB_ROUTE(app, "/google/v1/") | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleGoogleV1Get, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleRootOfTrustCollectionGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/<str>/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleRootOfTrustGet, std::ref(app))); | |
| BMCWEB_ROUTE(app, "/google/v1/RootOfTrustCollection/<str>/Certificates/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::get)( | |
| std::bind_front(handleRootOfTrustCertificatesGet, std::ref(app))); | |
| #ifdef BMCWEB_ENABLE_ALLOW_ARBITRARY_COMMAND_TO_ROT | |
| BMCWEB_ROUTE(app, | |
| "/google/v1/RootOfTrustCollection/<str>/Actions/" | |
| "RootOfTrust.SendCommand/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handleRoTSendCommandPost, std::ref(app))); | |
| #endif | |
| BMCWEB_ROUTE(app, | |
| "/google/v1/RootOfTrustCollection/<str>/Actions/" | |
| "RootOfTrust.GenerateSignedCSR/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handleGenerateSignedCSRPost, std::ref(app))); | |
| BMCWEB_ROUTE(app, | |
| "/google/v1/RootOfTrustCollection/<str>/Actions/" | |
| "RootOfTrust.TPMGetSignedMeasurements/") | |
| .privileges({{"ConfigureManager"}}) | |
| .methods(boost::beast::http::verb::post)( | |
| std::bind_front(handleGetSignedMeasurementsPost, std::ref(app))); | |
| } | |
| } // namespace google_api | |
| } // namespace crow | |
| #endif // THIRD_PARTY_GBMCWEB_INCLUDE_GOOGLE_GOOGLE_SERVICE_ROOT_H_ |