Commit c17c199c authored by Sertay Sener's avatar Sertay Sener Committed by Commit Bot

Push: applicationServerKey field should accept a base64-encoded URL.

According to Push API spec[1], while subscribing to a push service, applicationServerKey field in push should also accept a base64url encoded string.

[1]https://w3c.github.io/push-api/#idl-def-pushsubscriptionoptionsinit

Bug: 802280

Change-Id: I8b02e4e5259b6359aece4c42eb66e1ce00b2bab3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1597492Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Commit-Queue: Sertay Sener <sesener@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#664674}
parent 48e950d1
......@@ -11,3 +11,6 @@ var kApplicationServerKey = new Uint8Array([
0x21, 0xD3, 0x71, 0x90, 0x13, 0xA8, 0xC1, 0xCF, 0xED, 0x20, 0xF7, 0x1F, 0xD1,
0x7F, 0xF2, 0x76, 0xB6, 0x01, 0x20, 0xD8, 0x35, 0xA5, 0xD9, 0x3C, 0x43, 0xFD
]);
// Base64URLEncoded version of |kApplicationServerKey|
const kBase64URLEncodedKey = "BFVSaqVujqpHlzYQwWY8HmW_oXvuSMnGu78CGFNyHQx7qeMRtwNSIdNxkBOowc_tIPcf0X_ydrYBINg1pdk8Q_0"
......@@ -128,6 +128,18 @@ function documentSubscribePushWithNumericKey() {
}).catch(sendErrorToTest);
}
function documentSubscribePushWithBase64URLEncodedString() {
navigator.serviceWorker.ready.then(function(swRegistration) {
return swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: kBase64URLEncodedKey
})
.then(function(subscription) {
sendResultToTest(subscription.endpoint);
});
}).catch(sendErrorToTest);
}
function workerSubscribePush() {
// Send the message to the worker for it to subscribe
navigator.serviceWorker.controller.postMessage({command: 'workerSubscribe'});
......@@ -142,11 +154,18 @@ function workerSubscribePushNoKey() {
}
function workerSubscribePushWithNumericKey(numericKey = '1234567890') {
// Send the message to the worker for it to subscribe with the given key
// Send the message to the worker for it to subscribe with the given numeric key
navigator.serviceWorker.controller.postMessage(
{command: 'workerSubscribeWithNumericKey', key: numericKey});
}
function workerSubscribePushWithBase64URLEncodedString() {
// Send the message to the worker for it to subscribe with the given Base64URLEncoded key
navigator.serviceWorker.controller.postMessage(
{command: 'workerSubscribePushWithBase64URLEncodedString',
key: kBase64URLEncodedKey});
}
function GetP256dh() {
navigator.serviceWorker.ready.then(function(swRegistration) {
return swRegistration.pushManager.getSubscription()
......
......@@ -54,6 +54,8 @@ self.addEventListener('message', function handler (event) {
} else if (event.data.command == 'workerSubscribeWithNumericKey') {
pushSubscriptionOptions.applicationServerKey =
new TextEncoder().encode(event.data.key);
} else if (event.data.command == 'workerSubscribePushWithBase64URLEncodedString') {
pushSubscriptionOptions.applicationServerKey = kBase64URLEncodedKey;
} else if (event.data.command == 'workerSubscribeNoKey') {
// Nothing to set up
} else {
......
......@@ -18,6 +18,8 @@ bindings_modules_generated_union_type_files = [
"$bindings_modules_v8_output_dir/animation_effect_or_animation_effect_sequence.h",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_dictionary.cc",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_dictionary.h",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_string.cc",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_string.h",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_usv_string.cc",
"$bindings_modules_v8_output_dir/array_buffer_or_array_buffer_view_or_usv_string.h",
"$bindings_modules_v8_output_dir/array_buffer_view_or_blob_or_string_or_form_data.cc",
......
......@@ -10,6 +10,7 @@
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/push_messaging/push_subscription_options.h"
#include "third_party/blink/renderer/modules/push_messaging/push_subscription_options_init.h"
#include "third_party/blink/renderer/platform/wtf/text/base64.h"
namespace blink {
namespace {
......@@ -27,28 +28,58 @@ const uint8_t kApplicationServerKey[kApplicationServerKeyLength] = {
0xA8, 0xC1, 0xCF, 0xED, 0x20, 0xF7, 0x1F, 0xD1, 0x7F, 0xF2, 0x76,
0xB6, 0x01, 0x20, 0xD8, 0x35, 0xA5, 0xD9, 0x3C, 0x43, 0xDF};
void IsApplicationServerKeyValid(WebPushSubscriptionOptions output) {
// Copy the key into a size+1 buffer so that it can be treated as a null
// terminated string for the purposes of EXPECT_EQ.
uint8_t sender_key[kApplicationServerKeyLength + 1];
for (unsigned i = 0; i < kApplicationServerKeyLength; i++)
sender_key[i] = kApplicationServerKey[i];
sender_key[kApplicationServerKeyLength] = 0x0;
ASSERT_EQ(output.application_server_key.length(),
kApplicationServerKeyLength);
ASSERT_EQ(reinterpret_cast<const char*>(sender_key),
output.application_server_key);
}
TEST(PushManagerTest, ValidSenderKey) {
PushSubscriptionOptionsInit* options = PushSubscriptionOptionsInit::Create();
options->setApplicationServerKey(
ArrayBufferOrArrayBufferView::FromArrayBuffer(DOMArrayBuffer::Create(
kApplicationServerKey, kApplicationServerKeyLength)));
ArrayBufferOrArrayBufferViewOrString::FromArrayBuffer(
DOMArrayBuffer::Create(kApplicationServerKey,
kApplicationServerKeyLength)));
DummyExceptionStateForTesting exception_state;
WebPushSubscriptionOptions output =
PushSubscriptionOptions::ToWeb(options, exception_state);
EXPECT_FALSE(exception_state.HadException());
EXPECT_EQ(output.application_server_key.length(),
kApplicationServerKeyLength);
ASSERT_FALSE(exception_state.HadException());
// Copy the key into a size+1 buffer so that it can be treated as a null
// terminated string for the purposes of EXPECT_EQ.
uint8_t sender_key[kApplicationServerKeyLength + 1];
for (unsigned i = 0; i < kApplicationServerKeyLength; i++)
sender_key[i] = kApplicationServerKey[i];
sender_key[kApplicationServerKeyLength] = 0x0;
EXPECT_EQ(reinterpret_cast<const char*>(sender_key),
output.application_server_key);
EXPECT_FALSE(output.application_server_key.empty());
ASSERT_NO_FATAL_FAILURE(IsApplicationServerKeyValid(output));
}
// applicationServerKey should be Unpadded 'base64url'
// https://tools.ietf.org/html/rfc7515#appendix-C
inline bool RemovePad(UChar character) {
return character == '=';
}
TEST(PushManagerTest, ValidBase64URLWithoutPaddingSenderKey) {
PushSubscriptionOptionsInit* options =
MakeGarbageCollected<PushSubscriptionOptionsInit>();
String base64_url =
WTF::Base64URLEncode(reinterpret_cast<const char*>(kApplicationServerKey),
kApplicationServerKeyLength);
base64_url = base64_url.RemoveCharacters(RemovePad);
options->setApplicationServerKey(
ArrayBufferOrArrayBufferViewOrString::FromString(base64_url));
DummyExceptionStateForTesting exception_state;
WebPushSubscriptionOptions output =
PushSubscriptionOptions::ToWeb(options, exception_state);
ASSERT_FALSE(exception_state.HadException());
ASSERT_NO_FATAL_FAILURE(IsApplicationServerKeyValid(output));
}
TEST(PushManagerTest, InvalidSenderKeyLength) {
......@@ -56,13 +87,49 @@ TEST(PushManagerTest, InvalidSenderKeyLength) {
memset(sender_key, 0, sizeof(sender_key));
PushSubscriptionOptionsInit* options = PushSubscriptionOptionsInit::Create();
options->setApplicationServerKey(
ArrayBufferOrArrayBufferView::FromArrayBuffer(
ArrayBufferOrArrayBufferViewOrString::FromArrayBuffer(
DOMArrayBuffer::Create(sender_key, kMaxKeyLength + 1)));
DummyExceptionStateForTesting exception_state;
WebPushSubscriptionOptions output =
PushSubscriptionOptions::ToWeb(options, exception_state);
EXPECT_TRUE(exception_state.HadException());
ASSERT_TRUE(exception_state.HadException());
ASSERT_EQ(exception_state.Message(),
"The provided applicationServerKey is not valid.");
}
TEST(PushManagerTest, InvalidBase64SenderKey) {
PushSubscriptionOptionsInit* options =
MakeGarbageCollected<PushSubscriptionOptionsInit>();
options->setApplicationServerKey(
ArrayBufferOrArrayBufferViewOrString::FromString(WTF::Base64Encode(
reinterpret_cast<const char*>(kApplicationServerKey),
kApplicationServerKeyLength)));
DummyExceptionStateForTesting exception_state;
WebPushSubscriptionOptions output =
PushSubscriptionOptions::ToWeb(options, exception_state);
ASSERT_TRUE(exception_state.HadException());
ASSERT_EQ(exception_state.Message(),
"The provided applicationServerKey is not encoded as base64url "
"without padding.");
}
TEST(PushManagerTest, InvalidBase64URLWithPaddingSenderKey) {
PushSubscriptionOptionsInit* options =
MakeGarbageCollected<PushSubscriptionOptionsInit>();
options->setApplicationServerKey(
ArrayBufferOrArrayBufferViewOrString::FromString(WTF::Base64URLEncode(
reinterpret_cast<const char*>(kApplicationServerKey),
kApplicationServerKeyLength)));
DummyExceptionStateForTesting exception_state;
WebPushSubscriptionOptions output =
PushSubscriptionOptions::ToWeb(options, exception_state);
ASSERT_TRUE(exception_state.HadException());
ASSERT_EQ(exception_state.Message(),
"The provided applicationServerKey is not encoded as base64url "
"without padding.");
}
} // namespace
......
......@@ -5,12 +5,15 @@
#include "third_party/blink/renderer/modules/push_messaging/push_subscription_options.h"
#include "third_party/blink/public/common/push_messaging/web_push_subscription_options.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
#include "third_party/blink/renderer/modules/push_messaging/push_subscription_options_init.h"
#include "third_party/blink/renderer/platform/bindings/exception_code.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
#include "third_party/blink/renderer/platform/wtf/text/base64.h"
namespace blink {
namespace {
......@@ -18,10 +21,12 @@ namespace {
const int kMaxApplicationServerKeyLength = 255;
std::string BufferSourceToString(
const ArrayBufferOrArrayBufferView& application_server_key,
const ArrayBufferOrArrayBufferViewOrString& application_server_key,
ExceptionState& exception_state) {
char* input;
int length;
Vector<char> decoded_application_server_key;
// Convert the input array into a string of bytes.
if (application_server_key.IsArrayBuffer()) {
input =
......@@ -34,6 +39,17 @@ std::string BufferSourceToString(
.View()
->buffer()
->ByteLength();
} else if (application_server_key.IsString()) {
if (!Base64UnpaddedURLDecode(application_server_key.GetAsString(),
decoded_application_server_key)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidCharacterError,
"The provided applicationServerKey is not encoded as base64url "
"without padding.");
return std::string();
}
input = reinterpret_cast<char*>(decoded_application_server_key.data());
length = decoded_application_server_key.size();
} else {
NOTREACHED();
return std::string();
......
......@@ -6,5 +6,5 @@
dictionary PushSubscriptionOptionsInit {
boolean userVisibleOnly = false;
BufferSource? applicationServerKey = null;
(BufferSource or DOMString)? applicationServerKey = null;
};
......@@ -259,6 +259,17 @@ bool Base64Decode(const String& in,
should_ignore_character, policy);
}
bool Base64UnpaddedURLDecode(const String& in,
Vector<char>& out,
CharacterMatchFunctionPtr should_ignore_character,
Base64DecodePolicy policy) {
if (in.Contains('+') || in.Contains('/') || in.Contains('='))
return false;
return Base64Decode(NormalizeToBase64(in), out, should_ignore_character,
policy);
}
String Base64URLEncode(const char* data,
unsigned length,
Base64EncodePolicy policy) {
......
......@@ -80,6 +80,11 @@ WTF_EXPORT bool Base64Decode(
Vector<char>&,
CharacterMatchFunctionPtr should_ignore_character = nullptr,
Base64DecodePolicy = kBase64DoNotValidatePadding);
WTF_EXPORT bool Base64UnpaddedURLDecode(
const String& in,
Vector<char>&,
CharacterMatchFunctionPtr should_ignore_character = nullptr,
Base64DecodePolicy = kBase64DoNotValidatePadding);
// Given an encoding in either base64 or base64url, returns a normalized
// encoding in plain base64.
......
......@@ -5,8 +5,8 @@
Subscribing with applicationServerKey should succeed only when the
applicationServerKey is valid.
</title>
<script src="resources/push-constants.js"></script>
<script src="resources/test-helpers.js"></script>
<script src="./resources/push-constants.js"></script>
<script src="./resources/test-helpers.js"></script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../resources/testharness-helpers.js"></script>
......@@ -14,6 +14,18 @@
</head>
<body>
<script>
// Subscribe should fail given a gcm sender id encoded as base64 url safe string.
promise_test(function(test) {
return registerAndSubscribePushWithBase64urlString(test, btoa('0123456789'))
.then(function(pushSubscription) {
assert_unreached('Subscribe should have failed.');
})
.catch (function(e) {
assert_true(e.message.includes(
'The provided applicationServerKey is not encoded as base64url without padding'));
});
}, 'Subscribing with a gcm sender id encoded as base64 url safe string should fail');
// Subscribe should succeed given a valid numeric sender ID.
promise_test(function(test) {
return registerAndSubscribePushWithString(test, '0123456789')
......@@ -32,6 +44,39 @@
});
}, 'Subscribing with a valid p256 applicationServerKey should succeed');
// Subscribe should succeed given a valid base64url encoded key.
promise_test(function(test) {
return registerAndSubscribePushWithBase64urlString(test, VALID_BASE64URL_ENCODED_KEY)
.then(function(pushSubscription) {
assert_true(
pushSubscription.endpoint.includes('StandardizedEndpoint'));
});
}, 'Subscribing with a valid base64url encoded key should succeed');
// Subscribe should fail given a base64 encoded key instead of base64url.
promise_test(function(test) {
return registerAndSubscribePushWithBase64urlString(test, INVALID_BASE64_ENCODED_KEY)
.then(function(pushSubscription) {
assert_unreached('Subscribe should have failed.');
})
.catch (function(e) {
assert_true(e.message.includes(
'The provided applicationServerKey is not encoded as base64url without padding'));
});
}, 'Subscribing with a base64 encoded string should fail');
// Subscribe should fail given an invalid base64url encoded string.
promise_test(function(test) {
return registerAndSubscribePushWithBase64urlString(test, INVALID_BASE64URL_ENCODED_KEY)
.then(function(pushSubscription) {
assert_unreached('Subscribe should have failed.');
})
.catch (function(e) {
assert_true(e.message.includes(
'The provided applicationServerKey is not encoded as base64url without padding'));
});
}, 'Subscribing with an invalid base64url encoded string should fail');
// Subscribe should fail given a non-numeric sender ID.
promise_test(function(test) {
return registerAndSubscribePushWithString(test, '01234a56789')
......
......@@ -10,15 +10,12 @@
</head>
<body>
<script>
// When running this test manually, grant permission when prompted.
// This test verifies that push subscriptions made with a valid P-256
// applicationServerKey return the standardized endpoint.
promise_test(function(test) {
var workerUrl = 'resources/empty_worker.js';
var workerScope = 'resources/scope/' + location.pathname;
var swRegistration;
function test_standardized_endpoint(test, applicationServerKey) {
var workerUrl = 'resources/empty_worker.js';
var workerScope = 'resources/scope/' + location.pathname;
var swRegistration;
return service_worker_unregister_and_register(test, workerUrl, workerScope)
return service_worker_unregister_and_register(test, workerUrl, workerScope)
.then(function(serviceWorkerRegistration) {
swRegistration = serviceWorkerRegistration;
return wait_for_state(test, swRegistration.installing, 'activated');
......@@ -29,14 +26,28 @@
return swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: new Uint8Array(PUBLIC_KEY)
applicationServerKey: applicationServerKey
});
})
.then(function(pushSubscription) {
assert_true(pushSubscription.endpoint.includes('StandardizedEndpoint'));
});
}
// When running this test manually, grant permission when prompted.
// This test verifies that push subscriptions made with a valid P-256
// applicationServerKey return the standardized endpoint.
promise_test(function(test) {
return test_standardized_endpoint(test, new Uint8Array(PUBLIC_KEY));
}, 'Subscribing with an applicationServerKey should return the standardized endpoint');
// When running this test manually, grant permission when prompted.
// This test verifies that push subscriptions made with a valid base64url encoded
// applicationServerKey return the standardized endpoint.
promise_test(function(test) {
return test_standardized_endpoint(test, VALID_BASE64URL_ENCODED_KEY);
}, 'Subscribing with a base64url encoded key should return the standardized endpoint');
</script>
</body>
</html>
......@@ -12,3 +12,9 @@ const PUBLIC_KEY = [
0x0E, 0x10, 0x7F, 0xCD, 0xA3, 0x44, 0x8C, 0x65, 0x54, 0x64, 0x7E,
0x25, 0xF3, 0x67, 0xF4, 0x7C, 0x4B, 0x0C, 0xBD, 0xCF, 0xF4
];
const VALID_BASE64URL_ENCODED_KEY = "BFVSaqVujqpHlzYQwWY8HmW_oXvuSMnGu78CGFNyHQx7qeMRtwNSIdNxkBOowc_tIPcf0X_ydrYBINg1pdk8Q98"
const INVALID_BASE64URL_ENCODED_KEY = "BJEOX8aRBRLJ1SitNXbDH9Lc5CCIsTE33YwGakcXvoQ5K7KZq13-jxB2vhMTcX9c4-tC0h6CIFifzJhH6cNS!!!"
const INVALID_BASE64_ENCODED_KEY = "BJEOX8aRBRLJ1SitNXbDH9Lc5CCIsTE33YwGakcXvoQ5K7KZq13+jxB2vhMTcX9c4+tC0h6CIFifzJhH6cNS/wQ="
\ No newline at end of file
......@@ -30,6 +30,12 @@ function registerAndSubscribePushWithString(test, serverKeyString) {
new TextEncoder().encode(serverKeyString));
}
// Registers a service worker and subscribes to push using the given base64url string
// as an applicationServerKey.
function registerAndSubscribePushWithBase64urlString(test, serverKeyString) {
return registerAndSubscribePush(test, serverKeyString);
}
// Subscribes to push with the given application server key. serverKey should be
// a Uint8Array.
function registerAndSubscribePush(test, serverKey) {
......
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