diff --git a/dom/push/PushServiceHttp2.jsm b/dom/push/PushServiceHttp2.jsm index b9303bf4493f..1711723cf4ce 100644 --- a/dom/push/PushServiceHttp2.jsm +++ b/dom/push/PushServiceHttp2.jsm @@ -330,6 +330,7 @@ SubscriptionListener.prototype = { scope: this._subInfo.record.scope, originAttributes: this._subInfo.record.originAttributes, systemRecord: this._subInfo.record.systemRecord, + appServerKey: this._subInfo.record.appServerKey, ctime: Date.now(), }); diff --git a/dom/push/PushServiceWebSocket.jsm b/dom/push/PushServiceWebSocket.jsm index dafd110b9391..38c441ce6a89 100644 --- a/dom/push/PushServiceWebSocket.jsm +++ b/dom/push/PushServiceWebSocket.jsm @@ -906,6 +906,7 @@ this.PushServiceWebSocket = { originAttributes: tmp.record.originAttributes, version: null, systemRecord: tmp.record.systemRecord, + appServerKey: tmp.record.appServerKey, ctime: Date.now(), }); Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime); @@ -1047,6 +1048,13 @@ this.PushServiceWebSocket = { let data = {channelID: this._generateID(), messageType: "register"}; + if (record.appServerKey) { + data.key = ChromeUtils.base64URLEncode(record.appServerKey, { + // The Push server requires padding. + pad: true, + }); + } + return new Promise((resolve, reject) => { this._registerRequests.set(data.channelID, { record: record, diff --git a/dom/push/test/mochitest.ini b/dom/push/test/mochitest.ini index 1ee91ce42b5a..7dc3587361e8 100644 --- a/dom/push/test/mochitest.ini +++ b/dom/push/test/mochitest.ini @@ -12,6 +12,7 @@ support-files = [test_has_permissions.html] [test_permissions.html] [test_register.html] +[test_register_key.html] [test_multiple_register.html] [test_multiple_register_during_service_activation.html] [test_unregister.html] diff --git a/dom/push/test/test_register_key.html b/dom/push/test/test_register_key.html new file mode 100644 index 000000000000..dcfce5b8e309 --- /dev/null +++ b/dom/push/test/test_register_key.html @@ -0,0 +1,206 @@ + + + + + Test for Bug 1247685 + + + + + + +Mozilla Bug 1247685 +

+ +
+
+ + + + + diff --git a/dom/push/test/worker.js b/dom/push/test/worker.js index 53e6b2118be8..0e26f228d013 100644 --- a/dom/push/test/worker.js +++ b/dom/push/test/worker.js @@ -73,20 +73,20 @@ function handlePush(event) { broadcast(event, {type: "finished", okay: "no"}); } -function handleMessage(event) { - if (event.data.type == "publicKey") { - reply(event, self.registration.pushManager.getSubscription().then( +var testHandlers = { + publicKey(data) { + return self.registration.pushManager.getSubscription().then( subscription => ({ p256dh: subscription.getKey("p256dh"), auth: subscription.getKey("auth"), }) - )); - return; - } - if (event.data.type == "resubscribe") { - reply(event, self.registration.pushManager.getSubscription().then( + ); + }, + + resubscribe(data) { + return self.registration.pushManager.getSubscription().then( subscription => { - assert(subscription.endpoint == event.data.endpoint, + assert(subscription.endpoint == data.endpoint, "Wrong push endpoint in worker"); return subscription.unsubscribe(); } @@ -100,11 +100,11 @@ function handleMessage(event) { return { endpoint: subscription.endpoint, }; - })); - return; - } - if (event.data.type == "denySubscribe") { - reply(event, self.registration.pushManager.getSubscription().then( + }); + }, + + denySubscribe(data) { + return self.registration.pushManager.getSubscription().then( subscription => { assert(!subscription, "Should not return worker subscription with revoked permission"); @@ -117,11 +117,34 @@ function handleMessage(event) { }; }); } - )); - return; + ); + }, + + subscribeWithKey(data) { + return self.registration.pushManager.subscribe({ + applicationServerKey: data.key, + }).then(subscription => { + return { + endpoint: subscription.endpoint, + key: subscription.options.applicationServerKey, + }; + }, error => { + return { + isDOMException: error instanceof DOMException, + name: error.name, + }; + }); + }, +}; + +function handleMessage(event) { + var handler = testHandlers[event.data.type]; + if (handler) { + reply(event, handler(event.data)); + } else { + reply(event, Promise.reject( + "Invalid message type: " + event.data.type)); } - reply(event, Promise.reject( - "Invalid message type: " + event.data.type)); } function handlePushSubscriptionChange(event) { diff --git a/dom/push/test/xpcshell/head.js b/dom/push/test/xpcshell/head.js index 6847e0fc1756..0475bcac06ae 100644 --- a/dom/push/test/xpcshell/head.js +++ b/dom/push/test/xpcshell/head.js @@ -455,6 +455,15 @@ var setUpServiceInParent = Task.async(function* (service, db) { })); }, onRegister(request) { + if (request.key) { + let appServerKey = new Uint8Array( + ChromeUtils.base64URLDecode(request.key, { + padding: "require", + }) + ); + equal(appServerKey.length, 65, 'Wrong app server key length'); + equal(appServerKey[0], 4, 'Wrong app server key format'); + } this.serverSendMsg(JSON.stringify({ messageType: 'register', uaid: userAgentID, diff --git a/dom/push/test/xpcshell/test_service_child.js b/dom/push/test/xpcshell/test_service_child.js index bce1e8e93349..ab313e040fb3 100644 --- a/dom/push/test/xpcshell/test_service_child.js +++ b/dom/push/test/xpcshell/test_service_child.js @@ -3,10 +3,26 @@ 'use strict'; +Cu.importGlobalProperties(["crypto"]); + const {PushDB, PushService, PushServiceWebSocket} = serviceExports; var db; +function done() { + do_test_finished(); + run_next_test(); +} + +function generateKey() { + return crypto.subtle.generateKey({ + name: "ECDSA", + namedCurve: "P-256", + }, true, ["sign", "verify"]).then(cryptoKey => + crypto.subtle.exportKey("raw", cryptoKey.publicKey) + ).then(publicKey => new Uint8Array(publicKey)); +} + function run_test() { if (isParent) { do_get_profile(); @@ -40,6 +56,69 @@ add_test(function test_subscribe_success() { ); }); +add_test(function test_subscribeWithKey_error() { + do_test_pending(); + + let invalidKey = [0, 1]; + PushServiceComponent.subscribeWithKey( + 'https://example.com/sub-key/invalid', + Services.scriptSecurityManager.getSystemPrincipal(), + invalidKey.length, + invalidKey, + (result, subscription) => { + ok(!Components.isSuccessCode(result), 'Expected error creating subscription with invalid key'); + equal(result, Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, 'Wrong error code for invalid key'); + strictEqual(subscription, null, 'Unexpected subscription'); + + do_test_finished(); + run_next_test(); + } + ); +}); + +add_test(function test_subscribeWithKey_success() { + do_test_pending(); + + generateKey().then(key => { + PushServiceComponent.subscribeWithKey( + 'https://example.com/sub-key/ok', + Services.scriptSecurityManager.getSystemPrincipal(), + key.length, + key, + (result, subscription) => { + ok(Components.isSuccessCode(result), 'Error creating subscription with key'); + notStrictEqual(subscription, null, 'Expected subscription'); + done(); + } + ); + }, error => { + ok(false, "Error generating app server key"); + done(); + }); +}); + +add_test(function test_subscribeWithKey_conflict() { + do_test_pending(); + + generateKey().then(differentKey => { + PushServiceComponent.subscribeWithKey( + 'https://example.com/sub-key/ok', + Services.scriptSecurityManager.getSystemPrincipal(), + differentKey.length, + differentKey, + (result, subscription) => { + ok(!Components.isSuccessCode(result), 'Expected error creating subscription with conflicting key'); + equal(result, Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR, 'Wrong error code for mismatched key'); + strictEqual(subscription, null, 'Unexpected subscription'); + done(); + } + ); + }, error => { + ok(false, "Error generating different app server key"); + done(); + }); +}); + add_test(function test_subscribe_error() { do_test_pending(); PushServiceComponent.subscribe(