Skip to content

Commit

Permalink
Limit iframes to only allow send-redemption-record
Browse files Browse the repository at this point in the history
Fixed: 1397605
Change-Id: I2b85acbf6d6e6e044af15689c178bf3fd8ad06ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4080544
Reviewed-by: Andrey Kosyakov <caseq@chromium.org>
Commit-Queue: Sam Schlesinger <samschlesinger@google.com>
Reviewed-by: Peter Kasting <pkasting@chromium.org>
Reviewed-by: Steven Valdez <svaldez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1085097}
  • Loading branch information
SamuelSchlesinger authored and Chromium LUCI CQ committed Dec 19, 2022
1 parent 21b219c commit ecc1059
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 79 deletions.
16 changes: 14 additions & 2 deletions content/browser/devtools/devtools_trust_token_browsertest.cc
Expand Up @@ -160,6 +160,20 @@ IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, IframeEndToEnd) {
Attach();
SendCommandSync("Network.enable");

// 3) Request and redeem a token, then use the redeemed token in a Signing
// request.
std::string command = R"(
(async () => {
await fetch('/issue', {trustToken: {type: 'token-request'}});
await fetch('/redeem', {trustToken: {type: 'token-redemption'}});
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"))));

// 3) Request and redeem a token, then use the redeemed token in a Signing
// request.
auto execute_op_via_iframe = [&](base::StringPiece path,
Expand All @@ -176,8 +190,6 @@ IN_PROC_BROWSER_TEST_F(DevToolsTrustTokenBrowsertest, IframeEndToEnd) {
load_observer.WaitForNavigationFinished();
};

execute_op_via_iframe("/issue", R"({"type": "token-request"})");
execute_op_via_iframe("/redeem", R"({"type": "token-redemption"})");
execute_op_via_iframe("/sign", JsReplace(
R"({"type": "send-redemption-record",
"issuers": [$1]})",
Expand Down
73 changes: 60 additions & 13 deletions content/browser/network/trust_token_browsertest.cc
Expand Up @@ -7,16 +7,11 @@
#include <memory>
#include <string>

#include "base/base64.h"
#include "base/containers/contains.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/network_service_instance.h"
Expand All @@ -28,17 +23,12 @@
#include "content/public/test/url_loader_interceptor.h"
#include "content/public/test/url_loader_monitor.h"
#include "content/shell/browser/shell.h"
#include "crypto/sha2.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_http_headers.h"
#include "services/network/public/cpp/trust_token_parameterization.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/test/trust_token_request_handler.h"
#include "services/network/test/trust_token_test_server_handler_registration.h"
#include "services/network/test/trust_token_test_util.h"
Expand Down Expand Up @@ -246,12 +236,23 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, XhrEndToEnd) {
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
}

IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeEndToEnd) {
IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeSendRedemptionRecord) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});

std::string command = R"(
(async () => {
await fetch("/issue", {trustToken: {type: 'token-request'}});
await fetch("/redeem", {trustToken: {type: 'token-redemption'}});
return "Success";
})())";

GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));

EXPECT_EQ(
"Success",
EvalJs(shell(), JsReplace(command, IssuanceOriginFromHost("a.test"))));

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
Expand All @@ -266,8 +267,6 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeEndToEnd) {
load_observer.WaitForNavigationFinished();
};

execute_op_via_iframe("/issue", R"({"type": "token-request"})");
execute_op_via_iframe("/redeem", R"({"type": "token-redemption"})");
execute_op_via_iframe("/sign", JsReplace(
R"({"type": "send-redemption-record",
"issuers": [$1]})",
Expand All @@ -280,6 +279,54 @@ IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, IframeEndToEnd) {
HasHeader(network::kTrustTokensSecTrustTokenVersionHeader))));
}

IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest,
IframeCanOnlySendRedemptionRecord) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});

GURL start_url = server_.GetURL("a.test", "/page_with_iframe.html");
ASSERT_TRUE(NavigateToURL(shell(), start_url));

auto fail_to_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();
};

