Bug 1225968 - Add authentication secret to push API, r=kitcambridge,smaug

--HG--
extra : commitid : 5qD0xsj9DLR
extra : rebase_source : 407d4152f897f87fa9b5fae8b6b93ef510cce59f
This commit is contained in:
Martin Thomson 2015-12-09 07:26:42 +11:00
Родитель a21a8ed722
Коммит 6e6387eecb
11 изменённых файлов: 268 добавлений и 103 удалений

Просмотреть файл

@ -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;