Commit 8d8fcbd6 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Reland "Trust Tokens: Add more browser tests covering issuance"

Note: This is an identical reland of crrev.com/c/2308860 except that it
adds two conditionals to return early from tests when OOR-CORS is
disabled. (The no-OOR-CORS code, aka Blink-CORS, is not supported in
user builds in M85+ and is scheduled to be deleted in a week or two.)

-- Original commit message:

The first in a series of several CLs, this change expands the Trust
Tokens browser tests to cover additional issuance cases:

issuance to issuer with no keys -> expect error
cross-origin issuance -> expect success
cross-site issuance -> expect success
issuance with number of associated origins at cap -> expect success iff
the issuer is associated
issuance after keys expire -> should fail
issuance from non-https top-frame origin -> should fail
issuance to non-https issuer -> should fail

Bug: 1071293
Change-Id: I4f1f6771c7f39efb4629acedc45d2b3b5bd1d95b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2337424
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarCharlie Harrison <csharrison@chromium.org>
Reviewed-by: default avatarTakashi Toyoshima <toyoshim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800009}
parent f750e14e
......@@ -370,8 +370,8 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, RecordsTimers) {
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"));
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));
......@@ -505,6 +505,181 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
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) {
// This test's first release is M86, where Blink-CORS is unconditionally
// unavailable (even via enterprise policy). Add this early return (okayed by
// Blink-CORS OWNERS) to avoid failing Blink-CORS FYI bots for the next week
// and a half until they are deleted.
if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors))
return;
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) {
// This test's first release is M86, where Blink-CORS is unconditionally
// unavailable (even via enterprise policy). Add this early return (okayed by
// Blink-CORS OWNERS) to avoid failing Blink-CORS FYI bots for the next week
// and a half until they are deleted.
if (!base::FeatureList::IsEnabled(network::features::kOutOfBlinkCors))
return;
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,
......@@ -614,10 +789,9 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IssuanceWithAbsentKeyFails) {
std::string command = R"(fetch($1, {trustToken: {type: 'token-request'}})
.then(() => "Success")
.catch(error => error.name);)";
EXPECT_THAT(
EvalJs(shell(), JsReplace(command, server_.GetURL("a.test", "/issue")))
.ExtractString(),
HasSubstr("OperationError"));
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
......
......@@ -15,6 +15,7 @@
#include "net/http/http_request_headers.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/trust_tokens/suitable_trust_token_origin.h"
#include "services/network/trust_tokens/test/trust_token_request_handler.h"
namespace network {
......@@ -31,7 +32,9 @@ std::unique_ptr<net::test_server::HttpResponse>
MakeTrustTokenFailureResponse() {
// No need to report a failure HTTP code here: returning a vanilla OK should
// fail the Trust Tokens operation client-side.
return std::make_unique<net::test_server::BasicHttpResponse>();
auto ret = std::make_unique<net::test_server::BasicHttpResponse>();
ret->AddCustomHeader("Access-Control-Allow-Origin", "*");
return ret;
}
// Constructs and returns an HTTP response bearing the given base64-encoded
......@@ -45,6 +48,7 @@ std::unique_ptr<net::test_server::HttpResponse> MakeTrustTokenResponse(
auto ret = std::make_unique<net::test_server::BasicHttpResponse>();
ret->AddCustomHeader("Sec-Trust-Token", std::string(contents));
ret->AddCustomHeader("Access-Control-Allow-Origin", "*");
return ret;
}
......@@ -115,7 +119,9 @@ void RegisterTrustTokenTestHandlers(net::EmbeddedTestServer* test_server,
// Unlike issuance and redemption, there's no special state to return
// on success for signing.
return std::make_unique<net::test_server::BasicHttpResponse>();
auto ret = std::make_unique<net::test_server::BasicHttpResponse>();
ret->AddCustomHeader("Access-Control-Allow-Origin", "*");
return ret;
}));
}
......
......@@ -79,6 +79,11 @@ struct TrustTokenRequestHandler::Rep {
std::vector<uint8_t> srr_verification;
std::vector<IssuanceKeyPair> issuance_keys;
// Whether to peremptorily reject issuance and redemption or whether to
// actually process the provided input.
ServerOperationOutcome issuance_outcome;
ServerOperationOutcome redemption_outcome;
// Creates a BoringSSL token issuer context suitable for issuance or
// redemption, using only the unexpired key pairs from |issuance_keys|.
bssl::UniquePtr<TRUST_TOKEN_ISSUER> CreateIssuerContextFromUnexpiredKeys()
......@@ -240,6 +245,10 @@ base::Optional<std::string> TrustTokenRequestHandler::Issue(
base::StringPiece issuance_request) {
base::AutoLock lock(mutex_);
if (rep_->issuance_outcome == ServerOperationOutcome::kUnconditionalFailure) {
return base::nullopt;
}
bssl::UniquePtr<TRUST_TOKEN_ISSUER> issuer_ctx =
rep_->CreateIssuerContextFromUnexpiredKeys();
......@@ -282,6 +291,11 @@ base::Optional<std::string> TrustTokenRequestHandler::Redeem(
base::StringPiece redemption_request) {
base::AutoLock lock(mutex_);
if (rep_->redemption_outcome ==
ServerOperationOutcome::kUnconditionalFailure) {
return base::nullopt;
}
bssl::UniquePtr<TRUST_TOKEN_ISSUER> issuer_ctx =
rep_->CreateIssuerContextFromUnexpiredKeys();
......@@ -437,6 +451,8 @@ void TrustTokenRequestHandler::UpdateOptions(Options options) {
rep_->batch_size = options.batch_size;
rep_->client_signing_outcome = options.client_signing_outcome;
rep_->issuance_outcome = options.issuance_outcome;
rep_->redemption_outcome = options.redemption_outcome;
rep_->srr_signing.resize(ED25519_PRIVATE_KEY_LEN);
rep_->srr_verification.resize(ED25519_PUBLIC_KEY_LEN);
......
......@@ -44,6 +44,11 @@ class TrustTokenRequestHandler {
kFailure,
};
enum class ServerOperationOutcome {
kExecuteOperationAsNormal,
kUnconditionalFailure,
};
struct Options {
// The number of issuance key pairs to provide via key commitment results.
int num_keys = 1;
......@@ -57,6 +62,14 @@ class TrustTokenRequestHandler {
// The number of tokens to sign per issuance operation; this value is also
// provided to the client as part of key commitment results.
int batch_size = 10;
// If set to |kUnconditionalFailure|, returns a failure response for the
// corresponding operation even if the operation would have succeeded had
// the server been operating correctly.
ServerOperationOutcome issuance_outcome =
ServerOperationOutcome::kExecuteOperationAsNormal;
ServerOperationOutcome redemption_outcome =
ServerOperationOutcome::kExecuteOperationAsNormal;
};
// Updates the handler's options, resetting its internal state.
......
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