зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1225968 - Add authentication secret to push API, r=kitcambridge,smaug
--HG-- extra : commitid : 5qD0xsj9DLR extra : rebase_source : 407d4152f897f87fa9b5fae8b6b93ef510cce59f
This commit is contained in:
Родитель
a21a8ed722
Коммит
6e6387eecb
|
@ -17,13 +17,15 @@ interface nsIPrincipal;
|
|||
* endpoint.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(dc201064-8e5c-4a26-bd37-d1e33558a903)]
|
||||
[scriptable, uuid(d83e398f-9920-4451-b23a-6d5a5ad2fa26)]
|
||||
interface nsIPushEndpointCallback : nsISupports
|
||||
{
|
||||
void onPushEndpoint(in nsresult status,
|
||||
in DOMString endpoint,
|
||||
in uint32_t keyLen,
|
||||
[array, size_is(keyLen)] in octet key);
|
||||
[array, size_is(keyLen)] in octet key,
|
||||
in uint32_t authSecretLen,
|
||||
[array, size_is(authSecretLen)] in octet authSecret);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -186,7 +186,8 @@ function PushEndpointCallback(pushManager, resolve, reject) {
|
|||
|
||||
PushEndpointCallback.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushEndpointCallback]),
|
||||
onPushEndpoint: function(ok, endpoint, keyLen, key) {
|
||||
onPushEndpoint: function(ok, endpoint, keyLen, key,
|
||||
authSecretLen, authSecretIn) {
|
||||
let {pushManager} = this;
|
||||
if (!Components.isSuccessCode(ok)) {
|
||||
this.reject(new pushManager._window.DOMException(
|
||||
|
@ -208,9 +209,17 @@ PushEndpointCallback.prototype = {
|
|||
keyView.set(key);
|
||||
}
|
||||
|
||||
let authSecret = null;
|
||||
if (authSecretLen) {
|
||||
authSecret = new ArrayBuffer(authSecretLen);
|
||||
let secretView = new Uint8Array(authSecret);
|
||||
secretView.set(authSecretIn);
|
||||
}
|
||||
|
||||
let sub = new pushManager._window.PushSubscription(endpoint,
|
||||
pushManager._scope,
|
||||
publicKey);
|
||||
publicKey,
|
||||
authSecret);
|
||||
sub.setPrincipal(pushManager._principal);
|
||||
this.resolve(sub);
|
||||
},
|
||||
|
|
|
@ -101,19 +101,26 @@ PushClient.prototype = {
|
|||
|
||||
_deliverPushEndpoint: function(request, registration) {
|
||||
if (!registration) {
|
||||
request.onPushEndpoint(Cr.NS_OK, "", 0, null);
|
||||
return;
|
||||
}
|
||||
if (registration.p256dhKey) {
|
||||
let key = new Uint8Array(registration.p256dhKey);
|
||||
request.onPushEndpoint(Cr.NS_OK,
|
||||
registration.pushEndpoint,
|
||||
key.length,
|
||||
key);
|
||||
request.onPushEndpoint(Cr.NS_OK, "", 0, null, 0, null);
|
||||
return;
|
||||
}
|
||||
|
||||
request.onPushEndpoint(Cr.NS_OK, registration.pushEndpoint, 0, null);
|
||||
let key;
|
||||
if (registration.p256dhKey) {
|
||||
key = new Uint8Array(registration.p256dhKey);
|
||||
}
|
||||
|
||||
let authSecret;
|
||||
if (registration.authSecret) {
|
||||
authSecret = new Uint8Array(registration.authSecret);
|
||||
}
|
||||
|
||||
request.onPushEndpoint(Cr.NS_OK,
|
||||
registration.pushEndpoint,
|
||||
key ? key.length : 0,
|
||||
key,
|
||||
authSecret ? authSecret.length : 0,
|
||||
authSecret);
|
||||
},
|
||||
|
||||
receiveMessage: function(aMessage) {
|
||||
|
@ -135,7 +142,7 @@ PushClient.prototype = {
|
|||
|
||||
case "PushService:Register:KO":
|
||||
case "PushService:Registration:KO":
|
||||
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null);
|
||||
request.onPushEndpoint(Cr.NS_ERROR_FAILURE, "", 0, null, 0, null);
|
||||
break;
|
||||
|
||||
case "PushService:Unregister:OK":
|
||||
|
|
|
@ -13,10 +13,16 @@ this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
|
|||
'getEncryptionKeyParams', 'getEncryptionParams',
|
||||
'base64UrlDecode'];
|
||||
|
||||
var ENCRYPT_INFO = new TextEncoder('utf-8').encode('Content-Encoding: aesgcm128');
|
||||
var NONCE_INFO = new TextEncoder('utf-8').encode('Content-Encoding: nonce');
|
||||
var UTF8 = new TextEncoder('utf-8');
|
||||
var ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
|
||||
var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
|
||||
var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
|
||||
var P256DH_INFO = UTF8.encode('P-256\0');
|
||||
|
||||
this.getEncryptionKeyParams = function(encryptKeyField) {
|
||||
if (!encryptKeyField) {
|
||||
return null;
|
||||
}
|
||||
var params = encryptKeyField.split(',');
|
||||
return params.reduce((m, p) => {
|
||||
var pmap = p.split(';').reduce(parseHeaderFieldParams, {});
|
||||
|
@ -41,7 +47,7 @@ var parseHeaderFieldParams = (m, v) => {
|
|||
// A quoted string with internal quotes is invalid for all the possible
|
||||
// values of this header field.
|
||||
m[v.substring(0, i).trim()] = v.substring(i + 1).trim()
|
||||
.replace(/^"(.*)"$/, '$1');
|
||||
.replace(/^"(.*)"$/, '$1');
|
||||
}
|
||||
return m;
|
||||
};
|
||||
|
@ -115,7 +121,7 @@ function hkdf(salt, ikm) {
|
|||
.then(prk => new hmac(prk));
|
||||
}
|
||||
|
||||
hkdf.prototype.generate = function(info, len) {
|
||||
hkdf.prototype.extract = function(info, len) {
|
||||
var input = concatArray([info, new Uint8Array([1])]);
|
||||
return this.prkhPromise
|
||||
.then(prkh => prkh.hash(input))
|
||||
|
@ -127,10 +133,10 @@ hkdf.prototype.generate = function(info, len) {
|
|||
});
|
||||
};
|
||||
|
||||
/* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
|
||||
/* generate a 96-bit nonce for use in GCM, 48-bits of which are populated */
|
||||
function generateNonce(base, index) {
|
||||
if (index >= Math.pow(2, 48)) {
|
||||
throw new Error('Error generating IV - index is too large.');
|
||||
throw new Error('Error generating nonce - index is too large.');
|
||||
}
|
||||
var nonce = base.slice(0, 12);
|
||||
nonce = new Uint8Array(nonce);
|
||||
|
@ -142,7 +148,11 @@ function generateNonce(base, index) {
|
|||
|
||||
this.PushCrypto = {
|
||||
|
||||
generateKeys: function() {
|
||||
generateAuthenticationSecret() {
|
||||
return crypto.getRandomValues(new Uint8Array(12));
|
||||
},
|
||||
|
||||
generateKeys() {
|
||||
return crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256'},
|
||||
true,
|
||||
['deriveBits'])
|
||||
|
@ -154,7 +164,8 @@ this.PushCrypto = {
|
|||
]));
|
||||
},
|
||||
|
||||
decodeMsg: function(aData, aPrivateKey, aRemotePublicKey, aSalt, aRs) {
|
||||
decodeMsg(aData, aPrivateKey, aPublicKey, aSenderPublicKey,
|
||||
aSalt, aRs, aAuthenticationSecret) {
|
||||
|
||||
if (aData.byteLength === 0) {
|
||||
// Zero length messages will be passed as null.
|
||||
|
@ -167,8 +178,9 @@ this.PushCrypto = {
|
|||
return Promise.reject(new Error('Data truncated'));
|
||||
}
|
||||
|
||||
let senderKey = base64UrlDecode(aSenderPublicKey)
|
||||
return Promise.all([
|
||||
crypto.subtle.importKey('raw', base64UrlDecode(aRemotePublicKey),
|
||||
crypto.subtle.importKey('raw', senderKey,
|
||||
{ name: 'ECDH', namedCurve: 'P-256' },
|
||||
false,
|
||||
['deriveBits']),
|
||||
|
@ -177,18 +189,12 @@ this.PushCrypto = {
|
|||
false,
|
||||
['deriveBits'])
|
||||
])
|
||||
.then(keys =>
|
||||
crypto.subtle.deriveBits({ name: 'ECDH', public: keys[0] }, keys[1], 256))
|
||||
.then(rawKey => {
|
||||
var kdf = new hkdf(base64UrlDecode(aSalt), new Uint8Array(rawKey));
|
||||
return Promise.all([
|
||||
kdf.generate(ENCRYPT_INFO, 16)
|
||||
.then(gcmBits =>
|
||||
crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
|
||||
['decrypt'])),
|
||||
kdf.generate(NONCE_INFO, 12)
|
||||
])
|
||||
})
|
||||
.then(keys => crypto.subtle.deriveBits({ name: 'ECDH', public: keys[0] }, keys[1], 256))
|
||||
.then(ikm => this._deriveKeyAndNonce(new Uint8Array(ikm),
|
||||
base64UrlDecode(aSalt),
|
||||
base64UrlDecode(aPublicKey),
|
||||
senderKey,
|
||||
aAuthenticationSecret))
|
||||
.then(r =>
|
||||
// AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
|
||||
Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
|
||||
|
@ -196,11 +202,57 @@ this.PushCrypto = {
|
|||
.then(r => concatArray(r));
|
||||
},
|
||||
|
||||
_decodeChunk: function(aSlice, aIndex, aNonce, aKey) {
|
||||
return crypto.subtle.decrypt({name: 'AES-GCM',
|
||||
iv: generateNonce(aNonce, aIndex)
|
||||
},
|
||||
aKey, aSlice)
|
||||
_deriveKeyAndNonce(ikm, salt, receiverKey, senderKey, authenticationSecret) {
|
||||
var kdfPromise;
|
||||
var context;
|
||||
// The authenticationSecret, when present, is mixed with the ikm using HKDF.
|
||||
// This is its primary purpose. However, since the authentication secret
|
||||
// was added at the same time that the info string was changed, we also use
|
||||
// its presence to change how the final info string is calculated:
|
||||
//
|
||||
// 1. When there is no authenticationSecret, the context string is simply
|
||||
// "Content-Encoding: <blah>". This corresponds to old, deprecated versions
|
||||
// of the content encoding. This should eventually be removed: bug 1230038.
|
||||
//
|
||||
// 2. When there is an authenticationSecret, the context string is:
|
||||
// "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
|
||||
// receiver key and sender key.
|
||||
if (authenticationSecret) {
|
||||
// Since we are using an authentication secret, we need to run an extra
|
||||
// round of HKDF with the authentication secret as salt.
|
||||
var authKdf = new hkdf(authenticationSecret, ikm);
|
||||
kdfPromise = authKdf.extract(AUTH_INFO, 32)
|
||||
.then(ikm2 => new hkdf(salt, ikm2));
|
||||
|
||||
// We also use the presence of the authentication secret to indicate that
|
||||
// we have extra context to add to the info parameter.
|
||||
context = concatArray([
|
||||
new Uint8Array([0]), P256DH_INFO,
|
||||
this._encodeLength(receiverKey), receiverKey,
|
||||
this._encodeLength(senderKey), senderKey
|
||||
]);
|
||||
} else {
|
||||
kdfPromise = Promise.resolve(new hkdf(salt, ikm));
|
||||
context = new Uint8Array(0);
|
||||
}
|
||||
return kdfPromise.then(kdf => Promise.all([
|
||||
kdf.extract(concatArray([ENCRYPT_INFO, context]), 16)
|
||||
.then(gcmBits => crypto.subtle.importKey('raw', gcmBits, 'AES-GCM', false,
|
||||
['decrypt'])),
|
||||
kdf.extract(concatArray([NONCE_INFO, context]), 12)
|
||||
]));
|
||||
},
|
||||
|
||||
_encodeLength(buffer) {
|
||||
return new Uint8Array([0, buffer.byteLength]);
|
||||
},
|
||||
|
||||
_decodeChunk(aSlice, aIndex, aNonce, aKey) {
|
||||
let params = {
|
||||
name: 'AES-GCM',
|
||||
iv: generateNonce(aNonce, aIndex)
|
||||
};
|
||||
return crypto.subtle.decrypt(params, aKey, aSlice)
|
||||
.then(decoded => {
|
||||
decoded = new Uint8Array(decoded);
|
||||
if (decoded.length == 0) {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "mozilla/unused.h"
|
||||
#include "mozilla/dom/PushManagerBinding.h"
|
||||
#include "mozilla/dom/PushSubscriptionBinding.h"
|
||||
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
|
||||
|
@ -124,11 +125,13 @@ PushSubscription::Unsubscribe(ErrorResult& aRv)
|
|||
PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
|
||||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const nsTArray<uint8_t>& aRawP256dhKey)
|
||||
const nsTArray<uint8_t>& aRawP256dhKey,
|
||||
const nsTArray<uint8_t>& aAuthSecret)
|
||||
: mGlobal(aGlobal)
|
||||
, mEndpoint(aEndpoint)
|
||||
, mScope(aScope)
|
||||
, mRawP256dhKey(aRawP256dhKey)
|
||||
, mAuthSecret(aAuthSecret)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -145,14 +148,18 @@ PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
|
|||
void
|
||||
PushSubscription::GetKey(JSContext* aCx,
|
||||
PushEncryptionKeyName aType,
|
||||
JS::MutableHandle<JSObject*> aP256dhKey)
|
||||
JS::MutableHandle<JSObject*> aKey)
|
||||
{
|
||||
if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
|
||||
aP256dhKey.set(ArrayBuffer::Create(aCx,
|
||||
mRawP256dhKey.Length(),
|
||||
mRawP256dhKey.Elements()));
|
||||
aKey.set(ArrayBuffer::Create(aCx,
|
||||
mRawP256dhKey.Length(),
|
||||
mRawP256dhKey.Elements()));
|
||||
} else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
|
||||
aKey.set(ArrayBuffer::Create(aCx,
|
||||
mAuthSecret.Length(),
|
||||
mAuthSecret.Elements()));
|
||||
} else {
|
||||
aP256dhKey.set(nullptr);
|
||||
aKey.set(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,6 +176,7 @@ PushSubscription::Constructor(GlobalObject& aGlobal,
|
|||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const Nullable<ArrayBuffer>& aP256dhKey,
|
||||
const Nullable<ArrayBuffer>& aAuthSecret,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(!aEndpoint.IsEmpty());
|
||||
|
@ -180,13 +188,20 @@ PushSubscription::Constructor(GlobalObject& aGlobal,
|
|||
if (!aP256dhKey.IsNull()) {
|
||||
const ArrayBuffer& key = aP256dhKey.Value();
|
||||
key.ComputeLengthAndData();
|
||||
rawKey.SetLength(key.Length());
|
||||
rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
|
||||
rawKey.InsertElementsAt(0, key.Data(), key.Length());
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> authSecret;
|
||||
if (!aAuthSecret.IsNull()) {
|
||||
const ArrayBuffer& sekrit = aAuthSecret.Value();
|
||||
sekrit.ComputeLengthAndData();
|
||||
authSecret.InsertElementsAt(0, sekrit.Data(), sekrit.Length());
|
||||
}
|
||||
RefPtr<PushSubscription> sub = new PushSubscription(global,
|
||||
aEndpoint,
|
||||
aScope,
|
||||
rawKey);
|
||||
aEndpoint,
|
||||
aScope,
|
||||
rawKey,
|
||||
authSecret);
|
||||
|
||||
return sub.forget();
|
||||
}
|
||||
|
@ -259,8 +274,12 @@ NS_INTERFACE_MAP_END
|
|||
|
||||
WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const nsTArray<uint8_t>& aRawP256dhKey)
|
||||
: mEndpoint(aEndpoint), mScope(aScope), mRawP256dhKey(aRawP256dhKey)
|
||||
const nsTArray<uint8_t>& aRawP256dhKey,
|
||||
const nsTArray<uint8_t>& aAuthSecret)
|
||||
: mEndpoint(aEndpoint)
|
||||
, mScope(aScope)
|
||||
, mRawP256dhKey(aRawP256dhKey)
|
||||
, mAuthSecret(aAuthSecret)
|
||||
{
|
||||
MOZ_ASSERT(!aScope.IsEmpty());
|
||||
MOZ_ASSERT(!aEndpoint.IsEmpty());
|
||||
|
@ -281,6 +300,7 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
|
|||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const Nullable<ArrayBuffer>& aP256dhKey,
|
||||
const Nullable<ArrayBuffer>& aAuthSecret,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
|
||||
|
@ -294,9 +314,19 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
|
|||
rawKey.SetLength(key.Length());
|
||||
rawKey.ReplaceElementsAt(0, key.Length(), key.Data(), key.Length());
|
||||
}
|
||||
|
||||
nsTArray<uint8_t> authSecret;
|
||||
if (!aAuthSecret.IsNull()) {
|
||||
const ArrayBuffer& sekrit = aAuthSecret.Value();
|
||||
sekrit.ComputeLengthAndData();
|
||||
authSecret.SetLength(sekrit.Length());
|
||||
authSecret.ReplaceElementsAt(0, sekrit.Length(),
|
||||
sekrit.Data(), sekrit.Length());
|
||||
}
|
||||
RefPtr<WorkerPushSubscription> sub = new WorkerPushSubscription(aEndpoint,
|
||||
aScope,
|
||||
rawKey);
|
||||
aScope,
|
||||
rawKey,
|
||||
authSecret);
|
||||
|
||||
return sub.forget();
|
||||
}
|
||||
|
@ -304,15 +334,20 @@ WorkerPushSubscription::Constructor(GlobalObject& aGlobal,
|
|||
void
|
||||
WorkerPushSubscription::GetKey(JSContext* aCx,
|
||||
PushEncryptionKeyName aType,
|
||||
JS::MutableHandle<JSObject*> aP256dhKey)
|
||||
JS::MutableHandle<JSObject*> aKey)
|
||||
{
|
||||
if (aType == mozilla::dom::PushEncryptionKeyName::P256dh &&
|
||||
!mRawP256dhKey.IsEmpty()) {
|
||||
aP256dhKey.set(ArrayBuffer::Create(aCx,
|
||||
mRawP256dhKey.Length(),
|
||||
mRawP256dhKey.Elements()));
|
||||
aKey.set(ArrayBuffer::Create(aCx,
|
||||
mRawP256dhKey.Length(),
|
||||
mRawP256dhKey.Elements()));
|
||||
} else if (aType == mozilla::dom::PushEncryptionKeyName::Auth &&
|
||||
!mAuthSecret.IsEmpty()) {
|
||||
aKey.set(ArrayBuffer::Create(aCx,
|
||||
mAuthSecret.Length(),
|
||||
mAuthSecret.Elements()));
|
||||
} else {
|
||||
aP256dhKey.set(nullptr);
|
||||
aKey.set(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,13 +535,15 @@ public:
|
|||
nsresult aStatus,
|
||||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const nsTArray<uint8_t>& aRawP256dhKey)
|
||||
const nsTArray<uint8_t>& aRawP256dhKey,
|
||||
const nsTArray<uint8_t>& aAuthSecret)
|
||||
: WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
|
||||
, mProxy(aProxy)
|
||||
, mStatus(aStatus)
|
||||
, mEndpoint(aEndpoint)
|
||||
, mScope(aScope)
|
||||
, mRawP256dhKey(aRawP256dhKey)
|
||||
, mAuthSecret(aAuthSecret)
|
||||
{ }
|
||||
|
||||
bool
|
||||
|
@ -518,7 +555,8 @@ public:
|
|||
promise->MaybeResolve(JS::NullHandleValue);
|
||||
} else {
|
||||
RefPtr<WorkerPushSubscription> sub =
|
||||
new WorkerPushSubscription(mEndpoint, mScope, mRawP256dhKey);
|
||||
new WorkerPushSubscription(mEndpoint, mScope,
|
||||
mRawP256dhKey, mAuthSecret);
|
||||
promise->MaybeResolve(sub);
|
||||
}
|
||||
} else {
|
||||
|
@ -537,6 +575,7 @@ private:
|
|||
nsString mEndpoint;
|
||||
nsString mScope;
|
||||
nsTArray<uint8_t> mRawP256dhKey;
|
||||
nsTArray<uint8_t> mAuthSecret;
|
||||
};
|
||||
|
||||
class GetSubscriptionCallback final : public nsIPushEndpointCallback
|
||||
|
@ -554,7 +593,9 @@ public:
|
|||
OnPushEndpoint(nsresult aStatus,
|
||||
const nsAString& aEndpoint,
|
||||
uint32_t aKeyLen,
|
||||
uint8_t* aKey) override
|
||||
uint8_t* aKey,
|
||||
uint32_t aAuthSecretLen,
|
||||
uint8_t* aAuthSecret) override
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
MOZ_ASSERT(mProxy, "OnPushEndpoint() called twice?");
|
||||
|
@ -572,16 +613,30 @@ public:
|
|||
nsTArray<uint8_t> rawP256dhKey(aKeyLen);
|
||||
rawP256dhKey.ReplaceElementsAt(0, aKeyLen, aKey, aKeyLen);
|
||||
|
||||
nsTArray<uint8_t> authSecret(aAuthSecretLen);
|
||||
authSecret.ReplaceElementsAt(0, aAuthSecretLen,
|
||||
aAuthSecret, aAuthSecretLen);
|
||||
|
||||
RefPtr<GetSubscriptionResultRunnable> r =
|
||||
new GetSubscriptionResultRunnable(proxy,
|
||||
aStatus,
|
||||
aEndpoint,
|
||||
mScope,
|
||||
rawP256dhKey);
|
||||
rawP256dhKey,
|
||||
authSecret);
|
||||
r->Dispatch(jsapi.cx());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Convenience method for use in this file.
|
||||
void
|
||||
OnPushEndpointError(nsresult aStatus)
|
||||
{
|
||||
Unused << NS_WARN_IF(NS_FAILED(
|
||||
OnPushEndpoint(aStatus, EmptyString(), 0, nullptr, 0, nullptr)));
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
~GetSubscriptionCallback()
|
||||
{}
|
||||
|
@ -619,23 +674,23 @@ public:
|
|||
PushPermissionState state;
|
||||
nsresult rv = GetPermissionState(principal, state);
|
||||
if (NS_FAILED(rv)) {
|
||||
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
|
||||
callback->OnPushEndpointError(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (state != PushPermissionState::Granted) {
|
||||
if (mAction == WorkerPushManager::GetSubscriptionAction) {
|
||||
callback->OnPushEndpoint(NS_OK, EmptyString(), 0, nullptr);
|
||||
callback->OnPushEndpointError(NS_OK);
|
||||
return NS_OK;
|
||||
}
|
||||
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
|
||||
callback->OnPushEndpointError(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPushClient> client =
|
||||
do_CreateInstance("@mozilla.org/push/PushClient;1");
|
||||
if (!client) {
|
||||
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
|
||||
callback->OnPushEndpointError(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -647,7 +702,7 @@ public:
|
|||
}
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
callback->OnPushEndpoint(NS_ERROR_FAILURE, EmptyString(), 0, nullptr);
|
||||
callback->OnPushEndpointError(NS_ERROR_FAILURE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,8 @@ public:
|
|||
explicit PushSubscription(nsIGlobalObject* aGlobal,
|
||||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const nsTArray<uint8_t>& aP256dhKey);
|
||||
const nsTArray<uint8_t>& aP256dhKey,
|
||||
const nsTArray<uint8_t>& aAuthSecret);
|
||||
|
||||
JSObject*
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
@ -87,13 +88,14 @@ public:
|
|||
void
|
||||
GetKey(JSContext* cx,
|
||||
PushEncryptionKeyName aType,
|
||||
JS::MutableHandle<JSObject*> aP256dhKey);
|
||||
JS::MutableHandle<JSObject*> aKey);
|
||||
|
||||
static already_AddRefed<PushSubscription>
|
||||
Constructor(GlobalObject& aGlobal,
|
||||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const Nullable<ArrayBuffer>& aP256dhKey,
|
||||
const Nullable<ArrayBuffer>& aAuthSecret,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
|
@ -111,6 +113,7 @@ private:
|
|||
nsString mEndpoint;
|
||||
nsString mScope;
|
||||
nsTArray<uint8_t> mRawP256dhKey;
|
||||
nsTArray<uint8_t> mAuthSecret;
|
||||
};
|
||||
|
||||
class PushManager final : public nsISupports
|
||||
|
@ -161,7 +164,8 @@ public:
|
|||
|
||||
explicit WorkerPushSubscription(const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const nsTArray<uint8_t>& aRawP256dhKey);
|
||||
const nsTArray<uint8_t>& aRawP256dhKey,
|
||||
const nsTArray<uint8_t>& aAuthSecret);
|
||||
|
||||
nsIGlobalObject*
|
||||
GetParentObject() const
|
||||
|
@ -177,6 +181,7 @@ public:
|
|||
const nsAString& aEndpoint,
|
||||
const nsAString& aScope,
|
||||
const Nullable<ArrayBuffer>& aP256dhKey,
|
||||
const Nullable<ArrayBuffer>& aAuthSecret,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void
|
||||
|
@ -199,6 +204,7 @@ private:
|
|||
nsString mEndpoint;
|
||||
nsString mScope;
|
||||
nsTArray<uint8_t> mRawP256dhKey;
|
||||
nsTArray<uint8_t> mAuthSecret;
|
||||
};
|
||||
|
||||
class WorkerPushManager final : public nsISupports
|
||||
|
|
|
@ -36,6 +36,7 @@ function PushRecord(props) {
|
|||
this.lastPush = props.lastPush || 0;
|
||||
this.p256dhPublicKey = props.p256dhPublicKey;
|
||||
this.p256dhPrivateKey = props.p256dhPrivateKey;
|
||||
this.authenticationSecret = props.authenticationSecret;
|
||||
this.setQuota(props.quota);
|
||||
this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
|
||||
}
|
||||
|
@ -220,6 +221,7 @@ PushRecord.prototype = {
|
|||
lastPush: this.lastPush,
|
||||
pushCount: this.pushCount,
|
||||
p256dhKey: this.p256dhPublicKey,
|
||||
authenticationSecret: this.authenticationSecret,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -787,17 +787,29 @@ this.PushService = {
|
|||
});
|
||||
},
|
||||
|
||||
ensureP256dhKey: function(record) {
|
||||
if (record.p256dhPublicKey && record.p256dhPrivateKey) {
|
||||
ensureCrypto: function(record) {
|
||||
if (record.authenticationSecret &&
|
||||
record.p256dhPublicKey &&
|
||||
record.p256dhPrivateKey) {
|
||||
return Promise.resolve(record);
|
||||
}
|
||||
|
||||
let keygen = Promise.resolve([]);
|
||||
if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
|
||||
keygen = PushCrypto.generateKeys();
|
||||
}
|
||||
// We do not have a encryption key. so we need to generate it. This
|
||||
// is only going to happen on db upgrade from version 4 to higher.
|
||||
return PushCrypto.generateKeys()
|
||||
.then(exportedKeys => {
|
||||
return keygen
|
||||
.then(([pubKey, privKey]) => {
|
||||
return this.updateRecordAndNotifyApp(record.keyID, record => {
|
||||
record.p256dhPublicKey = exportedKeys[0];
|
||||
record.p256dhPrivateKey = exportedKeys[1];
|
||||
if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
|
||||
record.p256dhPublicKey = pubKey;
|
||||
record.p256dhPrivateKey = privKey;
|
||||
}
|
||||
if (!record.authenticationSecret) {
|
||||
record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
|
||||
}
|
||||
return record;
|
||||
});
|
||||
}, error => {
|
||||
|
@ -873,9 +885,11 @@ this.PushService = {
|
|||
decodedPromise = PushCrypto.decodeMsg(
|
||||
message,
|
||||
record.p256dhPrivateKey,
|
||||
record.p256dhPublicKey,
|
||||
cryptoParams.dh,
|
||||
cryptoParams.salt,
|
||||
cryptoParams.rs
|
||||
cryptoParams.rs,
|
||||
cryptoParams.auth ? record.authenticationSecret : null
|
||||
);
|
||||
} else {
|
||||
decodedPromise = Promise.resolve(null);
|
||||
|
@ -889,6 +903,8 @@ this.PushService = {
|
|||
setTimeout(() => this._updateQuota(keyID),
|
||||
prefs.get("quotaUpdateDelay"));
|
||||
return notified;
|
||||
}, error => {
|
||||
console.error("receivedPushMessage: Error decrypting message", error);
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error("receivedPushMessage: Error notifying app", error);
|
||||
|
|
|
@ -151,10 +151,17 @@ PushChannelListener.prototype = {
|
|||
if (Components.isSuccessCode(aStatusCode) &&
|
||||
this._mainListener &&
|
||||
this._mainListener._pushService) {
|
||||
let requiresAuthenticationSecret = true;
|
||||
|
||||
var keymap = encryptKeyFieldParser(aRequest);
|
||||
var keymap = encryptKeyFieldParser(aRequest, "Crypto-Key");
|
||||
if (!keymap) {
|
||||
return;
|
||||
// Backward compatibility: use the absence of Crypto-Key to indicate
|
||||
// that the authentication secret isn't used.
|
||||
requiresAuthenticationSecret = false;
|
||||
keymap = encryptKeyFieldParser(aRequest, "Encryption-Key");
|
||||
if (!keymap) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
var enc = encryptFieldParser(aRequest);
|
||||
if (!enc || !enc.keyid) {
|
||||
|
@ -169,19 +176,24 @@ PushChannelListener.prototype = {
|
|||
|
||||
var msg = concatArray(this._message);
|
||||
|
||||
let cryptoParams = {
|
||||
dh: dh,
|
||||
salt: salt,
|
||||
rs: rs,
|
||||
auth: requiresAuthenticationSecret,
|
||||
};
|
||||
|
||||
this._mainListener._pushService._pushChannelOnStop(this._mainListener.uri,
|
||||
this._ackUri,
|
||||
msg,
|
||||
dh,
|
||||
salt,
|
||||
rs);
|
||||
cryptoParams);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function encryptKeyFieldParser(aRequest) {
|
||||
function encryptKeyFieldParser(aRequest, name) {
|
||||
try {
|
||||
var encryptKeyField = aRequest.getRequestHeader("Encryption-Key");
|
||||
var encryptKeyField = aRequest.getRequestHeader(name);
|
||||
return getEncryptionKeyParams(encryptKeyField);
|
||||
} catch(e) {
|
||||
// getRequestHeader can throw.
|
||||
|
@ -490,9 +502,10 @@ this.PushServiceHttp2 = {
|
|||
})
|
||||
.then(result =>
|
||||
PushCrypto.generateKeys()
|
||||
.then(exportedKeys => {
|
||||
result.p256dhPublicKey = exportedKeys[0];
|
||||
result.p256dhPrivateKey = exportedKeys[1];
|
||||
.then(([publicKey, privateKey]) => {
|
||||
result.p256dhPublicKey = publicKey;
|
||||
result.p256dhPrivateKey = privateKey;
|
||||
result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
|
||||
this._conns[result.subscriptionUri] = {
|
||||
channel: null,
|
||||
listener: null,
|
||||
|
@ -673,7 +686,7 @@ this.PushServiceHttp2 = {
|
|||
|
||||
for (let i = 0; i < aSubscriptions.length; i++) {
|
||||
let record = aSubscriptions[i];
|
||||
this._mainPushService.ensureP256dhKey(record).then(record => {
|
||||
this._mainPushService.ensureCrypto(record).then(record => {
|
||||
this._startSingleConnection(record);
|
||||
}, error => {
|
||||
console.error("startConnections: Error updating record",
|
||||
|
@ -800,14 +813,9 @@ this.PushServiceHttp2 = {
|
|||
}
|
||||
},
|
||||
|
||||
_pushChannelOnStop: function(aUri, aAckUri, aMessage, dh, salt, rs) {
|
||||
_pushChannelOnStop: function(aUri, aAckUri, aMessage, cryptoParams) {
|
||||
console.debug("pushChannelOnStop()");
|
||||
|
||||
let cryptoParams = {
|
||||
dh: dh,
|
||||
salt: salt,
|
||||
rs: rs,
|
||||
};
|
||||
this._mainPushService.receivedPushMessage(
|
||||
aUri, aMessage, cryptoParams, record => {
|
||||
// Always update the stored record.
|
||||
|
|
|
@ -63,9 +63,14 @@ function getCryptoParams(headers) {
|
|||
if (!headers) {
|
||||
return null;
|
||||
}
|
||||
var keymap = getEncryptionKeyParams(headers.encryption_key);
|
||||
var requiresAuthenticationSecret = true;
|
||||
var keymap = getEncryptionKeyParams(headers.crypto_key);
|
||||
if (!keymap) {
|
||||
return null;
|
||||
requiresAuthenticationSecret = false;
|
||||
keymap = getEncryptionKeyParams(headers.encryption_key);
|
||||
if (!keymap) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
var enc = getEncryptionParams(headers.encryption);
|
||||
if (!enc || !enc.keyid) {
|
||||
|
@ -77,7 +82,7 @@ function getCryptoParams(headers) {
|
|||
if (!dh || !salt || isNaN(rs) || (rs <= 1)) {
|
||||
return null;
|
||||
}
|
||||
return {dh, salt, rs};
|
||||
return {dh, salt, rs, auth: requiresAuthenticationSecret};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -802,7 +807,7 @@ this.PushServiceWebSocket = {
|
|||
if (this._dataEnabled) {
|
||||
this._mainPushService.getAllUnexpired().then(records =>
|
||||
Promise.all(records.map(record =>
|
||||
this._mainPushService.ensureP256dhKey(record).catch(error => {
|
||||
this._mainPushService.ensureCrypto(record).catch(error => {
|
||||
console.error("finishHandshake: Error updating record",
|
||||
record.keyID, error);
|
||||
})
|
||||
|
@ -1014,6 +1019,7 @@ this.PushServiceWebSocket = {
|
|||
.then(([publicKey, privateKey]) => {
|
||||
record.p256dhPublicKey = publicKey;
|
||||
record.p256dhPrivateKey = privateKey;
|
||||
record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
|
||||
return record;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,11 +11,13 @@ interface Principal;
|
|||
|
||||
enum PushEncryptionKeyName
|
||||
{
|
||||
"p256dh"
|
||||
"p256dh",
|
||||
"auth"
|
||||
};
|
||||
|
||||
[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
|
||||
ChromeConstructor(DOMString pushEndpoint, DOMString scope, ArrayBuffer? key)]
|
||||
ChromeConstructor(DOMString pushEndpoint, DOMString scope,
|
||||
ArrayBuffer? key, ArrayBuffer? authSecret)]
|
||||
interface PushSubscription
|
||||
{
|
||||
readonly attribute USVString endpoint;
|
||||
|
|
Загрузка…
Ссылка в новой задаче