0

[Web Payment] Blink CSP check for payment method identifier

Before this patch, a Content Security Policy (CSP) violation in payment
method identifier would be counted, but there was no way to enforce CSP.

This patch adds a chrome://flags/#web-payment-api-csp flag that enables
enforcing the CSP connect-src directive for payment method identifiers.

After this patch, if chrome://flags/#web-payment-api-csp is set to
"Enabled", then a CSP violation in payment method identifier will print
a "refused to connect" error message and PaymentRequest constructor will
throw a RangeError. (Not in this patch: handling redirects, e.g., from
https://host.com/pay to https://pay.host.com/.)

Intent to prototype:
https://groups.google.com/a/chromium.org/g/blink-dev/c/jklZJYcOVyg/m/Gfwa4QQBAwAJ

Bug: 1349091
Change-Id: I2df9bf8a0e207f06dc674b53263b219803c3a5ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3805640
Commit-Queue: Rouslan Solomakhin <rouslan@chromium.org>
Reviewed-by: Daniel Bratell <bratell.d@gmail.com>
Reviewed-by: Arthur Sonzogni <arthursonzogni@chromium.org>
Reviewed-by: Stephen McGruer <smcgruer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1032997}
This commit is contained in:
Rouslan Solomakhin
2022-08-09 14:02:57 +00:00
committed by Chromium LUCI CQ
parent 875aa6cde3
commit 6cc110854f
13 changed files with 102 additions and 15 deletions

