Commit 2538ed2a authored by David Van Cleve's avatar David Van Cleve Committed by Chromium LUCI CQ

Trust Tokens: Finish up //content wiring for local trust token issuance

We've now landed code that
(1) Adds a generic //content-to-embedder API for requesting Trust Tokens
issuance satisfied by on-device providers like system services
(2) Expands the network service code responsible for the general Trust
Tokens protocol flow to handle cases where issuance operations get
delegated to on-device providers via NetworkContextClient

This CL wires StoragePartitionImpl to bind a Mojo remote that can talk
to the //content-to-embedder API (1), so that it can do something useful
when it receives requests from the network service in step (2), rather
than its current behavior of always failing the requests it receives.

Test: Adds unit tests covering the new StoragePartitionImpl logic
Change-Id: Iecd48e51ac827534fcac3073f0ce22bef4b2d8b4
Fixed: 1130273
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2493540
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarMatt Falkenhagen <falken@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832564}
parent 0d360f55
......@@ -120,6 +120,7 @@ source_set("browser") {
"//content/public/browser:proto",
"//content/public/common:common_sources",
"//content/public/common:content_descriptor_keys",
"//content/public/common:trust_tokens_mojo_bindings",
"//content/public/common/zygote:buildflags",
"//crypto",
"//device/base",
......
......@@ -117,7 +117,9 @@
#include "third_party/blink/public/mojom/quota/quota_types.mojom.h"
#if defined(OS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "net/android/http_auth_negotiate_android.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#else
#include "content/browser/host_zoom_map_impl.h"
#endif // defined(OS_ANDROID)
......@@ -1859,12 +1861,45 @@ void StoragePartitionImpl::OnTrustAnchorUsed() {
void StoragePartitionImpl::OnTrustTokenIssuanceDivertedToSystem(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
OnTrustTokenIssuanceDivertedToSystemCallback callback) {
// TODO(crbug.com/1130272): Implement logic that allows executing Trust
// Tokens operations when available, rather than failing unconditionally.
auto response = network::mojom::FulfillTrustTokenIssuanceAnswer::New();
response->status =
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
std::move(callback).Run(std::move(response));
if (!local_trust_token_fulfiller_ &&
!attempted_to_bind_local_trust_token_fulfiller_) {
attempted_to_bind_local_trust_token_fulfiller_ = true;
ProvisionallyBindUnboundLocalTrustTokenFulfillerIfSupportedBySystem();
}
if (!local_trust_token_fulfiller_) {
auto response = network::mojom::FulfillTrustTokenIssuanceAnswer::New();
response->status =
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
std::move(callback).Run(std::move(response));
return;
}
int callback_key = next_pending_trust_token_issuance_callback_key_++;
pending_trust_token_issuance_callbacks_.emplace(callback_key,
std::move(callback));
local_trust_token_fulfiller_->FulfillTrustTokenIssuance(
std::move(request),
base::BindOnce(
[](int callback_key, base::WeakPtr<StoragePartitionImpl> partition,
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
if (!partition)
return;
if (!base::Contains(
partition->pending_trust_token_issuance_callbacks_,
callback_key)) {
return;
}
auto callback =
std::move(partition->pending_trust_token_issuance_callbacks_.at(
callback_key));
partition->pending_trust_token_issuance_callbacks_.erase(
callback_key);
std::move(callback).Run(std::move(answer));
},
callback_key, weak_factory_.GetWeakPtr()));
}
void StoragePartitionImpl::ClearDataImpl(
......@@ -2551,4 +2586,34 @@ StoragePartitionImpl::CreateCookieAccessObserverForServiceWorker() {
return remote;
}
void StoragePartitionImpl::OnLocalTrustTokenFulfillerConnectionError() {
auto not_found_answer =
network::mojom::FulfillTrustTokenIssuanceAnswer::New();
// kNotFound represents a case where the local system was unable to provide an
// answer to the request.
not_found_answer->status =
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound;
for (auto& key_and_callback : pending_trust_token_issuance_callbacks_)
std::move(key_and_callback.second).Run(not_found_answer.Clone());
pending_trust_token_issuance_callbacks_.clear();
}
void StoragePartitionImpl::
ProvisionallyBindUnboundLocalTrustTokenFulfillerIfSupportedBySystem() {
if (local_trust_token_fulfiller_)
return;
#if defined(OS_ANDROID)
GetGlobalJavaInterfaces()->GetInterface(
local_trust_token_fulfiller_.BindNewPipeAndPassReceiver());
#endif // defined(OS_ANDROID)
if (local_trust_token_fulfiller_) {
local_trust_token_fulfiller_.set_disconnect_handler(base::BindOnce(
&StoragePartitionImpl::OnLocalTrustTokenFulfillerConnectionError,
weak_factory_.GetWeakPtr()));
}
}
} // namespace content
......@@ -12,6 +12,7 @@
#include <string>
#include "base/compiler_specific.h"
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
......@@ -46,6 +47,7 @@
#include "content/browser/worker_host/shared_worker_service_impl.h"
#include "content/common/content_export.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/trust_tokens.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
......@@ -362,6 +364,11 @@ class CONTENT_EXPORT StoragePartitionImpl
std::vector<std::string> GetCorsExemptHeaderList();
// Empties the collection |pending_trust_token_issuance_callbacks_| of
// callbacks pending responses from |local_trust_token_fulfiller_|, providing
// each callback a suitable error response.
void OnLocalTrustTokenFulfillerConnectionError();
private:
class DataDeletionHelper;
class QuotaManagedDataDeletionHelper;
......@@ -473,6 +480,17 @@ class CONTENT_EXPORT StoragePartitionImpl
IndexedDBContextImpl* GetIndexedDBContextInternal();
// If |local_trust_token_fulfiller_| is bound, returns immediately.
//
// Otherwise, if it's supported by the environment, attempts to bind
// |local_trust_token_fulfiller_|. In this case,
// local_trust_token_fulfiller_.is_bound() will return true after this method
// returns. This does NOT guarantee that |local_trust_token_fulfiller_| will
// ever find an implementation of the interface to talk to. If downstream code
// rejects the connection, this will be reflected asynchronously by a call to
// OnLocalTrustTokenFulfillerConnectionError.
void ProvisionallyBindUnboundLocalTrustTokenFulfillerIfSupportedBySystem();
// Raw pointer that should always be valid. The BrowserContext owns the
// StoragePartitionImplMap which then owns StoragePartitionImpl. When the
// BrowserContext is destroyed, |this| will be destroyed too.
......@@ -596,6 +614,24 @@ class CONTENT_EXPORT StoragePartitionImpl
mojo::UniqueReceiverSet<network::mojom::CookieAccessObserver>
service_worker_cookie_observers_;
// |local_trust_token_fulfiller_| provides responses to certain Trust Tokens
// operations, for instance via the content embedder calling into a system
// service ("platform-provided Trust Tokens operations").
//
// Binding the interface might not succeed, and failures could involve costly
// operations in other processes, so we attempt at most once to bind it.
bool attempted_to_bind_local_trust_token_fulfiller_ = false;
mojo::Remote<mojom::LocalTrustTokenFulfiller> local_trust_token_fulfiller_;
// Maintain pending callbacks provided to OnTrustTokenIssuanceDivertedToSystem
// so that we can provide them error responses if the Mojo pipe breaks. One
// likely common case where this happens is when the content embedder declines
// to provide an implementation when we attempt to bind the
// LocalTrustTokenFulfiller interface, for instance because the embedder
// hasn't implemented support for mediating Trust Tokens operations.
base::flat_map<int, OnTrustTokenIssuanceDivertedToSystemCallback>
pending_trust_token_issuance_callbacks_;
int next_pending_trust_token_issuance_callback_key_ = 0;
base::WeakPtrFactory<StoragePartitionImpl> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(StoragePartitionImpl);
......
......@@ -22,6 +22,7 @@
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/services/storage/dom_storage/async_dom_storage_database.h"
#include "components/services/storage/dom_storage/dom_storage_database.h"
#include "components/services/storage/dom_storage/local_storage_database.pb.h"
......@@ -38,6 +39,7 @@
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_usage_info.h"
#include "content/public/common/content_features.h"
#include "content/public/common/trust_tokens.mojom.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_utils.h"
......@@ -68,6 +70,11 @@
#include "url/origin.h"
#endif // BUILDFLAG(ENABLE_PLUGINS)
#if defined(OS_ANDROID)
#include "content/public/browser/android/java_interfaces.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#endif // defined(OS_ANDROID)
using net::CanonicalCookie;
using CookieDeletionFilter = network::mojom::CookieDeletionFilter;
using CookieDeletionFilterPtr = network::mojom::CookieDeletionFilterPtr;
......@@ -1883,4 +1890,191 @@ TEST_F(StoragePartitionImplTest, DataRemovalObserver) {
kBeginTime, kEndTime, base::DoNothing());
}
namespace {
class MockLocalTrustTokenFulfiller : public mojom::LocalTrustTokenFulfiller {
public:
enum IgnoreRequestsTag { kIgnoreRequestsIndefinitely };
explicit MockLocalTrustTokenFulfiller(IgnoreRequestsTag) {}
explicit MockLocalTrustTokenFulfiller(
const network::mojom::FulfillTrustTokenIssuanceAnswerPtr& answer)
: answer_(answer.Clone()) {}
void FulfillTrustTokenIssuance(
network::mojom::FulfillTrustTokenIssuanceRequestPtr request,
FulfillTrustTokenIssuanceCallback callback) override {
if (answer_)
std::move(callback).Run(answer_.Clone());
// Otherwise, this class was constructed with an IgnoreRequestsTag; drop the
// request.
}
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::LocalTrustTokenFulfiller>(
std::move(handle)));
}
private:
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer_;
mojo::Receiver<mojom::LocalTrustTokenFulfiller> receiver_{this};
};
} // namespace
#if defined(OS_ANDROID)
TEST_F(StoragePartitionImplTest, BindsTrustTokenFulfiller) {
auto expected_answer = network::mojom::FulfillTrustTokenIssuanceAnswer::New();
expected_answer->status =
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kOk;
expected_answer->response = "Okay, here are some tokens";
MockLocalTrustTokenFulfiller mock_fulfiller(expected_answer);
// On Android, binding a local trust token operation delegate should succeed
// by default, but it can be explicitly rejected by the Android-side
// implementation code: to avoid making assumptions about that code's
// behavior, manually override the bind to make it succeed.
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
int num_binds_attempted = 0;
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindLambdaForTesting([&num_binds_attempted, &mock_fulfiller](
mojo::ScopedMessagePipeHandle handle) {
++num_binds_attempted;
mock_fulfiller.Bind(std::move(handle));
}));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
request->request = "Some tokens, please";
{
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
base::RunLoop run_loop;
partition->OnTrustTokenIssuanceDivertedToSystem(
request.Clone(),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(mojo::Equals(received_answer, expected_answer));
EXPECT_EQ(num_binds_attempted, 1);
}
{
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
base::RunLoop run_loop;
// Execute another operation to cover the case where we've already
// successfully bound the fulfiller, ensuring that we don't attempt to bind
// it again.
partition->OnTrustTokenIssuanceDivertedToSystem(
request.Clone(),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
EXPECT_TRUE(mojo::Equals(received_answer, expected_answer));
EXPECT_EQ(num_binds_attempted, 1);
}
}
#endif // defined(OS_ANDROID)
#if defined(OS_ANDROID)
TEST_F(StoragePartitionImplTest, HandlesDisconnectedTrustTokenFulfiller) {
// Construct a mock fulfiller that doesn't reply to issuance requests it
// receives...
MockLocalTrustTokenFulfiller mock_fulfiller(
MockLocalTrustTokenFulfiller::kIgnoreRequestsIndefinitely);
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating(&MockLocalTrustTokenFulfiller::Bind,
base::Unretained(&mock_fulfiller)));
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
base::RunLoop run_loop;
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
partition->OnTrustTokenIssuanceDivertedToSystem(
std::move(request),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
// ... and, when the pipe disconnects, the disconnection handler should still
// ensure we get an error response.
partition->OnLocalTrustTokenFulfillerConnectionError();
run_loop.Run();
ASSERT_TRUE(received_answer);
EXPECT_EQ(received_answer->status,
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound);
}
#endif // defined(OS_ANDROID)
TEST_F(StoragePartitionImplTest, HandlesMissingTrustTokenFulfiller) {
#if defined(OS_ANDROID)
// On Android, binding can be explicitly rejected by the Android-side
// implementation code: to ensure we can handle the rejection, manually force
// the bind to fail.
//
// On other platforms, local Trust Tokens issuance isn't yet implemented, so
// StoragePartitionImpl won't attempt to bind the fulfiller.
service_manager::InterfaceProvider::TestApi interface_overrider(
content::GetGlobalJavaInterfaces());
// Instead of using interface_overrider.ClearBinder(name), it's necessary to
// provide a callback that explicitly closes the pipe, since
// InterfaceProvider's contract requires that it either bind or close pipes
// it's given (see its comments in interface_provider.mojom).
interface_overrider.SetBinderForName(
mojom::LocalTrustTokenFulfiller::Name_,
base::BindRepeating([](mojo::ScopedMessagePipeHandle handle) {
mojo::Close(std::move(handle));
}));
#endif // defined(OS_ANDROID)
StoragePartitionImpl* partition = static_cast<StoragePartitionImpl*>(
BrowserContext::GetDefaultStoragePartition(browser_context()));
auto request = network::mojom::FulfillTrustTokenIssuanceRequest::New();
base::RunLoop run_loop;
network::mojom::FulfillTrustTokenIssuanceAnswerPtr received_answer;
partition->OnTrustTokenIssuanceDivertedToSystem(
std::move(request),
base::BindLambdaForTesting(
[&run_loop, &received_answer](
network::mojom::FulfillTrustTokenIssuanceAnswerPtr answer) {
received_answer = std::move(answer);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_TRUE(received_answer);
EXPECT_EQ(received_answer->status,
network::mojom::FulfillTrustTokenIssuanceAnswer::Status::kNotFound);
}
} // namespace content
......@@ -2209,6 +2209,7 @@ test("content_unittests") {
"//content/public/child",
"//content/public/common",
"//content/public/common:service_names",
"//content/public/common:trust_tokens_mojo_bindings",
"//content/public/renderer",
"//content/renderer:for_content_tests",
"//crypto",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment