blob: 20337106e8a1eb2f38167b48269b75b6bd70bb9f [file]
#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>
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
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 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);
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;
}
}
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_