feat(keys): Add special-case derivation of legacy sync key. (#14); r=vladikoff

This commit is contained in:
Ryan Kelly 2018-04-19 08:23:52 +10:00 коммит произвёл GitHub
Родитель 4ddb967fe6
Коммит 89ba7c75f7
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 43098 добавлений и 45467 удалений

29074
dist/fxa-crypto-relier/fxa-crypto-deriver.amd.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

29074
dist/fxa-crypto-relier/fxa-crypto-deriver.js поставляемый

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

30313
dist/fxa-crypto-relier/fxa-crypto-relier.js поставляемый

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -54,12 +54,17 @@ return scopedKeys.deriveScopedKey({
* [deriver-ScopedKeys](#module_deriver-ScopedKeys)
* [~deriveScopedKey(options)](#module_deriver-ScopedKeys..deriveScopedKey) ⇒ <code>Promise</code>
* [~_deriveLegacySyncKey(options)](#module_deriver-ScopedKeys.._deriveLegacySyncKey) ⇒ <code>Promise</code>
* [~_deriveHKDF(salt, initialKeyingMaterial, info, keyLength)](#module_deriver-ScopedKeys.._deriveHKDF) ⇒ <code>Promise</code>
<a name="module_deriver-ScopedKeys..deriveScopedKey"></a>
### deriver-ScopedKeys~deriveScopedKey(options) ⇒ <code>Promise</code>
Derive a scoped key
Derive a scoped key.
This method derives the key material for a particular scope from the user's master key material.
For most scopes it will produce a JWK containing a 32-byte symmetric key.
There is also special-case support for a legacy key-derivation algorithm used by Firefox Sync,
which generates a 64-byte key when `options.identifier` is 'https://identity.mozilla.com/apps/oldsync'.
**Kind**: inner method of [<code>deriver-ScopedKeys</code>](#module_deriver-ScopedKeys)
@ -72,6 +77,20 @@ Derive a scoped key
| options.identifier | <code>string</code> | a unique URI string identifying the requested scoped key |
| options.uid | <code>string</code> | a 16-byte Firefox Account UID hex string |
<a name="module_deriver-ScopedKeys.._deriveLegacySyncKey"></a>
### deriver-ScopedKeys~_deriveLegacySyncKey(options) ⇒ <code>Promise</code>
Derive a scoped key using the special legacy algorithm from Firefox Sync.
**Kind**: inner method of [<code>deriver-ScopedKeys</code>](#module_deriver-ScopedKeys)
**Access**: private
| Param | Type | Description |
| --- | --- | --- |
| options | <code>object</code> | required set of options to derive the scoped key |
| options.inputKey | <code>string</code> | input key hex string that the scoped key is derived from |
| options.keyRotationTimestamp | <code>number</code> | A 13-digit number, the timestamp in milliseconds at which this scoped key most recently changed |
<a name="module_deriver-ScopedKeys.._deriveHKDF"></a>
### deriver-ScopedKeys~_deriveHKDF(salt, initialKeyingMaterial, info, keyLength) ⇒ <code>Promise</code>

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

@ -4,8 +4,10 @@
const HKDF = require('node-hkdf');
const base64url = require('base64url');
const jose = require('node-jose');
const KEY_LENGTH = 48;
const LEGACY_SYNC_SCOPE = 'https://identity.mozilla.com/apps/oldsync';
/**
* Scoped key deriver
@ -26,7 +28,11 @@ const KEY_LENGTH = 48;
*/
class ScopedKeys {
/**
* Derive a scoped key
* Derive a scoped key.
* This method derives the key material for a particular scope from the user's master key material.
* For most scopes it will produce a JWK containing a 32-byte symmetric key.
* There is also special-case support for a legacy key-derivation algorithm used by Firefox Sync,
* which generates a 64-byte key when `options.identifier` is 'https://identity.mozilla.com/apps/oldsync'.
* @method deriveScopedKey
* @param {object} options - required set of options to derive a scoped key
* @param {string} options.inputKey - input key hex string that the scoped key is derived from
@ -63,6 +69,10 @@ class ScopedKeys {
throw new Error('keyRotationTimestamp must be a 13-digit number');
}
if (options.identifier === LEGACY_SYNC_SCOPE) {
return resolve(this._deriveLegacySyncKey(options));
}
const context = 'identity.mozilla.com/picl/v1/scoped_key\n' +
options.identifier;
const contextBuf = Buffer.from(context);
@ -74,19 +84,57 @@ class ScopedKeys {
scope: options.identifier,
};
this._deriveHKDF(saltBuf, Buffer.concat([inputKeyBuf, keyRotationSecretBuf]), contextBuf, KEY_LENGTH)
.then((key) => {
const kid = key.slice(0, 16);
const k = key.slice(16, 48);
const keyTimestamp = Math.round(options.keyRotationTimestamp / 1000);
return resolve(
this._deriveHKDF(saltBuf, Buffer.concat([inputKeyBuf, keyRotationSecretBuf]), contextBuf, KEY_LENGTH)
.then((key) => {
const kid = key.slice(0, 16);
const k = key.slice(16, 48);
const keyTimestamp = Math.round(options.keyRotationTimestamp / 1000);
scopedKey.k = base64url(k);
scopedKey.kid = keyTimestamp + '-' + base64url(kid);
scopedKey.k = base64url(k);
scopedKey.kid = keyTimestamp + '-' + base64url(kid);
resolve(scopedKey);
});
return scopedKey;
})
);
});
}
/**
* Derive a scoped key using the special legacy algorithm from Firefox Sync.
* @method _deriveLegacySyncKey
* @private
* @param {object} options - required set of options to derive the scoped key
* @param {string} options.inputKey - input key hex string that the scoped key is derived from
* @param {number} options.keyRotationTimestamp
* A 13-digit number, the timestamp in milliseconds at which this scoped key most recently changed
* @returns {Promise}
*/
_deriveLegacySyncKey(options) {
return new Promise((resolve) => {
const context = 'identity.mozilla.com/picl/v1/oldsync';
const contextBuf = Buffer.from(context);
const inputKeyBuf = Buffer.from(options.inputKey, 'hex');
const scopedKey = {
kty: 'oct',
scope: LEGACY_SYNC_SCOPE
};
return resolve(this._deriveHKDF(null, inputKeyBuf, contextBuf, 64)
.then((key) => {
scopedKey.k = base64url(key);
return jose.JWA.digest('SHA-256', inputKeyBuf)
.then((kHash) => {
const keyTimestamp = Math.round(options.keyRotationTimestamp / 1000);
scopedKey.kid = keyTimestamp + '-' + base64url(kHash.slice(0, 16));
return scopedKey;
});
})
);
});
}
/**
* Derive a key using HKDF.
* Ref: https://tools.ietf.org/html/rfc5869

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

@ -55,6 +55,21 @@ describe('ScopedKeys', function () {
});
});
it('should correctly derive legacy sync key to known test vectors', () => {
return scopedKeys.deriveScopedKey({
inputKey: 'eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9',
keyRotationSecret: '0000000000000000000000000000000000000000000000000000000000000000',
keyRotationTimestamp: 1510726317000,
identifier: 'https://identity.mozilla.com/apps/oldsync',
uid: uid
})
.then((key) => {
assert.equal(key.kty, 'oct');
assert.equal(key.k, 'DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang');
assert.equal(key.kid, '1510726317-IqQv4onc7VcVE1kTQkyyOw');
});
});
it('validates keyRotationTimestamp', () => {
return scopedKeys.deriveScopedKey({
inputKey: sampleKb,