@ -4912,6 +4912,9 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kWebPaymentsExperimentalFeaturesName,
flag_descriptions::kWebPaymentsExperimentalFeaturesDescription, kOsAll,
FEATURE_VALUE_TYPE(payments::features::kWebPaymentsExperimentalFeatures)},
{"web-payment-api-csp", flag_descriptions::kWebPaymentAPICSPName,
flag_descriptions::kWebPaymentAPICSPDescription, kOsAll,
FEATURE_VALUE_TYPE(features::kWebPaymentAPICSP)},
{"enable-payment-request-basic-card",
flag_descriptions::kPaymentRequestBasicCardName,
flag_descriptions::kPaymentRequestBasicCardDescription, kOsAll,

@ -6560,6 +6560,11 @@
"owners": [ "yigu" ],
"expiry_milestone": 110
},
{
"name": "web-payment-api-csp",
"owners": [ "rouslan", "web-payments-team@google.com" ],
"expiry_milestone": 110
},
{
"name": "web-share",
"owners": [ "mhochk@microsoft.com", "ericwilligers@google.com", "hatalat@microsoft.com" ],

@ -3000,6 +3000,12 @@ const char kWebPaymentsExperimentalFeaturesName[] =
const char kWebPaymentsExperimentalFeaturesDescription[] =
"Enable experimental Web Payments API features";
const char kWebPaymentAPICSPName[] = "CSP policy for Web Payment API";
const char kWebPaymentAPICSPDescription[] =
"Enforce Content Security Policy connect-src directive for Web Payment API "
"when fetching manifest files, app icons, and service worker JavaScript "
"files.";
const char kPaymentRequestBasicCardName[] =
"PaymentRequest API 'basic-card' method";
const char kPaymentRequestBasicCardDescription[] =

@ -1683,6 +1683,9 @@ extern const char kWebGpuDeveloperFeaturesDescription[];
extern const char kWebPaymentsExperimentalFeaturesName[];
extern const char kWebPaymentsExperimentalFeaturesDescription[];
extern const char kWebPaymentAPICSPName[];
extern const char kWebPaymentAPICSPDescription[];
extern const char kPaymentRequestBasicCardName[];
extern const char kPaymentRequestBasicCardDescription[];

@ -3,18 +3,28 @@
// found in the LICENSE file.
#include "base/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/test/payments/payment_request_platform_browsertest_base.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace payments {
class IframeCspTest : public PaymentRequestPlatformBrowserTestBase {
class IframeCspTest : public PaymentRequestPlatformBrowserTestBase,
public ::testing::WithParamInterface<bool> {
public:
IframeCspTest() = default;
IframeCspTest() {
if (WebPaymentAPICSPEnabled()) {
features_.InitAndEnableFeature(::features::kWebPaymentAPICSP);
} else {
features_.InitAndDisableFeature(::features::kWebPaymentAPICSP);
}
}
~IframeCspTest() override = default;
void SetUpOnMainThread() override {
@ -26,11 +36,16 @@ class IframeCspTest : public PaymentRequestPlatformBrowserTestBase {
ASSERT_TRUE(app_server_.Start());
}
bool WebPaymentAPICSPEnabled() const { return GetParam(); }
protected:
net::EmbeddedTestServer app_server_{net::EmbeddedTestServer::TYPE_HTTPS};
private:
base::test::ScopedFeatureList features_;
};
IN_PROC_BROWSER_TEST_F(IframeCspTest, Show) {
IN_PROC_BROWSER_TEST_P(IframeCspTest, Show) {
NavigateTo("/csp_test_main.html");
content::WebContentsConsoleObserver console_observer(GetActiveWebContents());
@ -54,8 +69,20 @@ IN_PROC_BROWSER_TEST_F(IframeCspTest, Show) {
SetDownloaderAndIgnorePortInOriginComparisonForTestingInFrame(
{{method_name, &app_server_}}, iframe);
EXPECT_EQ(true, content::EvalJs(iframe, "checkCanMakePayment()"));
if (WebPaymentAPICSPEnabled()) {
EXPECT_EQ(
"RangeError: Failed to construct 'PaymentRequest': "
"https://kylepay.com/webpay payment method identifier violates Content "
"Security Policy.",
content::EvalJs(iframe, "checkCanMakePayment()"));
} else {
// CSP is disabled.
EXPECT_EQ(true, content::EvalJs(iframe, "checkCanMakePayment()"));
}
EXPECT_TRUE(console_observer.messages().empty());
}
INSTANTIATE_TEST_SUITE_P(All, IframeCspTest, ::testing::Bool());
} // namespace payments

@ -252,6 +252,7 @@ void SetRuntimeFeaturesFromChromiumFeatures() {
kSetOnlyIfOverridden},
{wf::EnableWebAppManifestId, blink::features::kWebAppEnableManifestId},
{wf::EnablePaymentApp, features::kServiceWorkerPaymentApps},
{wf::EnableWebPaymentAPICSP, features::kWebPaymentAPICSP},
{wf::EnablePaymentRequest, features::kWebPayments},
{wf::EnablePaymentRequestBasicCard, features::kPaymentRequestBasicCard},
{wf::EnablePercentBasedScrolling, features::kWindowsScrollingPersonality},

@ -835,6 +835,10 @@ const base::Feature kSendBeaconThrowForBlobWithNonSimpleType{
const base::Feature kServiceWorkerPaymentApps{"ServiceWorkerPaymentApps",
base::FEATURE_ENABLED_BY_DEFAULT};
// Enable connect-src CSP directive for the Web Payment API.
const base::Feature kWebPaymentAPICSP{"WebPaymentAPICSP",
base::FEATURE_DISABLED_BY_DEFAULT};
// Enable the basic-card payment method from the PaymentRequest API. This has
// been disabled since M100 and is soon to be removed: crbug.com/1209835.
const base::Feature kPaymentRequestBasicCard{"PaymentRequestBasicCard",

@ -179,6 +179,7 @@ CONTENT_EXPORT extern const base::Feature kNotificationContentImage;
CONTENT_EXPORT extern const base::Feature kNotificationTriggers;
CONTENT_EXPORT extern const base::Feature kOriginIsolationHeader;
CONTENT_EXPORT extern const base::Feature kOverscrollHistoryNavigation;
CONTENT_EXPORT extern const base::Feature kWebPaymentAPICSP;
CONTENT_EXPORT extern const base::Feature kPaymentRequestBasicCard;
CONTENT_EXPORT extern const base::Feature kPeriodicBackgroundSync;
CONTENT_EXPORT extern const base::Feature kFeaturePolicyHeader;

@ -129,6 +129,7 @@ class WebRuntimeFeatures {
BLINK_PLATFORM_EXPORT static void EnableNotifications(bool);
BLINK_PLATFORM_EXPORT static void EnableOverlayScrollbars(bool);
BLINK_PLATFORM_EXPORT static void EnablePaymentApp(bool);
BLINK_PLATFORM_EXPORT static void EnableWebPaymentAPICSP(bool);
BLINK_PLATFORM_EXPORT static void EnablePaymentRequest(bool);
BLINK_PLATFORM_EXPORT static void EnablePaymentRequestBasicCard(bool);
BLINK_PLATFORM_EXPORT static void EnablePaymentRequestRequiresUserActivation(

@ -638,6 +638,32 @@ void ValidateAndConvertPaymentDetailsUpdate(const PaymentDetailsUpdate* input,
}
}
// Checks whether Content Securituy Policy (CSP) allows a connection to the
// given `url`.
//
// If CSP is being enforced, then a CSP violation will be reported in the
// developer console.
//
// If CSP is not being enforced, then a CSP violation will be counted, but not
// reported to the web developer.
bool CSPAllowsConnectToSource(const KURL& url, ExecutionContext& context) {
const bool enforce_csp =
RuntimeEnabledFeatures::WebPaymentAPICSPEnabled(&context);
if (context.GetContentSecurityPolicy()->AllowConnectToSource(
url, /*url_before_redirects=*/url, RedirectStatus::kNoRedirect,
enforce_csp ? ReportingDisposition::kReport
: ReportingDisposition::kSuppressReporting)) {
return true; // Allow request.
}
if (enforce_csp)
return false; // Block request.
UseCounter::Count(context, WebFeature::kPaymentRequestCSPViolation);
return true; // Allow request.
}
void ValidateAndConvertPaymentMethodData(
const HeapVector<Member<PaymentMethodData>>& input,
const PaymentOptions* options,
@ -692,12 +718,11 @@ void ValidateAndConvertPaymentMethodData(
}
KURL url(payment_method_data->supportedMethod());
if (url.IsValid() &&
!execution_context.GetContentSecurityPolicy()->AllowConnectToSource(
url, url, RedirectStatus::kNoRedirect,
ReportingDisposition::kSuppressReporting)) {
UseCounter::Count(&execution_context,
WebFeature::kPaymentRequestCSPViolation);
if (url.IsValid() && !CSPAllowsConnectToSource(url, execution_context)) {
exception_state.ThrowRangeError(
payment_method_data->supportedMethod() +
" payment method identifier violates Content Security Policy.");
return;
}
method_names.insert(payment_method_data->supportedMethod());
@ -1693,9 +1718,9 @@ void PaymentRequest::DispatchPaymentRequestUpdateEvent(
// could have destroyed it.
if (GetExecutionContext() && !event->is_waiting_for_update()) {
// DispatchEvent runs synchronously. The method is_waiting_for_update()
// returns false if the merchant did not call event.updateWith() within the
// event handler, which is optional, so the renderer sends a message to the
// browser to re-enable UI interactions.
// returns false if the merchant did not call event.updateWith() within
// the event handler, which is optional, so the renderer sends a message
// to the browser to re-enable UI interactions.
const String& message = String::Format(
"No updateWith() call in '%s' event handler. User may see outdated "
"line items and total.",
@ -1705,8 +1730,8 @@ void PaymentRequest::DispatchPaymentRequestUpdateEvent(
mojom::ConsoleMessageSource::kJavaScript,
mojom::ConsoleMessageLevel::kWarning, message));
payment_provider_->OnPaymentDetailsNotUpdated();
// Make sure that updateWith() is only allowed to be called within the same
// event loop as the event dispatch. See
// Make sure that updateWith() is only allowed to be called within the
// same event loop as the event dispatch. See
// https://w3c.github.io/payment-request/#paymentrequest-updated-algorithm
event->start_waiting_for_update(true);
}

@ -260,6 +260,10 @@ void WebRuntimeFeatures::EnablePaymentApp(bool enable) {
RuntimeEnabledFeatures::SetPaymentAppEnabled(enable);
}
void WebRuntimeFeatures::EnableWebPaymentAPICSP(bool enable) {
RuntimeEnabledFeatures::SetWebPaymentAPICSPEnabled(enable);
}
void WebRuntimeFeatures::EnablePaymentRequest(bool enable) {
RuntimeEnabledFeatures::SetPaymentRequestEnabled(enable);
if (!enable) {

@ -2632,6 +2632,11 @@
depends_on: ["WebOTP"],
status: "stable",
},
{
name: "WebPaymentAPICSP",
origin_trial_feature_name: "WebPaymentAPICSP",
origin_trial_allows_third_party: true,
},
// WebShare is enabled by default on Android.
{
name: "WebShare",

@ -59518,6 +59518,7 @@ from previous Chrome versions.
<int value="339388667" label="SyncAndroidPromosWithTitle:enabled"/>
<int value="339419844" label="EmojiSuggestAddition:enabled"/>
<int value="339671131" label="disable-per-user-timezone"/>
<int value="340497722" label="WebPaymentAPICSP:enabled"/>
<int value="341152650" label="SoundContentSetting:enabled"/>
<int value="341851350" label="EnableDbusAndX11StatusIcons:enabled"/>
<int value="342431487" label="VizForWebView:enabled"/>
@ -61575,6 +61576,7 @@ from previous Chrome versions.
<int value="1659969202" label="AlignWakeUps:disabled"/>
<int value="1660002388" label="FilesWebDriveOffice:disabled"/>
<int value="1660491118" label="AllowAmbientEQ:enabled"/>
<int value="1660605883" label="WebPaymentAPICSP:disabled"/>
<int value="1660772828" label="NtpModulesDragAndDrop:enabled"/>
<int value="1661354480" label="BlockInsecurePrivateNetworkRequests:disabled"/>
<int value="1661870048" label="SkipUndecryptablePasswords:disabled"/>