blob: 8ee1be61723ab7bbb21e56e9144c81e2f28c335a [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include "base/base64.h"
#include "base/strings/string_piece.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_monitor.h"
#include "content/shell/browser/shell.h"
#include "net/base/escape.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/trust_token_parameterization.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/trust_tokens/test/test_server_handler_registration.h"
#include "services/network/trust_tokens/test/trust_token_request_handler.h"
#include "services/network/trust_tokens/test/trust_token_test_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/resource_request_blocked_reason.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_canon_stdstring.h"
namespace content {
namespace {
using network::test::TrustTokenRequestHandler;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::IsFalse;
using ::testing::Optional;
// TrustTokenBrowsertest is a fixture containing boilerplate for initializing an
// HTTPS test server and passing requests through to an embedded instance of
// network::test::TrustTokenRequestHandler, which contains the guts of the
// "server-side" token issuance and redemption logic as well as some consistency
// checks for subsequent signed requests.
class TrustTokenBrowsertest : public ContentBrowserTest {
public:
TrustTokenBrowsertest() {
auto& field_trial_param =
network::features::kTrustTokenOperationsRequiringOriginTrial;
features_.InitAndEnableFeatureWithParameters(
network::features::kTrustTokens,
{{field_trial_param.name,
field_trial_param.GetName(
network::features::TrustTokenOriginTrialSpec::
kOriginTrialNotRequired)}});
}
// Registers the following handlers:
// - default //content/test/data files;
// - a special "/issue" endpoint executing Trust Tokens issuance;
// - a special "/redeem" endpoint executing redemption; and
// - a special "/sign" endpoint that verifies that the received signed request
// data is correctly structured and that the provided Sec-Signature header's
// verification key was previously bound to a successful token redemption.
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
server_.AddDefaultHandlers(
base::FilePath(FILE_PATH_LITERAL("content/test/data")));
SetupCrossSiteRedirector(embedded_test_server());
SetupCrossSiteRedirector(&server_);
network::test::RegisterTrustTokenTestHandlers(&server_, &request_handler_);
ASSERT_TRUE(server_.Start());
}
protected:
// Provides the network service key commitments from the internal
// TrustTokenRequestHandler. All hosts in |hosts| will be provided identical
// commitments.
void ProvideRequestHandlerKeyCommitmentsToNetworkService(
std::vector<base::StringPiece> hosts = {}) {
base::flat_map<url::Origin, base::StringPiece> origins_and_commitments;
std::string key_commitments = request_handler_.GetKeyCommitmentRecord();
// TODO(davidvc): This could be extended to make the request handler aware
// of different origins, which would allow using different key commitments
// per origin.
for (base::StringPiece host : hosts) {
GURL::Replacements replacements;
replacements.SetHostStr(host);
origins_and_commitments.insert_or_assign(
url::Origin::Create(
server_.base_url().ReplaceComponents(replacements)),
key_commitments);
}
if (origins_and_commitments.empty()) {
origins_and_commitments = {
{url::Origin::Create(server_.base_url()), key_commitments}};
}
base::RunLoop run_loop;
GetNetworkService()->SetTrustTokenKeyCommitments(
network::WrapKeyCommitmentsForIssuers(
std::move(origins_and_commitments)),
run_loop.QuitClosure());
run_loop.Run();
}
// Given a host (e.g. "a.test"), returns the corresponding storage origin
// for Trust Tokens state. (This adds the correct scheme---probably https---as
// well as |server_|'s port, which can vary from test to test. There's no
// ambiguity in the result because the scheme and port are both fixed across
// all domains.)
std::string IssuanceOriginFromHost(const std::string& host) const {
auto ret = url::Origin::Create(server_.GetURL(host, "/")).Serialize();
return ret;
}
base::test::ScopedFeatureList features_;
// TODO(davidvc): Extend this to support more than one key set.
TrustTokenRequestHandler request_handler_;
net::EmbeddedTestServer server_{net::EmbeddedTestServer::TYPE_HTTPS};
};
} // namespace
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
await fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1]}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, XhrEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// If this isn't idiomatic JS, I don't know what is.
std::string command = R"(
(async () => {
let request = new XMLHttpRequest();
request.open('GET', '/issue');
request.setTrustToken({
type: 'token-request'
});
let promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
request = new XMLHttpRequest();
request.open('GET', '/redeem');
request.setTrustToken({
type: 'srr-token-redemption'
});
promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
request = new XMLHttpRequest();
request.open('GET', '/sign');
request.setTrustToken({
type: 'send-srr',
signRequestData: 'include',
issuers: [$1]
});
promise = new Promise((res, rej) => {
request.onload = res; request.onerror = rej;
});
request.send();
await promise;
return "Success";
})(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt)
<< *request_handler_.LastVerificationError();
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeEndToEnd) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
auto execute_op_via_iframe = [&](base::StringPiece path,
base::StringPiece trust_token) {
// It's important to set the trust token arguments before updating src, as
// the latter triggers a load.
EXPECT_TRUE(ExecJs(
shell(), JsReplace(
R"( const myFrame = document.getElementById("test_iframe");
myFrame.trustToken = $1;
myFrame.src = $2;)",
trust_token, path)));
TestNavigationObserver load_observer(shell()->web_contents());
load_observer.WaitForNavigationFinished();
};
execute_op_via_iframe("/issue", R"({"type": "token-request"})");
execute_op_via_iframe("/redeem", R"({"type": "srr-token-redemption"})");
execute_op_via_iframe(
"/sign",
JsReplace(
R"({"type": "send-srr", "signRequestData": "include", "issuer": $1})",
IssuanceOriginFromHost("a.test")));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, HasTrustTokenAfterIssuance) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = JsReplace(R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
return await document.hasTrustToken($1);
})();)",
IssuanceOriginFromHost("a.test"));
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
//
// Note: EvalJs's EXPECT_EQ type-conversion magic only supports the
// "Yoda-style" EXPECT_EQ(expected, actual).
EXPECT_EQ(true, EvalJs(shell(), command));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SigningWithNoRedemptionRecordDoesntCancelRequest) {
TrustTokenRequestHandler::Options options;
options.client_signing_outcome =
TrustTokenRequestHandler::SigningOutcome::kFailure;
request_handler_.UpdateOptions(std::move(options));
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// This sign operation will fail, because we don't have a signed redemption
// record in storage, a prerequisite. However, the failure shouldn't be fatal.
std::string command = JsReplace(R"((async () => {
await fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1]}});
return "Success";
})(); )",
IssuanceOriginFromHost("a.test"));
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), command));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, FetchEndToEndInIsolatedWorld) {
// Ensure an isolated world can execute Trust Tokens operations when its
// window's main world can. In particular, this ensures that the
// redemtion-and-signing feature policy is appropriately propagated by the
// browser process.
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
await fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1]}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test")),
EXECUTE_SCRIPT_DEFAULT_OPTIONS,
/*world_id=*/30));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsTimers) {
base::HistogramTester histograms;
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
await fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1]}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
// Just check that the timers were populated: since we can't mock a clock in
// this browser test, it's hard to check the recorded values for
// reasonableness.
content::FetchHistogramsFromChildProcesses();
for (const std::string& op : {"Issuance", "Redemption", "Signing"}) {
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationBeginTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationTotalTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationServerTime.Success." + op, 1);
histograms.ExpectTotalCount(
"Net.TrustTokens.OperationFinalizeTime.Success." + op, 1);
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success." + op, net::OK,
1);
}
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsNetErrorCodes) {
// Verify that the Net.TrustTokens.NetErrorForTrustTokenOperation.* metrics
// record successfully by testing two "success" cases where there's an
// unrelated net stack error and one case where the Trust Tokens operation
// itself fails.
base::HistogramTester histograms;
ProvideRequestHandlerKeyCommitmentsToNetworkService(
{"no-cert-for-this.domain"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_THAT(
EvalJs(shell(), JsReplace(
R"(fetch($1, {trustToken: {type: 'token-request'}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
IssuanceOriginFromHost("no-cert-for-this.domain")))
.ExtractString(),
HasSubstr("Failed to fetch"));
EXPECT_THAT(
EvalJs(shell(), JsReplace(
R"(fetch($1, {trustToken: {type: 'send-srr',
issuers: ['https://nonexistent-issuer.example']}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
IssuanceOriginFromHost("no-cert-for-this.domain")))
.ExtractString(),
HasSubstr("Failed to fetch"));
content::FetchHistogramsFromChildProcesses();
// "Success" since we executed the outbound half of the Trust Tokens
// operation without issue:
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success.Issuance",
net::ERR_CERT_COMMON_NAME_INVALID, 1);
// "Success" since signing can't fail:
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Success.Signing",
net::ERR_CERT_COMMON_NAME_INVALID, 1);
// Attempt a redemption against 'a.test'; we don't have a token for this
// domain, so it should fail.
EXPECT_EQ(
"InvalidStateError",
EvalJs(shell(),
JsReplace(
R"(fetch($1, {trustToken: {type: 'srr-token-redemption'}})
.then(() => "Unexpected success!")
.catch(err => err.name);)",
IssuanceOriginFromHost("a.test"))));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.NetErrorForTrustTokenOperation.Failure.Redemption",
net::ERR_TRUST_TOKEN_OPERATION_FAILED, 1);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsFetchFailureReasons) {
// Verify that the Net.TrustTokens.FetchFailedReason.* metrics
// record successfully by testing one case with a blocked resource, one case
// with a generic net-stack failure, and one case with a Trust Tokens
// operation failure.
base::HistogramTester histograms;
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// This fetch will fail because we set `redirect: 'error'` and the
// /cross-site/ URL will redirect the request.
EXPECT_EQ("TypeError", EvalJs(shell(),
R"(fetch("/cross-site/b.test/issue", {
redirect: 'error',
trustToken: {type: 'token-request'}
})
.then(() => "Unexpected success!")
.catch(err => err.name);)"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.FetchFailedReason.Issuance",
9 /* fetch_manager.cc::FailedReason::kOtherNonBlockReason */,
/*expected_count=*/1);
// Since issuance failed, there should be no tokens to redeem, so redemption
// should fail:
EXPECT_EQ(
"OperationError",
EvalJs(shell(),
R"(fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}})
.then(() => "Unexpected success!")
.catch(err => err.name);)"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectUniqueSample(
"Net.TrustTokens.FetchFailedReason.Redemption",
8 /* fetch_manager.cc::FailedReason::kTrustTokensError */,
/*expected_count=*/1);
// Execute a cross-site b.test -> a.test issuance that would succeed, were it
// not for site b requiring CORP headers and none being present on the a.test
// issuance response:
ASSERT_TRUE(NavigateToURL(
shell(),
server_.GetURL("b.test",
"/cross-origin-opener-policy_redirect_final.html")));
GURL site_a_issuance_url =
GURL(IssuanceOriginFromHost("a.test")).Resolve("/issue");
EXPECT_THAT(EvalJs(shell(), JsReplace(R"(fetch($1, {
mode: 'no-cors',
trustToken: {type: 'token-request'}})
.then(() => "Unexpected success!")
.catch(err => err.message);)",
site_a_issuance_url))
.ExtractString(),
HasSubstr("Failed to fetch"));
content::FetchHistogramsFromChildProcesses();
histograms.ExpectBucketCount(
"Net.TrustTokens.FetchFailedReason.Issuance",
// fetch_manager.cc::FailedReason::
// kCorpNotSameOriginAfterDefaultedToSameOriginByCoep
21,
/*expected_count=*/1);
}
// Trust Tokens should require that their executing contexts be secure.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, OperationsRequireSecureContext) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL start_url =
embedded_test_server()->GetURL("insecure.test", "/page_with_iframe.html");
// Make sure that we are, in fact, using an insecure page.
ASSERT_FALSE(network::IsUrlPotentiallyTrustworthy(start_url));
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// 1. Confirm that the Fetch interface doesn't work:
std::string command =
R"(fetch("/issue", {trustToken: {type: 'token-request'}})
.catch(error => error.message);)";
EXPECT_THAT(EvalJs(shell(), command).ExtractString(),
HasSubstr("secure context"));
// 2. Confirm that the XHR interface isn't present:
EXPECT_EQ(false, EvalJs(shell(), "'setTrustToken' in (new XMLHttpRequest);"));
// 3. Confirm that the iframe interface doesn't work by verifying that no
// Trust Tokens operation gets executed.
GURL issuance_url = server_.GetURL("/issue");
URLLoaderMonitor monitor({issuance_url});
// It's important to set the trust token arguments before updating src, as
// the latter triggers a load.
EXPECT_TRUE(ExecJs(
shell(), JsReplace(
R"( const myFrame = document.getElementById("test_iframe");
myFrame.trustToken = $1;
myFrame.src = $2;)",
R"({"type": "token-request"})", issuance_url)));
monitor.WaitForUrls();
EXPECT_THAT(monitor.GetRequestInfo(issuance_url),
Optional(Field(&network::ResourceRequest::trust_token_params,
IsFalse())));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, AdditionalSigningData) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), start_url));
std::string cmd = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
await fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1],
additionalSigningData: 'some additional data to sign'}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(cmd, IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, OverlongAdditionalSigningData) {
TrustTokenRequestHandler::Options options;
options.client_signing_outcome =
TrustTokenRequestHandler::SigningOutcome::kFailure;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string cmd = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), cmd));
// Even though this contains fewer than
// network::kTrustTokenAdditionalSigningDataMaxSizeBytes code units, once it's
// converted to UTF-8 it will contain more than that many bytes, so we expect
// that it will get rejected by the network service.
base::string16 overlong_signing_data(
network::kTrustTokenAdditionalSigningDataMaxSizeBytes,
u'€' /* char16 literal */);
ASSERT_LE(overlong_signing_data.size(),
network::kTrustTokenAdditionalSigningDataMaxSizeBytes);
cmd = R"(
fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1],
additionalSigningData: $2}}).then(()=>"Success");)";
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(cmd, IssuanceOriginFromHost("a.test"),
overlong_signing_data)));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
AdditionalSigningDataNotAValidHeader) {
TrustTokenRequestHandler::Options options;
options.client_signing_outcome =
TrustTokenRequestHandler::SigningOutcome::kFailure;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'srr-token-redemption'}});
return "Success"; })(); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), command));
command = R"(
fetch("/sign", {trustToken: {type: 'send-srr',
signRequestData: 'include',
issuers: [$1],
additionalSigningData: '\r'}}).then(()=>"Success");)";
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
}
// Issuance should fail if we don't have keys for the issuer at hand.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IssuanceRequiresKeys) {
ProvideRequestHandlerKeyCommitmentsToNetworkService(
{"not-the-right-server.example"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
EXPECT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(
fetch('/issue', {trustToken: {type: 'token-request'}})
.then(() => 'Success').catch(err => err.name); )";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("InvalidStateError", EvalJs(shell(), command));
}
// When the server rejects issuance, the client-side issuance operation should
// fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
CorrectlyReportsServerErrorDuringIssuance) {
TrustTokenRequestHandler::Options options;
options.issuance_outcome =
TrustTokenRequestHandler::ServerOperationOutcome::kUnconditionalFailure;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("OperationError", EvalJs(shell(), R"(fetch('/issue',
{ trustToken: { type: 'token-request' } })
.then(()=>'Success').catch(err => err.name); )"));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, CrossOriginIssuanceWorks) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"sub1.b.test"});
GURL start_url = server_.GetURL("sub2.b.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// Using GetURL to generate the issuance location is important
// because it sets the port correctly.
EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(R"(
fetch($1, { trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("sub1.b.test", "/issue"))));
}
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, CrossSiteIssuanceWorks) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("b.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// Using GetURL to generate the issuance location is important
// because it sets the port correctly.
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch($1, { trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
}
// Issuance should succeed only if the number of issuers associated with the
// requesting context's top frame origin is less than the limit on the number of
// such issuers.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IssuanceRespectsAssociatedIssuersCap) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
static_assert(
network::kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers < 10,
"Consider rewriting this test for performance's sake if the "
"number-of-issuers limit gets too large.");
// Each hasTrustToken call adds the provided issuer to the calling context's
// list of associated issuers.
for (int i = 0;
i < network::kTrustTokenPerToplevelMaxNumberOfAssociatedIssuers; ++i) {
ASSERT_EQ("Success", EvalJs(shell(), "document.hasTrustToken('https://a" +
base::NumberToString(i) +
".test').then(()=>'Success');"));
}
EXPECT_EQ("OperationError", EvalJs(shell(), R"(
fetch('/issue', { trustToken: { type: 'token-request' } })
.then(() => 'Success').catch(error => error.name); )"));
}
// When an issuance request is made in cors mode, a cross-origin redirect from
// issuer A to issuer B should result in a new issuance request to issuer B,
// obtaining issuer B tokens on success.
//
// Note: For more on the interaction between Trust Tokens and redirects, see the
// "Handling redirects" section in the design doc
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.5erfr3uo012t
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
CorsModeCrossOriginRedirectIssuanceUsesNewOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch($1, {trustToken: {type: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"Success",
EvalJs(shell(),
JsReplace(command,
server_.GetURL("a.test", "/cross-site/b.test/issue"))));
EXPECT_EQ(true, EvalJs(shell(), JsReplace("document.hasTrustToken($1);",
IssuanceOriginFromHost("b.test"))));
EXPECT_EQ(false,
EvalJs(shell(), JsReplace("document.hasTrustToken($1);",
IssuanceOriginFromHost("a.test"))));
}
// When an issuance request is made in no-cors mode, a cross-origin redirect
// from issuer A to issuer B should result in recycling the original issuance
// request, obtaining issuer A tokens on success.
//
// Note: For more on the interaction between Trust Tokens and redirects, see the
// "Handling redirects" section in the design doc
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.5erfr3uo012t
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectIssuanceUsesOriginalOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch($1, {mode: 'no-cors',
trustToken: {type: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"Success",
EvalJs(shell(),
JsReplace(command,
server_.GetURL("a.test", "/cross-site/b.test/issue"))));
EXPECT_EQ(true, EvalJs(shell(), JsReplace("document.hasTrustToken($1);",
IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(false,
EvalJs(shell(), JsReplace("document.hasTrustToken($1);",
IssuanceOriginFromHost("b.test"))));
}
// Issuance from a context with a secure-but-non-HTTP/S top frame origin
// should fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IssuanceRequiresSuitableTopFrameOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService();
GURL file_url = GetTestUrl(/*dir=*/nullptr, "title1.html");
ASSERT_TRUE(file_url.SchemeIsFile());
ASSERT_TRUE(NavigateToURL(shell(), file_url));
std::string command =
R"(fetch($1, {trustToken: {type: 'token-request'}})
.catch(error => error.name);)";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(command, server_.GetURL("/issue"))));
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ(
false,
EvalJs(shell(),
JsReplace("document.hasTrustToken($1);",
url::Origin::Create(server_.base_url()).Serialize())));
}
// Redemption from a secure-but-non-HTTP(S) top frame origin should fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionRequiresSuitableTopFrameOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command =
R"(fetch("/issue", {trustToken: {type: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
// We use EvalJs here, not ExecJs, because EvalJs waits for promises to
// resolve.
EXPECT_EQ("Success", EvalJs(shell(), command));
GURL file_url = GetTestUrl(/*dir=*/nullptr, "title1.html");
ASSERT_TRUE(NavigateToURL(shell(), file_url));
// Redemption from a page with a file:// top frame origin should fail.
command = R"(fetch($1, {trustToken: {type: 'srr-token-redemption'}})
.catch(error => error.name);)";
EXPECT_EQ(
"InvalidStateError",
EvalJs(shell(), JsReplace(command, server_.GetURL("a.test", "/redeem"))));
}
// hasTrustToken from a context with a secure-but-non-HTTP/S top frame origin
// should fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
HasTrustTokenRequiresSuitableTopFrameOrigin) {
GURL file_url = GetTestUrl(/*dir=*/nullptr, "title1.html");
ASSERT_TRUE(file_url.SchemeIsFile());
ASSERT_TRUE(NavigateToURL(shell(), file_url));
EXPECT_EQ("NotAllowedError",
EvalJs(shell(),
R"(document.hasTrustToken('https://issuer.example')
.catch(error => error.name);)"));
}
// An operation initiated from a secure context should succeed even if the
// operation's associated request's initiator is opaque (e.g. from a sandboxed
// iframe).
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
OperationFromSecureSubframeWithOpaqueOrigin) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(
shell(), server_.GetURL("a.test", "/page_with_sandboxed_iframe.html")));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ("Success", EvalJs(root->child_at(0)->current_frame_host(),
JsReplace(R"(
fetch($1, {mode: 'no-cors',
trustToken: {type: 'token-request'}
}).then(()=>'Success');)",
server_.GetURL("a.test", "/issue"))));
}
// If a server issues with a key not present in the client's collection of key
// commitments, the issuance operation should fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IssuanceWithAbsentKeyFails) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
// Reset the handler, so that the client's valid keys disagree with the
// server's keys. (This is theoretically flaky, but the chance of the client's
// random keys colliding with the server's random keys is negligible.)
request_handler_.UpdateOptions(TrustTokenRequestHandler::Options());
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
std::string command = R"(fetch($1, {trustToken: {type: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_EQ(
"OperationError",
EvalJs(shell(), JsReplace(command, server_.GetURL("a.test", "/issue"))));
}
// This regression test for crbug.com/1111735 ensures it's possible to execute
// redemption from a nested same-origin frame that hasn't committed a
// navigation.
//
// How it works: The main frame embeds a same-origin iframe that does not
// commit a navigation (here, specifically because of an HTTP 204 return). From
// this iframe, we execute a Trust Tokens redemption operation via the iframe
// interface (in other words, the Trust Tokens operation executes during the
// process of navigating to a grandchild frame). The grandchild frame's load
// will result in a renderer kill without the fix for the bug applied.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
SignFromFrameLackingACommittedNavigation) {
GURL start_url = server_.GetURL(
"a.test", "/page-executing-trust-token-signing-from-204-subframe.html");
// Execute a signing operation from a child iframe that has not committed a
// navigation (see the html source).
ASSERT_TRUE(NavigateToURL(shell(), start_url));
// For good measure, make sure the analogous signing operation works from
// fetch, too, even though it wasn't broken by the same bug.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ("Success", EvalJs(root->child_at(0)->current_frame_host(),
JsReplace(R"(
fetch($1, {mode: 'no-cors',
trustToken: {type: 'send-srr',
issuers: [
'https://issuer.example'
]}
}).then(()=>'Success');)",
server_.GetURL("a.test", "/issue"))));
}
// Redemption should fail when there are no keys for the issuer.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RedemptionRequiresKeys) {
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
}
// Redemption should fail when there are no tokens to redeem.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RedemptionRequiresTokens) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("OperationError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
}
// When we have tokens for one issuer A, redemption against a different issuer B
// should still fail if we don't have any tokens for B.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionWithoutTokensForDesiredIssuerFails) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("OperationError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )",
server_.GetURL("b.test", "/redeem"))));
}
// When the server rejects redemption, the client-side redemption operation
// should fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
CorrectlyReportsServerErrorDuringRedemption) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
GURL start_url = server_.GetURL("a.test", "/title1.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));
EXPECT_EQ("Success", EvalJs(shell(), R"(fetch('/issue',
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )"));
// Send a redemption request to the issuance endpoint, which should error out
// for the obvious reason that it isn't an issuance request:
EXPECT_EQ("OperationError", EvalJs(shell(), R"(fetch('/issue',
{ trustToken: { type: 'srr-token-redemption' } })
.then(() => 'Success')
.catch(err => err.name); )"));
}
// After a successful issuance and redemption, a subsequent redemption against
// the same issuer should hit the signed redemption record (SRR) cache.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RedemptionHitsRedemptionRecordCache) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("NoModificationAllowedError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.catch(err => err.name); )",
server_.GetURL("a.test", "/redeem"))));
}
// Redemption with `refresh-policy: 'refresh'` from an issuer context should
// succeed, overwriting the existing SRR.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RefreshPolicyRefreshWorksInIssuerContext) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/redeem"))));
}
// Redemption with `refresh-policy: 'refresh'` from a non-issuer context should
// fail.
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
RefreshPolicyRefreshRequiresIssuerContext) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
// Execute the operations against issuer https://b.test:<port> from a
// different context; attempting to use refreshPolicy: 'refresh' should error.
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/redeem"))));
EXPECT_EQ("InvalidStateError",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'srr-token-redemption',
refreshPolicy: 'refresh' } })
.then(()=>'Success').catch(err => err.name); )",
server_.GetURL("b.test", "/redeem"))));
}
// When a redemption request is made in cors mode, a cross-origin redirect from
// issuer A to issuer B should result in a new redemption request to issuer B,
// failing if there are no issuer B tokens.
//
// Note: For more on the interaction between Trust Tokens and redirects, see the
// "Handling redirects" section in the design doc
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.5erfr3uo012t
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
CorsModeCrossOriginRedirectRedemptionUsesNewOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test", "b.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
// Obtain both https://a.test:<PORT> and https://b.test:<PORT> tokens, the
// former for the initial redemption request to https://a.test:<PORT> and the
// latter for the fresh post-redirect redemption request to
// https://b.test:<PORT>.
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("a.test", "/issue"))));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(fetch($1,
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )",
server_.GetURL("b.test", "/issue"))));
// On the redemption request, `mode: 'cors'` (the default) has the effect that
// that redirecting a request will renew the request's Trust Tokens state.
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ trustToken: { mode: 'cors', type: 'srr-token-redemption' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ trustToken: { type: 'send-srr', issuers: [$1],
signRequestData: 'headers-only' } }).then(()=>'Success');)",
IssuanceOriginFromHost("b.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ trustToken: { type: 'send-srr', issuers: [$1],
signRequestData: 'headers-only' } }).then(()=>'Success');)",
IssuanceOriginFromHost("a.test"))));
// There shouldn't have been an a.test SRR attached to the request.
EXPECT_TRUE(request_handler_.LastVerificationError());
}
// When a redemption request is made in no-cors mode, a cross-origin redirect
// from issuer A to issuer B should result in recycling the original redemption
// request, obtaining an issuer A SRR on success.
//
// Note: This isn't necessarily the behavior we'll end up wanting here; the test
// serves to document how redemption and redirects currently interact. For more
// on the interaction between Trust Tokens and redirects, see the "Handling
// redirects" section in the design doc
// https://docs.google.com/document/d/1TNnya6B8pyomDK2F1R9CL3dY10OAmqWlnCxsWyOBDVQ/edit#heading=h.5erfr3uo012t
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectRedemptionUsesOriginalOriginAsIssuer) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/issue',
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )"));
// `mode: 'no-cors'` on redemption has the effect that that redirecting a
// request will maintain the request's Trust Tokens state.
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ mode: 'no-cors',
trustToken: { type: 'srr-token-redemption' } })
.then(()=>'Success'); )"));
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ trustToken: { type: 'send-srr', issuers: [$1],
signRequestData: 'headers-only' } })
.then(()=>'Success'); )",
IssuanceOriginFromHost("a.test"))));
EXPECT_EQ(request_handler_.LastVerificationError(), base::nullopt);
EXPECT_EQ("Success",
EvalJs(shell(), JsReplace(R"(
fetch('/sign',
{ trustToken: { type: 'send-srr', issuers: [$1],
signRequestData: 'headers-only' } })
.then(()=>'Success'); )",
IssuanceOriginFromHost("b.test"))));
// There shouldn't have been an a.test SRR attached to the request.
EXPECT_TRUE(request_handler_.LastVerificationError());
}
// When a redemption request is made in no-cors mode, a cross-origin redirect
// from issuer A to issuer B should result in recycling the original redemption
// request and, in particular, sending the same token.
//
// Note: This isn't necessarily the behavior we'll end up wanting here; the test
// serves to document how redemption and redirects currently interact.
IN_PROC_BROWSER_TEST_F(
TrustTokenBrowsertest,
NoCorsModeCrossOriginRedirectRedemptionRecyclesSameRedemptionRequest) {
// Have issuance provide only a single token so that, if the redemption logic
// searches for a new token after redirect, the redemption will fail.
TrustTokenRequestHandler::Options options;
options.batch_size = 1;
request_handler_.UpdateOptions(std::move(options));
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});
ASSERT_TRUE(NavigateToURL(shell(), server_.GetURL("a.test", "/title1.html")));
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/issue',
{ trustToken: { type: 'token-request' } })
.then(()=>'Success'); )"));
// The redemption should succeed after the redirect, yielding an a.test SRR
// (the SRR correctly corresponding to a.test is covered by a prior test
// case).
EXPECT_EQ("Success", EvalJs(shell(), R"(
fetch('/cross-site/b.test/redeem',
{ mode: 'no-cors',
trustToken: { type: 'srr-token-redemption' } })
.then(()=>'Success'); )"));
}
} // namespace content