- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- define(['./request', 'sjcl', './hkdf', './pbkdf2'], function (Request, sjcl, hkdf, pbkdf2) {
- 'use strict';
-
- // Key wrapping and stretching configuration.
- var NAMESPACE = 'identity.mozilla.com/picl/v1/';
- var PBKDF2_ROUNDS = 1000;
- var STRETCHED_PASS_LENGTH_BYTES = 32 * 8;
-
- var HKDF_SALT = sjcl.codec.hex.toBits('00');
- var HKDF_LENGTH = 32;
-
- /**
- * Key Wrapping with a name
- *
- * @method kw
- * @static
- * @param {String} name The name of the salt
- * @return {bitArray} the salt combination with the namespace
- */
- function kw(name) {
- return sjcl.codec.utf8String.toBits(NAMESPACE + name);
- }
-
- /**
- * Key Wrapping with a name and an email
- *
- * @method kwe
- * @static
- * @param {String} name The name of the salt
- * @param {String} email The email of the user.
- * @return {bitArray} the salt combination with the namespace
- */
- function kwe(name, email) {
- return sjcl.codec.utf8String.toBits(NAMESPACE + name + ':' + email);
- }
-
- /**
- * @class credentials
- * @constructor
- */
- return {
- /**
- * Setup credentials
- *
- * @method setup
- * @param {String} emailInput
- * @param {String} passwordInput
- * @return {Promise} A promise that will be fulfilled with `result` of generated credentials
- */
- setup: function (emailInput, passwordInput) {
- var result = {};
- var email = kwe('quickStretch', emailInput);
- var password = sjcl.codec.utf8String.toBits(passwordInput);
-
- result.emailUTF8 = emailInput;
- result.passwordUTF8 = passwordInput;
-
- return pbkdf2.derive(password, email, PBKDF2_ROUNDS, STRETCHED_PASS_LENGTH_BYTES)
- .then(
- function (quickStretchedPW) {
- result.quickStretchedPW = quickStretchedPW;
-
- return hkdf(quickStretchedPW, kw('authPW'), HKDF_SALT, HKDF_LENGTH)
- .then(
- function (authPW) {
- result.authPW = authPW;
-
- return hkdf(quickStretchedPW, kw('unwrapBkey'), HKDF_SALT, HKDF_LENGTH);
- }
- );
- }
- )
- .then(
- function (unwrapBKey) {
- result.unwrapBKey = unwrapBKey;
- return result;
- }
- );
- },
- /**
- * Wrap
- *
- * @method wrap
- * @param {bitArray} bitArray1
- * @param {bitArray} bitArray2
- * @return {bitArray} wrap result of the two bitArrays
- */
- xor: function (bitArray1, bitArray2) {
- var result = [];
-
- for (var i = 0; i < bitArray1.length; i++) {
- result[i] = bitArray1[i] ^ bitArray2[i];
- }
-
- return result;
- },
- /**
- * Unbundle the WrapKB
- * @param {String} key Bundle Key in hex
- * @param {String} bundle Key bundle in hex
- * @returns {*}
- */
- unbundleKeyFetchResponse: function (key, bundle) {
- var self = this;
- var bitBundle = sjcl.codec.hex.toBits(bundle);
-
- return this.deriveBundleKeys(key, 'account/keys')
- .then(
- function (keys) {
- var ciphertext = sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64);
- var expectedHmac = sjcl.bitArray.bitSlice(bitBundle, 8 * -32);
- var hmac = new sjcl.misc.hmac(keys.hmacKey, sjcl.hash.sha256);
- hmac.update(ciphertext);
-
- if (!sjcl.bitArray.equal(hmac.digest(), expectedHmac)) {
- throw new Error('Bad HMac');
- }
-
- var keyAWrapB = self.xor(sjcl.bitArray.bitSlice(bitBundle, 0, 8 * 64), keys.xorKey);
-
- return {
- kA: sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(keyAWrapB, 0, 8 * 32)),
- wrapKB: sjcl.codec.hex.fromBits(sjcl.bitArray.bitSlice(keyAWrapB, 8 * 32))
- };
- }
- );
- },
- /**
- * Derive the HMAC and XOR keys required to encrypt a given size of payload.
- * @param {String} key Hex Bundle Key
- * @param {String} keyInfo Bundle Key Info
- * @returns {Object} hmacKey, xorKey
- */
- deriveBundleKeys: function(key, keyInfo) {
- var bitKeyInfo = kw(keyInfo);
- var salt = sjcl.codec.hex.toBits('');
- key = sjcl.codec.hex.toBits(key);
-
- return hkdf(key, bitKeyInfo, salt, 3 * 32)
- .then(
- function (keyMaterial) {
-
- return {
- hmacKey: sjcl.bitArray.bitSlice(keyMaterial, 0, 8 * 32),
- xorKey: sjcl.bitArray.bitSlice(keyMaterial, 8 * 32)
- };
- }
- );
- }
- };
-
- });
-
-