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