diff --git a/client/index.js b/client/index.js index 6adbd603..b08b89a2 100644 --- a/client/index.js +++ b/client/index.js @@ -5,7 +5,7 @@ var crypto = require('crypto') var P = require('p-promise') var ClientApi = require('./api') -var keyStretch = require('../crypto/keystretch') +var butil = require('../crypto/butil') var pbkdf2 = require('../crypto/pbkdf2') var hkdf = require('../crypto/hkdf') var tokens = require('../tokens')({ trace: function () {}}) @@ -34,7 +34,7 @@ Client.Api = ClientApi Client.prototype.setupCredentials = function (email, password) { this.email = email - return pbkdf2.derive(Buffer(password), keyStretch.KWE('quickStretch', email), 1000, 32) + return pbkdf2.derive(Buffer(password), hkdf.KWE('quickStretch', email), 1000, 32) .then( function (stretch) { return hkdf(stretch, 'authPW', null, 32) @@ -218,7 +218,7 @@ Client.prototype.changePassword = function (newPassword) { ) .then( function () { - this.wrapKb = keyStretch.xor(this.kB, this.unwrapBKey) + this.wrapKb = butil.xorBuffers(this.kB, this.unwrapBKey) return this.api.passwordChangeFinish(this.passwordChangeToken, this.authPW, this.wrapKb) }.bind(this) ) @@ -251,7 +251,7 @@ Client.prototype.keys = function () { this.keyFetchToken = null this.kA = keys.kA this.wrapKb = keys.wrapKb - this.kB = keys.kB = keyStretch.xor(this.wrapKb, this.unwrapBKey) + this.kB = keys.kB = butil.xorBuffers(this.wrapKb, this.unwrapBKey) return keys }.bind(this), function (err) { diff --git a/crypto/hkdf.js b/crypto/hkdf.js index 87c7efe4..d9c8eebb 100644 --- a/crypto/hkdf.js +++ b/crypto/hkdf.js @@ -5,15 +5,21 @@ var HKDF = require('hkdf') var P = require('p-promise') -function kw(name) { - return 'identity.mozilla.com/picl/v1/' + name +const NAMESPACE = 'identity.mozilla.com/picl/v1/' + +function KWE(name, email) { + return Buffer(NAMESPACE + name + ':' + email) +} + +function KW(name) { + return Buffer(NAMESPACE + name) } function hkdf(km, info, salt, len) { var d = P.defer() var df = new HKDF('sha256', salt, km) df.derive( - kw(info), + KW(info), len, function(key) { d.resolve(key) @@ -22,4 +28,7 @@ function hkdf(km, info, salt, len) { return d.promise } +hkdf.KW = KW +hkdf.KWE = KWE + module.exports = hkdf diff --git a/crypto/keystretch.js b/crypto/keystretch.js deleted file mode 100644 index a017f9dc..00000000 --- a/crypto/keystretch.js +++ /dev/null @@ -1,128 +0,0 @@ -/* 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/. */ - -var P = require('p-promise') -var pbkdf2 = require('./pbkdf2') -var scrypt = require('./scrypt') -var hkdf = require('./hkdf') - -// The namespace for the salt functions -const NAMESPACE = 'identity.mozilla.com/picl/v1/' -const SCRYPT_HELPER = 'https://scrypt-accounts.dev.lcip.org/' -const ITERATIONS = 20000 -const LENGTH = 8 * 32 - -/** Derive a key from an email and password pair - * - * @param {Buffer} email The email hex buffer of the user - * @param {Buffer} password The password of the user - * @param {String} saltHex The salt to derive hkdf as a hex string - * @return p.promise object - It will resolve with - * {Buffer} srpPw srp password - * {Buffer} unwrapBKey unwrapBKey - * or fail with {object} err - */ -function derive(email, password, saltHex) { - var p = P.defer() - - if (!password || !email || !saltHex) { - p.reject('Bad password, salt or email input') - return p.promise - } - - var salt = Buffer(saltHex, 'hex') - // derive the first key from pbkdf2 - pbkdf2 - .derive(password, KWE('first-PBKDF', email), ITERATIONS, LENGTH) - .then( - function(K1) { - // request a hash from scrypt based on the first key - return scrypt.hash(K1, KW("scrypt"), module.exports.SCRYPT_HELPER) - } - ) - .then( - function (K2) { - // combine the K2 hex string and a password UTF8 into a bit array - var scryptPassword = Buffer.concat([ - Buffer(K2, 'hex'), - password - ]) - // derive the second key from pbkdf2 - return pbkdf2.derive(scryptPassword, KWE('second-PBKDF', email), ITERATIONS, LENGTH) - } - ) - .then( - function (stretchedPw) { - var input = new Buffer (stretchedPw, 'hex') - var lengthHkdf = 2 * 32 - - return hkdf(input, 'mainKDF', salt, lengthHkdf) - } - ) - .done( - function (hkdfResult) { - var hkdfResultHex = hkdfResult.toString('hex') - var srpPw = Buffer(hkdfResultHex.substring(0,64), 'hex') - var unwrapBKey = Buffer(hkdfResultHex.substring(64,128), 'hex') - - p.resolve({ srpPw: srpPw, unwrapBKey: unwrapBKey }) - }, - function (err) { - p.reject(err) - } - ) - - return p.promise -} - -/** XOR - * - * @param {Buffer|String} input1 first value of the buffer as a hex string or a buffer - * @param {Buffer|String} input2 second value of the buffer as hex string or a buffer - * @return {Buffer} xorResult Result XOR buffer - */ -function xor(input1, input2) { - var buf1 = Buffer.isBuffer(input1) ? input1 : Buffer(input1, 'hex') - var buf2 = Buffer.isBuffer(input2) ? input2 : Buffer(input2, 'hex') - var xorResult = Buffer(buf1.length) - - if (buf1.length !== buf2.length) { - throw new Error( - 'XOR buffers must be same length %d != %d', - buf1.length, - buf2.length - ) - } - for (var i = 0; i < xorResult.length; i++) { - xorResult[i] = buf2[i] ^ buf1[i] - } - - return xorResult -} - - -/** KWE - * - * @param {String} name The name of the salt - * @param {Buffer} email The email of the user. - * @return {Buffer} the salt combination with the namespace - */ -function KWE(name, email) { - return Buffer(NAMESPACE + name + ':' + email) -} - -/** KW - * - * @param {String} name The name of the salt - * @return {Buffer} the salt combination with the namespace - */ -function KW(name) { - return Buffer(NAMESPACE + name) -} - -module.exports.SCRYPT_HELPER = SCRYPT_HELPER -module.exports.derive = derive -module.exports.xor = xor -module.exports.KWE = KWE -module.exports.KW = KW diff --git a/test/run/key_stretch_tests.js b/test/run/key_stretch_tests.js deleted file mode 100644 index 8cccd5fd..00000000 --- a/test/run/key_stretch_tests.js +++ /dev/null @@ -1,166 +0,0 @@ -/* 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/. */ - -var test = require('../ptaptest') -var keyStretch = require('../../crypto/keystretch') - -test( - 'basic key stretching, test vectors', - function (t) { - var emailBuf = Buffer('andré@example.org') - var password = Buffer('pässwörd') - var salt = '00f000000000000000000000000000000000000000000000000000000000034d' - return keyStretch.derive(emailBuf, password, salt) - .then( - function (result) { - t.equal(result.srpPw.toString('hex'), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d') - t.equal(result.unwrapBKey.toString('hex'), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a') - } - ) - } -) - -test( - 'basic key stretching, longer credentials', - function (t) { - var salt = '00f000000000000000000000000000000000000000000000000000000000034d' - var email = 'ijqmkkafer3xsj5rzoq+msnxsacvkmqxabtsvxvj@some-test-domain-with-a-long-name-example.org' - var emailBuf = Buffer(email) - var password = Buffer('mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ') - return keyStretch.derive(emailBuf, password, salt) - .then( - function (result) { - t.equal(result.srpPw.toString('hex'), '261559a74f7b7199fef846c8138db08333bbcc7f5177194da5c965ba953a346b') - t.equal(result.unwrapBKey.toString('hex'), 'cf48fbc1613a46c794d37c2fe5423c7813b70e5b6c525d5c4463056f267959ff') - } - ) - } -) - -test( - 'false input both', - function (t) { - return keyStretch.derive('', '', '') - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'false input email', - function (t) { - var email = 'me@example.org' - return keyStretch.derive(email, '', '') - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'false input password', - function (t) { - var email = '' - var password = 'password' - var salt = '' - return keyStretch.derive(email, password, salt) - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'undefined input', - function (t) { - var email - var password - var salt - return keyStretch.derive(email, password, salt) - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'not enough arguments', - function (t) { - return keyStretch.derive() - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'one argument', - function (t) { - return keyStretch.derive(Buffer('andré@example.org')) - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'null input', - function (t) { - return keyStretch.derive(null, null, null) - .then( - function (stretchedPassword) { - t.fail('Got a stretchedPassword from false input') - }, - function (err) { - t.equal(err, 'Bad password, salt or email input') - } - ) - } -) - -test( - 'wrapkB xor string and buffer test', - function (t) { - var wrapkB = '404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f' - var unwrapBKey = '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a' - var kBgood = '2ee722fdd8ccaa721bdeb2d1b76560efef705b04349d9357c3e592cf4906e075' - var kBResult = keyStretch.xor(wrapkB, unwrapBKey) - var kBResultBuffer = keyStretch.xor(Buffer(wrapkB, 'hex'), Buffer(unwrapBKey, 'hex')) - - t.equal(kBResult.toString('hex'), kBgood) - t.equal(kBResultBuffer.toString('hex'), kBgood) - t.end() - } -)