fail_to_execute_op_via_iframe("/issue", R"({"type": "token-request"})");
std::string command = JsReplace(R"(
(async () => {
return await document.hasPrivateToken($1, 'private-state-token');
})();)",
IssuanceOriginFromHost("a.test"));

EXPECT_EQ(false, EvalJs(shell(), command));

fail_to_execute_op_via_iframe("/redeem", R"({"type": "token-redemption"})");
command = JsReplace(R"(
(async () => {
return document.hasRedemptionRecord($1, 'private-state-token');
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(false, EvalJs(shell(), command));

fail_to_execute_op_via_iframe("/bad", R"({"type": "bad-type"})");
command = JsReplace(R"(
(async () => {
return await document.hasPrivateToken($1, 'private-state-token')
|| document.hasRedemptionRecord($1, 'private-state-token');
})();)",
IssuanceOriginFromHost("a.test"));
EXPECT_EQ(false, EvalJs(shell(), command));
}

IN_PROC_BROWSER_TEST_F(TrustTokenBrowsertest, HasTrustTokenAfterIssuance) {
ProvideRequestHandlerKeyCommitmentsToNetworkService({"a.test"});

Expand Down
Expand Up @@ -305,6 +305,7 @@ INSTANTIATE_TEST_SUITE_P(ExecutingAllOperations,
IN_PROC_BROWSER_TEST_P(TrustTokenOriginTrialBrowsertest,
ProvidesParamsOnlyWhenAllowed) {
TestDescription test_description = std::get<1>(GetParam());
Interface interface = std::get<0>(GetParam());

URLLoaderInterceptor interceptor(base::BindLambdaForTesting(
[this](URLLoaderInterceptor::RequestParams* params) {
Expand Down Expand Up @@ -338,12 +339,15 @@ IN_PROC_BROWSER_TEST_P(TrustTokenOriginTrialBrowsertest,
trust_token_params);

std::string command;
switch (std::get<0>(GetParam()) /* interface */) {
switch (interface) {
case Interface::kFetch:
command = JsReplace("fetch($1, {trustToken: ", kTrustTokenUrl) +
expected_params_and_serialization.serialized_params + "});";
break;
case Interface::kIframe:
if (test_description.op != Op::kSigning) {
return;
}
command = JsReplace(
"let iframe = document.createElement('iframe');"
"iframe.src = $1;"
Expand Down
7 changes: 7 additions & 0 deletions content/browser/network/trust_token_parameters_browsertest.cc
Expand Up @@ -107,6 +107,13 @@ IN_PROC_BROWSER_TEST_P(TrustTokenParametersBrowsertest,
network::SerializeTrustTokenParametersAndConstructExpectation(
GetParam());

// In the iframe interface to private state tokens, we only accept the
// kSigning variant, i.e. the send-redemption-record operation.
if (expected_params_and_serialization.params->type !=
network::mojom::TrustTokenOperationType::kSigning) {
return;
}

GURL url(embedded_test_server()->GetURL("/title1.html"));
GURL trust_token_url(embedded_test_server()->GetURL("/title2.html"));

Expand Down
37 changes: 14 additions & 23 deletions third_party/blink/renderer/core/html/html_iframe_element.cc
Expand Up @@ -509,17 +509,20 @@ HTMLIFrameElement::ConstructTrustTokenParams() const {
return nullptr;
}

// Trust token redemption and signing (but not issuance) require that the
// trust-token-redemption permissions policy be present.
bool operation_requires_permissions_policy =
parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kRedemption ||
parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kSigning;

if (operation_requires_permissions_policy &&
(!GetExecutionContext()->IsFeatureEnabled(
mojom::blink::PermissionsPolicyFeature::kTrustTokenRedemption))) {
// Only the send-redemption-record (the kSigning variant) operation is
// valid in the iframe context.
if (parsed_params->type !=
network::mojom::blink::TrustTokenOperationType::kSigning) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"Trust Tokens: Attempted a trusttoken operation which isn't "
"send-redemption-record in an iframe."));
return nullptr;
}

if (!GetExecutionContext()->IsFeatureEnabled(
mojom::blink::PermissionsPolicyFeature::kTrustTokenRedemption)) {
GetExecutionContext()->AddConsoleMessage(
MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
Expand All @@ -529,18 +532,6 @@ HTMLIFrameElement::ConstructTrustTokenParams() const {
return nullptr;
}

if (parsed_params->type ==
network::mojom::blink::TrustTokenOperationType::kIssuance &&
!IsTrustTokenIssuanceAvailableInExecutionContext(
*GetExecutionContext())) {
GetDocument().AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
mojom::blink::ConsoleMessageSource::kOther,
mojom::blink::ConsoleMessageLevel::kError,
"Private State Tokens issuance is disabled except in "
"contexts with the PrivateStateTokens Origin Trial enabled."));
return nullptr;
}

return parsed_params;
}

Expand Down
@@ -1 +1 @@
<iframe src="https://issuer.example" trusttoken="{&#x22;type&#x22;: &#x22;token-request&#x22;}"></iframe>
<iframe src="https://issuer.example" trusttoken="{&#x22;type&#x22;: &#x22;send-redemption-record&#x22;, &#x22;issuers&#x22;: [&#x22;https://issuer.example&#x22;] }"></iframe>
@@ -1,4 +1,4 @@
Check that TrustTokenParams are included when an iframe requests a trust token'
Main frame navigation not expected to contain trustTokenParams.
Included trustTokenParams in request: {"type":"Issuance","refreshPolicy":"UseCached"}
Included trustTokenParams in request: {"type":"Signing","refreshPolicy":"UseCached","issuers":["https://issuer.example"]}

Expand Up @@ -9,44 +9,23 @@
<script>
'use strict';

promise_test(() => {
const frame = document.createElement('iframe');
frame.src = '/wpt_internal/trust-tokens/resources/trust_token_redemption.py';
frame.trustToken = JSON.stringify({ type: 'token-redemption' });
document.body.appendChild(frame);

return new Promise(resolve => {
frame.addEventListener("error", resolve("iframe raised an error"));
}).then(event => {
assert_equals(event, "iframe raised an error");
});
}, 'Trust token redemption using iframes fails without prior issuance.');

promise_test(() => {
const frame = document.createElement('iframe');
frame.src = '/wpt_internal/trust-tokens/resources/trust_token_issuance.py';
frame.trustToken = JSON.stringify({ type: 'token-request' });
document.body.appendChild(frame);

return new Promise(resolve => {
frame.addEventListener("load", resolve);
}).then(event => {
assert_equals(frame.contentWindow.document.body.innerText, 'Trust token issuance succeeded.');
});
}, 'Trust token issuance succeeds using iframes.');

promise_test(() => {
const frame = document.createElement('iframe');
frame.src = '/wpt_internal/trust-tokens/resources/trust_token_redemption.py';
frame.trustToken = JSON.stringify({ type: 'token-redemption' });
document.body.appendChild(frame);

return new Promise(resolve => {
frame.addEventListener("load", resolve);
}).then(event => {
assert_equals(frame.contentWindow.document.body.innerText, 'Trust token redemption succeeded.');
});
}, 'Trust token redemption succeeds using iframes.');
promise_test(function () {
return fetch('/wpt_internal/trust-tokens/resources/trust_token_issuance.py', {
trustToken: { type: 'token-request' }
}).then(function (response) {
assert_equals(response.status, 200);
assert_equals(response.headers.get('Sec-Trust-Token'), null);
})
}, 'Token issuance succeeds.');

promise_test(function () {
return fetch('/wpt_internal/trust-tokens/resources/trust_token_redemption.py', {
trustToken: { type: 'token-redemption' }
}).then(function (response) {
assert_equals(response.status, 200);
assert_equals(response.headers.get('Sec-Trust-Token'), null);
})
}, 'Token redemption succeeds.');

promise_test(() => {
const host_info = get_host_info();
Expand Down

0 comments on commit ecc1059

Please sign in to comment.