chromium/content/browser/smart_card/smart_card_service.cc

// 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/map_util.h"
#include "content/browser/renderer_host/render_frame_host_impl.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 "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)) {
  context_wrapper_receivers_.set_disconnect_handler(
      base::BindRepeating(&SmartCardService::OnMojoWrapperContextDisconnected,
                          base::Unretained(this)));
}

SmartCardService::~SmartCardService() {}

// 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(
          blink::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,
    ConnectCallback callback) {
  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),
                            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::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),
                          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));
}

}  // namespace content