blob: bd361e876203ad3de1d0575754aeee8b45e6df8a [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "vtpm/client/vtpm_dbus_proxy.h"
#include <memory>
#include <utility>
#include <absl/strings/str_format.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include "trunks/error_codes.h"
#include "vtpm/dbus_interface.h"
namespace {
// Use a five minute timeout because some commands on some TPM hardware can take
// a very long time. If a few lengthy operations are already in the queue, a
// subsequent command needs to wait for all of them. Timeouts are always
// possible but under normal conditions 5 minutes seems to be plenty.
const int kDBusMaxTimeout = 5 * 60 * 1000;
} // namespace
namespace vtpm {
VtpmDBusProxy::VtpmDBusProxy() : VtpmDBusProxy(/*bus=*/nullptr) {}
VtpmDBusProxy::VtpmDBusProxy(scoped_refptr<dbus::Bus> bus) : bus_(bus) {}
VtpmDBusProxy::~VtpmDBusProxy() {
if (bus_) {
bus_->ShutdownAndBlock();
}
}
bool VtpmDBusProxy::Init() {
origin_thread_id_ = base::PlatformThread::CurrentId();
if (!bus_) {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = base::MakeRefCounted<dbus::Bus>(options);
}
if (!bus_->Connect()) {
return false;
}
vtpm_proxy_ =
std::make_unique<org::chromium::VtpmProxy>(bus_, kVtpmServiceName);
if (!vtpm_proxy_) {
return false;
}
base::TimeTicks deadline = base::TimeTicks::Now() + init_timeout_;
while (!IsServiceReady(false /* force_check */) &&
base::TimeTicks::Now() < deadline) {
base::PlatformThread::Sleep(init_attempt_delay_);
}
return IsServiceReady(false /* force_check */);
}
bool VtpmDBusProxy::IsServiceReady(bool force_check) {
if (!service_ready_ || force_check) {
service_ready_ = CheckIfServiceReady();
}
return service_ready_;
}
bool VtpmDBusProxy::CheckIfServiceReady() {
if (!bus_ || !vtpm_proxy_) {
return false;
}
std::string owner = bus_->GetServiceOwnerAndBlock(kVtpmServiceName,
dbus::Bus::SUPPRESS_ERRORS);
return !owner.empty();
}
void VtpmDBusProxy::SendCommand(const std::string& command,
ResponseCallback callback) {
if (origin_thread_id_ != base::PlatformThread::CurrentId()) {
LOG(ERROR) << "Error VtpmDBusProxy cannot be shared by multiple threads.";
std::move(callback).Run(
trunks::CreateErrorResponse(trunks::TRUNKS_RC_IPC_ERROR));
return;
}
if (!IsServiceReady(false /* force_check */)) {
LOG(ERROR) << "Error VtpmDBusProxy cannot connect to vtpmd.";
std::move(callback).Run(
trunks::CreateErrorResponse(trunks::SAPI_RC_NO_CONNECTION));
return;
}
std::pair<ResponseCallback, ResponseCallback> split =
base::SplitOnceCallback(std::move(callback));
SendCommandRequest tpm_command_proto;
tpm_command_proto.set_command(command);
auto on_success = base::BindOnce(
[](ResponseCallback callback, const SendCommandResponse& response) {
std::move(callback).Run(response.response());
},
std::move(split.first));
auto on_error = base::BindOnce(&VtpmDBusProxy::OnError, GetWeakPtr(),
std::move(split.second));
vtpm_proxy_->SendCommandAsync(tpm_command_proto, std::move(on_success),
std::move(on_error), kDBusMaxTimeout);
}
void VtpmDBusProxy::OnError(ResponseCallback callback, brillo::Error* error) {
trunks::TPM_RC error_code = IsServiceReady(true /* force_check */)
? trunks::SAPI_RC_NO_RESPONSE_RECEIVED
: trunks::SAPI_RC_NO_CONNECTION;
std::move(callback).Run(trunks::CreateErrorResponse(error_code));
}
std::string VtpmDBusProxy::SendCommandAndWait(const std::string& command) {
if (origin_thread_id_ != base::PlatformThread::CurrentId()) {
LOG(ERROR) << "Error VtpmDBusProxy cannot be shared by multiple threads.";
return trunks::CreateErrorResponse(trunks::TRUNKS_RC_IPC_ERROR);
}
if (!IsServiceReady(false /* force_check */)) {
LOG(ERROR) << "Error VtpmDBusProxy cannot connect to vtpmd.";
return trunks::CreateErrorResponse(trunks::SAPI_RC_NO_CONNECTION);
}
SendCommandRequest tpm_command_proto;
tpm_command_proto.set_command(command);
brillo::ErrorPtr error;
SendCommandResponse tpm_response_proto;
if (vtpm_proxy_->SendCommand(tpm_command_proto, &tpm_response_proto, &error,
kDBusMaxTimeout)) {
return tpm_response_proto.response();
} else {
LOG(ERROR) << "VtpmProxy could not parse response: " << error->GetMessage();
trunks::TPM_RC error_code;
if (!IsServiceReady(true /* force_check */)) {
error_code = trunks::SAPI_RC_NO_CONNECTION;
} else {
error_code = trunks::SAPI_RC_MALFORMED_RESPONSE;
}
return trunks::CreateErrorResponse(error_code);
}
}
} // namespace vtpm