blob: a44287f72215c8e95dc686a9f1baa5739d958c44 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/smart_card/smart_card_service.h"
#include "base/check_deref.h"
#include "base/containers/extend.h"
#include "base/containers/map_util.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/smart_card/smart_card_histograms.h"
#include "content/public/browser/isolated_context_util.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/smart_card_delegate.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/smart_card.mojom.h"
#include "third_party/blink/public/common/features_generated.h"
namespace content {
using device::mojom::SmartCardConnectResult;
using device::mojom::SmartCardCreateContextResult;
using device::mojom::SmartCardError;
namespace {
SmartCardDelegate& GetSmartCardDelegate() {
return CHECK_DEREF(GetContentClient()->browser()->GetSmartCardDelegate());
}
} // namespace
SmartCardService::SmartCardService(
RenderFrameHost& render_frame_host,
mojo::PendingReceiver<blink::mojom::SmartCardService> receiver,
mojo::PendingRemote<device::mojom::SmartCardContextFactory> context_factory)
: DocumentService(render_frame_host, std::move(receiver)),
context_factory_(std::move(context_factory)) {
// base::Unretained is safe in below cases, as receiver sets, being members,
// are bound to the lifetime of this object.
context_wrapper_receivers_.set_disconnect_handler(
base::BindRepeating(&SmartCardService::OnMojoWrapperContextDisconnected,
base::Unretained(this)));
connection_watcher_receivers_.set_disconnect_handler(base::BindRepeating(
&SmartCardService::OnMojoWatcherPipeClosed, base::Unretained(this)));
GetSmartCardDelegate().AddObserver(render_frame_host, this);
}
SmartCardService::~SmartCardService() {
GetSmartCardDelegate().RemoveObserver(render_frame_host(), this);
}
// static
void SmartCardService::Create(
RenderFrameHost* render_frame_host,
mojo::PendingReceiver<blink::mojom::SmartCardService> receiver) {
BrowserContext* browser_context = render_frame_host->GetBrowserContext();
DCHECK(browser_context);
if (!base::FeatureList::IsEnabled(blink::features::kSmartCard)) {
mojo::ReportBadMessage("The SmartCard feature is disabled.");
return;
}
if (!render_frame_host->IsFeatureEnabled(
network::mojom::PermissionsPolicyFeature::kSmartCard)) {
mojo::ReportBadMessage(
"Access to the feature \"smart-card\" is disallowed by permissions "
"policy.");
return;
}
if (!HasIsolatedContextCapability(render_frame_host)) {
mojo::ReportBadMessage(
"Frame is not sufficiently isolated to use the Smart Card API.");
return;
}
SmartCardDelegate* delegate =
GetContentClient()->browser()->GetSmartCardDelegate();
if (!delegate) {
mojo::ReportBadMessage("Browser has no Smart Card delegate.");
return;
}
new SmartCardService(*render_frame_host, std::move(receiver),
delegate->GetSmartCardContextFactory(*browser_context));
}
void SmartCardService::CreateContext(CreateContextCallback callback) {
if (GetSmartCardDelegate().IsPermissionBlocked(render_frame_host())) {
std::move(callback).Run(SmartCardCreateContextResult::NewError(
SmartCardError::kPermissionDenied));
return;
}
context_factory_->CreateContext(
base::BindOnce(&SmartCardService::OnContextCreated,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SmartCardService::ListReaders(ListReadersCallback callback) {
mojo::ReceiverId context_wrapper_id =
context_wrapper_receivers_.current_receiver();
mojo::Remote<SmartCardContext>& context_remote =
CHECK_DEREF(base::FindOrNull(context_remotes_, context_wrapper_id));
context_remote->ListReaders(
base::BindOnce(&SmartCardService::OnListReadersResult,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void SmartCardService::GetStatusChange(
base::TimeDelta timeout,
std::vector<device::mojom::SmartCardReaderStateInPtr> reader_states,
GetStatusChangeCallback callback) {
mojo::ReceiverId context_wrapper_id =
context_wrapper_receivers_.current_receiver();
mojo::Remote<SmartCardContext>& context_remote =
CHECK_DEREF(base::FindOrNull(context_remotes_, context_wrapper_id));
context_remote->GetStatusChange(timeout, std::move(reader_states),
std::move(callback));
}
void SmartCardService::Cancel(CancelCallback callback) {
mojo::ReceiverId context_wrapper_id =
context_wrapper_receivers_.current_receiver();
mojo::Remote<SmartCardContext>& context_remote =
CHECK_DEREF(base::FindOrNull(context_remotes_, context_wrapper_id));
context_remote->Cancel(std::move(callback));
}
void SmartCardService::Connect(
const std::string& reader,
device::mojom::SmartCardShareMode share_mode,
device::mojom::SmartCardProtocolsPtr preferred_protocols,
mojo::PendingRemote<device::mojom::SmartCardConnectionWatcher>
connection_watcher,
ConnectCallback callback) {
// This argument should be passed only from here to the platform-specific
// component - not to here.
CHECK(!mojo::Remote(std::move(connection_watcher)).is_bound());
SmartCardDelegate& delegate = GetSmartCardDelegate();
mojo::ReceiverId context_wrapper_id =
context_wrapper_receivers_.current_receiver();
if (delegate.HasReaderPermission(render_frame_host(), reader)) {
mojo::Remote<SmartCardContext>& context_remote =
CHECK_DEREF(base::FindOrNull(context_remotes_, context_wrapper_id));
context_remote->Connect(reader, share_mode, std::move(preferred_protocols),
GetNewConnectionWatcher(reader),
std::move(callback));
return;
}
if (!valid_reader_names_.contains(reader)) {
// Avoid showing the user a string that comes directly from the application.
//
// This will also block the case where an application asks to connect to a
// reader without first checking whether it exists in the system (via
// ListReaders). But no sane application should be doing this anyway. If
// this turns out to be a problem we will have to do a ListReaders() here on
// our own before coming to a decision.
std::move(callback).Run(
SmartCardConnectResult::NewError(SmartCardError::kPermissionDenied));
return;
}
delegate.RequestReaderPermission(
render_frame_host(), reader,
base::BindOnce(&SmartCardService::OnReaderPermissionResult,
weak_ptr_factory_.GetWeakPtr(), context_wrapper_id, reader,
share_mode, std::move(preferred_protocols),
std::move(callback)));
}
void SmartCardService::NotifyConnectionUsed() {
GetSmartCardDelegate().NotifyConnectionUsed(render_frame_host());
}
void SmartCardService::OnReaderPermissionResult(
mojo::ReceiverId context_wrapper_id,
const std::string& reader,
device::mojom::SmartCardShareMode share_mode,
device::mojom::SmartCardProtocolsPtr preferred_protocols,
ConnectCallback callback,
bool granted) {
auto it = context_remotes_.find(context_wrapper_id);
if (it == context_remotes_.end()) {
// Can happen if the renderer has dropped the wrapper remote in the
// meantime.
std::move(callback).Run(
SmartCardConnectResult::NewError(SmartCardError::kUnexpected));
return;
}
if (!granted) {
std::move(callback).Run(
SmartCardConnectResult::NewError(SmartCardError::kPermissionDenied));
return;
}
mojo::Remote<SmartCardContext>& context_remote = it->second;
context_remote->Connect(reader, share_mode, std::move(preferred_protocols),
GetNewConnectionWatcher(reader), std::move(callback));
}
void SmartCardService::OnMojoWrapperContextDisconnected() {
mojo::ReceiverId context_wrapper_id =
context_wrapper_receivers_.current_receiver();
context_remotes_.erase(context_wrapper_id);
}
void SmartCardService::OnListReadersResult(
ListReadersCallback callback,
device::mojom::SmartCardListReadersResultPtr result) {
if (result->is_readers()) {
// Update our set of valid reader names.
// Note that this is larger than the set of "currently available readers".
for (const auto& reader : result->get_readers()) {
valid_reader_names_.insert(reader);
}
}
// And finally forward the result to the original caller.
std::move(callback).Run(std::move(result));
}
void SmartCardService::OnContextCreated(
CreateContextCallback callback,
::device::mojom::SmartCardCreateContextResultPtr result) {
if (result->is_error()) {
std::move(callback).Run(std::move(result));
return;
}
// Wrap the context so that we can do permission checking/prompting before
// forwarding a call where appropriate.
mojo::PendingRemote<device::mojom::SmartCardContext> wrapper_remote;
mojo::ReceiverId context_wrapper_id = context_wrapper_receivers_.Add(
this, wrapper_remote.InitWithNewPipeAndPassReceiver());
context_remotes_[context_wrapper_id] =
mojo::Remote<SmartCardContext>(std::move(result->get_context()));
result->set_context(std::move(wrapper_remote));
std::move(callback).Run(std::move(result));
}
mojo::PendingRemote<device::mojom::SmartCardConnectionWatcher>
SmartCardService::GetNewConnectionWatcher(const std::string& reader) {
mojo::PendingRemote<device::mojom::SmartCardConnectionWatcher>
ceaseless_watcher;
auto receiver_id = connection_watcher_receivers_.Add(
this, ceaseless_watcher.InitWithNewPipeAndPassReceiver());
connection_watchers_per_reader_[reader].insert(receiver_id);
reader_names_per_watcher_[receiver_id] = reader;
return ceaseless_watcher;
}
void SmartCardService::OnMojoWatcherPipeClosed() {
auto receiver_id = connection_watcher_receivers_.current_receiver();
auto reader_it = reader_names_per_watcher_.find(receiver_id);
if (reader_it == reader_names_per_watcher_.end()) {
return;
}
const std::string& reader = reader_it->second;
auto reader_ids_it = connection_watchers_per_reader_.find(reader);
if (reader_ids_it == connection_watchers_per_reader_.end()) {
return;
}
reader_ids_it->second.erase(receiver_id);
reader_names_per_watcher_.erase(reader_it);
if (reader_names_per_watcher_.empty()) {
GetSmartCardDelegate().NotifyLastConnectionLost(render_frame_host());
}
RecordSmartCardConnectionClosedReason(
SmartCardConnectionClosedReason::kSmartCardConnectionClosedDisconnect);
}
void SmartCardService::OnPermissionRevoked(const url::Origin& origin) {
if (render_frame_host().GetLastCommittedOrigin() != origin) {
return;
}
auto& delegate = GetSmartCardDelegate();
std::vector<mojo::ReceiverId> watchers_of_connections_to_remove;
for (const auto& [reader_name, receiver_ids] :
connection_watchers_per_reader_) {
if (delegate.HasReaderPermission(render_frame_host(), reader_name)) {
continue;
}
base::Extend(watchers_of_connections_to_remove, receiver_ids);
}
for (const auto& receiver_id : watchers_of_connections_to_remove) {
connection_watcher_receivers_.Remove(receiver_id);
RecordSmartCardConnectionClosedReason(
SmartCardConnectionClosedReason::
kSmartCardConnectionClosedPermissionRevoked);
}
}
} // namespace content