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