#include "remoting/host/webauthn/remote_webauthn_native_messaging_host.h"
#include <algorithm>
#include <memory>
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "remoting/base/logging.h"
#include "remoting/host/chromoting_host_services_client.h"
#include "remoting/host/mojom/webauthn_proxy.mojom.h"
#include "remoting/host/native_messaging/native_messaging_constants.h"
#include "remoting/host/native_messaging/native_messaging_helpers.h"
#include "remoting/host/webauthn/desktop_session_type_util.h"
#include "remoting/host/webauthn/remote_webauthn_constants.h"
namespace remoting {
namespace {
constexpr base::TimeDelta kQueryRemoteStateTimeout = base::Seconds(5);
base::Value::Dict CreateWebAuthnExceptionDetailsDict(
const std::string& name,
const std::string& message) {
return base::Value::Dict()
.Set(kWebAuthnErrorNameKey, name)
.Set(kWebAuthnErrorMessageKey, message);
}
base::Value::Dict MojoErrorToErrorDict(
const mojom::WebAuthnExceptionDetailsPtr& mojo_error) {
return CreateWebAuthnExceptionDetailsDict(mojo_error->name,
mojo_error->message);
}
}
RemoteWebAuthnNativeMessagingHost::RemoteWebAuthnNativeMessagingHost(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: RemoteWebAuthnNativeMessagingHost(
std::make_unique<ChromotingHostServicesClient>(),
task_runner) {}
RemoteWebAuthnNativeMessagingHost::RemoteWebAuthnNativeMessagingHost(
std::unique_ptr<ChromotingHostServicesProvider> host_service_api_client,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner),
host_service_api_client_(std::move(host_service_api_client)) {
request_cancellers_.set_disconnect_handler(base::BindRepeating(
&RemoteWebAuthnNativeMessagingHost::OnRequestCancellerDisconnected,
base::Unretained(this)));
}
RemoteWebAuthnNativeMessagingHost::~RemoteWebAuthnNativeMessagingHost() {
DCHECK(task_runner_->BelongsToCurrentThread());
#if !BUILDFLAG(IS_CHROMEOS)
log_message_handler_->set_log_synchronously_if_possible(true);
#endif
if (!id_to_request_canceller_.empty()) {
LOG(WARNING) << id_to_request_canceller_.size()
<< "Requests are still pending at destruction.";
}
HOST_LOG << "Remote WebAuthn native messaging host is being terminated";
}
void RemoteWebAuthnNativeMessagingHost::OnMessage(const std::string& message) {
DCHECK(task_runner_->BelongsToCurrentThread());
std::string type;
base::Value::Dict request;
if (!ParseNativeMessageJson(message, type, request)) {
return;
}
std::optional<base::Value::Dict> response =
CreateNativeMessageResponse(request);
if (!response.has_value()) {
return;
}
if (type == kHelloMessage) {
ProcessHello(std::move(*response));
return;
} else if (type == kGetRemoteStateMessageType) {
ProcessGetRemoteState(std::move(*response));
return;
}
void (RemoteWebAuthnNativeMessagingHost::*fn)(
const base::Value::Dict& request, base::Value::Dict response);
if (type == kIsUvpaaMessageType) {
fn = &RemoteWebAuthnNativeMessagingHost::ProcessIsUvpaa;
} else if (type == kCreateMessageType) {
fn = &RemoteWebAuthnNativeMessagingHost::ProcessCreate;
} else if (type == kGetMessageType) {
fn = &RemoteWebAuthnNativeMessagingHost::ProcessGet;
} else if (type == kCancelMessageType) {
fn = &RemoteWebAuthnNativeMessagingHost::ProcessCancel;
} else {
LOG(ERROR) << "Unsupported request type: " << type;
return;
}
EnsureIpcConnectionThenProcess(base::BindOnce(
fn, base::Unretained(this), std::move(request), std::move(*response)));
}
void RemoteWebAuthnNativeMessagingHost::Start(
extensions::NativeMessageHost::Client* client) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_ = client;
#if !BUILDFLAG(IS_CHROMEOS)
log_message_handler_ =
std::make_unique<LogMessageHandler>(base::BindRepeating(
&RemoteWebAuthnNativeMessagingHost::SendMessageToClient,
base::Unretained(this)));
#endif
HOST_LOG << "Remote WebAuthn native messaging host has started";
}
scoped_refptr<base::SingleThreadTaskRunner>
RemoteWebAuthnNativeMessagingHost::task_runner() const {
return task_runner_;
}
void RemoteWebAuthnNativeMessagingHost::ProcessHello(
base::Value::Dict response) {
DCHECK(task_runner_->BelongsToCurrentThread());
ProcessNativeMessageHelloResponse(response);
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::ProcessIsUvpaa(
const base::Value::Dict& request,
base::Value::Dict response) {
DCHECK(task_runner_->BelongsToCurrentThread());
remote_->IsUserVerifyingPlatformAuthenticatorAvailable(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnIsUvpaaResponse,
base::Unretained(this), std::move(response)));
}
void RemoteWebAuthnNativeMessagingHost::ProcessCreate(
const base::Value::Dict& request,
base::Value::Dict response) {
DCHECK(task_runner_->BelongsToCurrentThread());
const base::Value* message_id = FindMessageIdOrSendError(response);
if (!message_id) {
return;
}
const std::string* request_data =
FindRequestDataOrSendError(request, kCreateRequestDataKey, response);
if (!request_data) {
return;
}
remote_->Create(
*request_data, AddRequestCanceller(message_id->Clone()),
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnCreateResponse,
base::Unretained(this), std::move(response)));
}
void RemoteWebAuthnNativeMessagingHost::ProcessGet(
const base::Value::Dict& request,
base::Value::Dict response) {
DCHECK(task_runner_->BelongsToCurrentThread());
const base::Value* message_id = FindMessageIdOrSendError(response);
if (!message_id) {
return;
}
const std::string* request_data =
FindRequestDataOrSendError(request, kGetRequestDataKey, response);
if (!request_data) {
return;
}
remote_->Get(*request_data, AddRequestCanceller(message_id->Clone()),
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnGetResponse,
base::Unretained(this), std::move(response)));
}
void RemoteWebAuthnNativeMessagingHost::ProcessCancel(
const base::Value::Dict& request,
base::Value::Dict response) {
const base::Value* message_id = request.Find(kMessageId);
if (!message_id) {
LOG(ERROR) << "Message ID not found in cancel request.";
response.Set(kCancelResponseWasCanceledKey, false);
SendMessageToClient(std::move(response));
return;
}
auto it = id_to_request_canceller_.find(*message_id);
if (it == id_to_request_canceller_.end()) {
LOG(ERROR) << "No cancelable request found for message ID " << *message_id;
response.Set(kCancelResponseWasCanceledKey, false);
SendMessageToClient(std::move(response));
return;
}
auto* canceller = request_cancellers_.Get(it->second);
CHECK(canceller);
canceller->Cancel(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnCancelResponse,
base::Unretained(this), std::move(response)));
}
void RemoteWebAuthnNativeMessagingHost::ProcessGetRemoteState(
base::Value::Dict response) {
DCHECK(task_runner_->BelongsToCurrentThread());
pending_get_remote_state_requests_.push(std::move(response));
if (pending_get_remote_state_requests_.size() == 1) {
QueryNextRemoteState();
}
}
void RemoteWebAuthnNativeMessagingHost::OnQueryVersionResult(uint32_t version) {
DCHECK(task_runner_->BelongsToCurrentThread());
query_remote_state_timeout_timer_.Stop();
is_connection_valid_ = true;
while (!pending_process_requests_.empty()) {
std::move(pending_process_requests_.front()).Run();
pending_process_requests_.pop();
}
if (!pending_get_remote_state_requests_.empty()) {
SendNextRemoteState(true);
}
}
void RemoteWebAuthnNativeMessagingHost::OnQueryNextRemoteStateTimeout() {
LOG(ERROR) << "Timed out waiting for the response of QueryVersion";
OnIpcDisconnected();
}
void RemoteWebAuthnNativeMessagingHost::OnIpcDisconnected() {
DCHECK(task_runner_->BelongsToCurrentThread());
query_remote_state_timeout_timer_.Stop();
is_connection_valid_ = false;
remote_.reset();
base::queue<base::OnceClosure> empty_queue;
pending_process_requests_.swap(empty_queue);
id_to_request_canceller_.clear();
request_cancellers_.Clear();
if (!pending_get_remote_state_requests_.empty()) {
SendNextRemoteState(false);
} else {
SendClientDisconnectedMessage();
}
}
void RemoteWebAuthnNativeMessagingHost::OnIsUvpaaResponse(
base::Value::Dict response,
bool is_available) {
DCHECK(task_runner_->BelongsToCurrentThread());
response.Set(kIsUvpaaResponseIsAvailableKey, is_available);
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::OnCreateResponse(
base::Value::Dict response,
mojom::WebAuthnCreateResponsePtr remote_response) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!remote_response.is_null()) {
switch (remote_response->which()) {
case mojom::WebAuthnCreateResponse::Tag::kErrorDetails:
response.Set(
kWebAuthnErrorKey,
MojoErrorToErrorDict(remote_response->get_error_details()));
break;
case mojom::WebAuthnCreateResponse::Tag::kResponseData:
response.Set(kCreateResponseDataKey,
remote_response->get_response_data());
break;
default:
NOTREACHED() << "Unexpected create response tag: "
<< static_cast<uint32_t>(remote_response->which());
}
}
const base::Value* message_id = response.Find(kMessageId);
if (message_id) {
RemoveRequestCancellerByMessageId(*message_id);
}
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::OnGetResponse(
base::Value::Dict response,
mojom::WebAuthnGetResponsePtr remote_response) {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!remote_response.is_null()) {
switch (remote_response->which()) {
case mojom::WebAuthnGetResponse::Tag::kErrorDetails:
response.Set(
kWebAuthnErrorKey,
MojoErrorToErrorDict(remote_response->get_error_details()));
break;
case mojom::WebAuthnGetResponse::Tag::kResponseData:
response.Set(kGetResponseDataKey, remote_response->get_response_data());
break;
default:
NOTREACHED() << "Unexpected get response tag: "
<< static_cast<uint32_t>(remote_response->which());
}
}
const base::Value* message_id = response.Find(kMessageId);
if (message_id) {
RemoveRequestCancellerByMessageId(*message_id);
}
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::OnCancelResponse(
base::Value::Dict response,
bool was_canceled) {
DCHECK(task_runner_->BelongsToCurrentThread());
const base::Value* message_id = response.Find(kMessageId);
if (message_id) {
RemoveRequestCancellerByMessageId(*message_id);
}
response.Set(kCancelResponseWasCanceledKey, was_canceled);
SendMessageToClient(std::move(response));
}
void RemoteWebAuthnNativeMessagingHost::QueryNextRemoteState() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (!EnsureRemoteBound()) {
OnIpcDisconnected();
return;
}
query_remote_state_timeout_timer_.Start(
FROM_HERE, kQueryRemoteStateTimeout, this,
&RemoteWebAuthnNativeMessagingHost::OnQueryNextRemoteStateTimeout);
remote_.QueryVersion(
base::BindOnce(&RemoteWebAuthnNativeMessagingHost::OnQueryVersionResult,
base::Unretained(this)));
}
void RemoteWebAuthnNativeMessagingHost::SendNextRemoteState(bool is_remoted) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!pending_get_remote_state_requests_.empty());
auto response = std::move(pending_get_remote_state_requests_.front());
pending_get_remote_state_requests_.pop();
response.Set(kGetRemoteStateResponseIsRemotedKey, is_remoted);
std::string desktop_session_type;
switch (GetDesktopSessionType()) {
case DesktopSessionType::UNSPECIFIED:
desktop_session_type = "unspecified";
break;
case DesktopSessionType::REMOTE_ONLY:
desktop_session_type = "remote-only";
break;
case DesktopSessionType::LOCAL_ONLY:
desktop_session_type = "local-only";
break;
}
response.Set(kGetRemoteStateResponseDesktopSessionTypeKey,
desktop_session_type);
SendMessageToClient(std::move(response));
if (!pending_get_remote_state_requests_.empty()) {
QueryNextRemoteState();
}
}
void RemoteWebAuthnNativeMessagingHost::EnsureIpcConnectionThenProcess(
base::OnceClosure closure) {
DCHECK(task_runner_->BelongsToCurrentThread());
DCHECK(!closure.is_null());
if (is_connection_valid_) {
std::move(closure).Run();
return;
}
pending_process_requests_.push(std::move(closure));
QueryNextRemoteState();
}
bool RemoteWebAuthnNativeMessagingHost::EnsureRemoteBound() {
DCHECK(task_runner_->BelongsToCurrentThread());
if (remote_.is_bound()) {
return true;
}
auto disconnect_handler =
base::BindRepeating(&RemoteWebAuthnNativeMessagingHost::OnIpcDisconnected,
base::Unretained(this));
host_service_api_client_->set_disconnect_handler(disconnect_handler);
auto* api = host_service_api_client_->GetSessionServices();
if (!api) {
return false;
}
api->BindWebAuthnProxy(remote_.BindNewPipeAndPassReceiver());
remote_.set_disconnect_handler(disconnect_handler);
return true;
}
void RemoteWebAuthnNativeMessagingHost::SendMessageToClient(
base::Value::Dict message) {
DCHECK(task_runner_->BelongsToCurrentThread());
std::optional<std::string> message_json = base::WriteJson(message);
if (!message_json.has_value()) {
LOG(ERROR) << "Failed to write message to JSON";
return;
}
client_->PostMessageFromNativeHost(message_json.value());
}
const base::Value* RemoteWebAuthnNativeMessagingHost::FindMessageIdOrSendError(
base::Value::Dict& response) {
const base::Value* message_id = response.Find(kMessageId);
if (message_id) {
return message_id;
}
response.Set(kWebAuthnErrorKey,
CreateWebAuthnExceptionDetailsDict(
"NotSupportedError", "Message ID not found in request."));
SendMessageToClient(std::move(response));
return nullptr;
}
const std::string*
RemoteWebAuthnNativeMessagingHost::FindRequestDataOrSendError(
const base::Value::Dict& request,
const std::string& request_data_key,
base::Value::Dict& response) {
const std::string* request_data = request.FindString(request_data_key);
if (request_data) {
return request_data;
}
response.Set(
kWebAuthnErrorKey,
CreateWebAuthnExceptionDetailsDict(
"NotSupportedError", "Request data not found in the request."));
SendMessageToClient(std::move(response));
return nullptr;
}
mojo::PendingReceiver<mojom::WebAuthnRequestCanceller>
RemoteWebAuthnNativeMessagingHost::AddRequestCanceller(base::Value message_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
mojo::PendingRemote<mojom::WebAuthnRequestCanceller>
pending_request_canceller;
auto request_canceller_receiver =
pending_request_canceller.InitWithNewPipeAndPassReceiver();
id_to_request_canceller_.emplace(
std::move(message_id),
request_cancellers_.Add(std::move(pending_request_canceller)));
return request_canceller_receiver;
}
void RemoteWebAuthnNativeMessagingHost::RemoveRequestCancellerByMessageId(
const base::Value& message_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
auto it = id_to_request_canceller_.find(message_id);
if (it != id_to_request_canceller_.end()) {
request_cancellers_.Remove(it->second);
id_to_request_canceller_.erase(it);
} else {
VLOG(1) << "Cannot find receiver for message ID " << message_id;
}
}
void RemoteWebAuthnNativeMessagingHost::OnRequestCancellerDisconnected(
mojo::RemoteSetElementId disconnecting_canceller) {
DCHECK(task_runner_->BelongsToCurrentThread());
auto it = std::ranges::find(id_to_request_canceller_, disconnecting_canceller,
&IdToRequestMap::value_type::second);
if (it != id_to_request_canceller_.end()) {
id_to_request_canceller_.erase(it);
}
if (on_request_canceller_disconnected_for_testing_) {
on_request_canceller_disconnected_for_testing_.Run();
}
}
void RemoteWebAuthnNativeMessagingHost::SendClientDisconnectedMessage() {
DCHECK(task_runner_->BelongsToCurrentThread());
base::Value::Dict message;
message.Set(kMessageType, kClientDisconnectedMessageType);
SendMessageToClient(std::move(message));
}
}