Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Updating methods to use buffers, adding tests, addressing feedback #164

Merged
merged 1 commit into from Sep 3, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 16 additions & 16 deletions client/index.js
Expand Up @@ -83,21 +83,24 @@ function verifier(salt, email, password, algorithm) {
}

Client.prototype.setupCredentials = function (email, password, customSalt) {
var salt = customSalt ? customSalt : crypto.randomBytes(32).toString('hex')
if (!this.email) {
this.email = Buffer(email).toString('hex')
}

var saltHex = customSalt ? customSalt : crypto.randomBytes(32).toString('hex')

return keyStretch.derive(email, password, salt)
return keyStretch.derive(Buffer(this.email), Buffer(password), saltHex)
.then(
function (result) {
this.email = Buffer(email).toString('hex')
this.srpPw = result.srpPw
this.unwrapBKey = result.unwrapBKey
this.srpPw = result.srpPw.toString('hex')
this.unwrapBKey = result.unwrapBKey.toString('hex')
this.srp = {}
this.srp.type = 'SRP-6a/SHA256/2048/v1'
this.srp.salt = crypto.randomBytes(32).toString('hex')
this.srp.algorithm = 'sha256'
this.srp.verifier = verifier(this.srp.salt, this.email, this.srpPw,
this.srp.algorithm)
this.passwordSalt = salt
this.passwordSalt = saltHex
}.bind(this)
)
}
Expand Down Expand Up @@ -212,13 +215,12 @@ Client.prototype.auth = function (callback) {

if (!this.srpPw) {
session = srpSession
var emailStr = Buffer(this.email, 'hex').toString()

keyStretch.derive(emailStr, this.password, session.passwordStretching.salt)
keyStretch.derive(Buffer(this.email), Buffer(this.password), session.passwordStretching.salt)
.then(
function (result) {
this.srpPw = result.srpPw
this.unwrapBKey = result.unwrapBKey
this.srpPw = result.srpPw.toString('hex')
this.unwrapBKey = result.unwrapBKey.toString('hex')
this.passwordSalt = session.passwordStretching.salt

k.resolve(srpSession)
Expand Down Expand Up @@ -417,13 +419,12 @@ Client.prototype.changePassword = function (newPassword, callback) {
.then(
function (token) {
var salt = crypto.randomBytes(32).toString('hex')
var emailStr = Buffer(this.email, 'hex').toString()

return keyStretch.derive(emailStr, newPassword, salt)
return keyStretch.derive(Buffer(this.email), Buffer(newPassword), salt)
.then(
function (result) {
this.srpPw = result.srpPw
this.unwrapBKey = result.unwrapBKey
this.srpPw = result.srpPw.toString('hex')
this.unwrapBKey = result.unwrapBKey.toString('hex')
this.passwordSalt = salt

return token
Expand Down Expand Up @@ -570,8 +571,7 @@ Client.prototype.reforgotPassword = function (callback) {
Client.prototype.resetPassword = function (code, password, callback) {
// this will generate a new wrapKb on the server
var wrapKb = '0000000000000000000000000000000000000000000000000000000000000000'
var emailStr = Buffer(this.email, 'hex').toString()
var p = this.setupCredentials(emailStr, password)
var p = this.setupCredentials(this.email, password)
.then(
function () {
return this.api.passwordForgotVerifyCode(this.forgotPasswordToken, code)
Expand Down
33 changes: 17 additions & 16 deletions client/keystretch.js
Expand Up @@ -11,35 +11,35 @@ var crypto = require('crypto')

// The namespace for the salt functions
const NAMESPACE = 'identity.mozilla.com/picl/v1/'
const SCRYPT_HELPER = 'http://scrypt.dev.lcip.org/'


/** Derive a key from an email and password pair
*
* @param {String} email The email of the user
* @param {String} password The password of the user
* @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
* {String} srpPw srp password
* {String} unwrapBKey unwrapBKey
* {String} salt salt used in this derivation
* {Buffer} srpPw srp password
* {Buffer} unwrapBKey unwrapBKey
* or fail with {object} err
*/
function derive(email, password, saltHex) {
var p = P.defer()
var salt = Buffer(saltHex, 'hex')

if (password == 'undefined' || email == 'undefined' || password.length === 0 || email.length === 0) {
p.reject('Bad password or email input')
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))
.then(
function(K1) {
// request a hash from scrypt based on the first key
return scrypt.hash(K1, KW("scrypt"), 'http://scrypt.dev.lcip.org/')
return scrypt.hash(K1, KW("scrypt"), SCRYPT_HELPER)
}
)
.then(
Expand All @@ -64,8 +64,9 @@ function derive(email, password, saltHex) {
.done(
function (hkdfResult) {
var hkdfResultHex = hkdfResult.toString('hex')
var srpPw = hkdfResultHex.substring(0,64)
var unwrapBKey = hkdfResultHex.substring(64,128)
var srpPw = Buffer(hkdfResultHex.substring(0,64), 'hex')
var unwrapBKey = Buffer(hkdfResultHex.substring(64,128), 'hex')

p.resolve({ srpPw: srpPw, unwrapBKey: unwrapBKey })
},
function (err) {
Expand Down Expand Up @@ -105,20 +106,20 @@ function xor(input1, input2) {
/** KWE
*
* @param {String} name The name of the salt
* @param {String} email The email of the user.
* @return {string} the salt combination with the namespace
* @param {Buffer} email The email of the user.
* @return {Buffer} the salt combination with the namespace
*/
function KWE(name, email) {
return NAMESPACE + name + ':' + email
return Buffer(NAMESPACE + name + ':' + email)
}

/** KW
*
* @param {String} name The name of the salt
* @return {string} the salt combination with the namespace
* @return {Buffer} the salt combination with the namespace
*/
function KW(name) {
return NAMESPACE + name
return Buffer(NAMESPACE + name)
}

module.exports.derive = derive
Expand Down
13 changes: 7 additions & 6 deletions client/pbkdf2.js
Expand Up @@ -10,15 +10,16 @@ const LENGTH = 32 * 8

/** pbkdf2 string creator
*
* @param {bitArray|String} password The password.
* @param {String} salt The salt.
* @return {String} the derived key.
* @param {Buffer} input The password hex buffer.
* @param {Buffer} salt The salt string buffer.
* @return {Buffer} the derived key hex buffer.
*/
function derive(password, salt) {
var saltBits = sjcl.codec.utf8String.toBits(salt)
function derive(input, salt) {
var password = Buffer.isBuffer(input) ? input.toString() : input
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it convenient to make this always take a Buffer? Variable-type arguments look like bug-magnets to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we convert this from an sjcl byteArray back into a Buffer? @zaach do we need to add the sjcl byte codec?

var saltBits = sjcl.codec.utf8String.toBits(salt.toString())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a safer way to get from Buffers to bitArrays. Passing through UTF-8 seems perilous. Maybe pass through hex instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked to @zaach he will updated it, we need to fix the sjcl bundle.

var result = sjcl.misc.pbkdf2(password, saltBits, ITERATIONS, LENGTH, sjcl.misc.hmac)

return P(sjcl.codec.hex.fromBits(result))
return P(Buffer(sjcl.codec.hex.fromBits(result), 'hex'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that feels safer

}

module.exports.derive = derive
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not part of your patch, I know, but isn't exports.derive= more common than module.exports.derive= ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked to @zaach about this, we'll check that, but module.exports should be okay for now.

8 changes: 4 additions & 4 deletions client/scrypt.js
Expand Up @@ -9,20 +9,20 @@ var scrypt = require('node-scrypt-js')

/** hash Creates an scrypt hash
*
* @param {String} input The input for scrypt
* @param {String} salt The salt for the hash
* @param {Buffer} input The input for scrypt
* @param {Buffer} salt The salt for the hash
* @param {String} url scrypt helper server url
* @returns {Object} d.promise Deferred promise
*/
function hash(input, salt, url) {
var p
var payload = {
salt: salt,
salt: salt.toString(),
N: 64 * 1024,
r: 8,
p: 1,
buflen: 32,
input: input
input: input.toString('hex')
}

if (url) {
Expand Down
114 changes: 96 additions & 18 deletions test/run/key_stretch_tests.js
@@ -1,20 +1,24 @@
var test = require('tap').test
var P = require('p-promise')
var sjcl = require('sjcl')
var keyStretch = require('../../client/keystretch')

test(
'basic key stretching, test vectors',
function (t) {
var email = 'andré@example.org'
var emailBuf = Buffer('andré@example.org')
var password = 'pässwörd'
var salt = '00f000000000000000000000000000000000000000000000000000000000034d'
function end() { t.end() }

keyStretch.derive(email, password, salt)
keyStretch.derive(emailBuf, password, salt)
.then(
function (result) {
t.equal(result.srpPw, '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d')
t.equal(result.unwrapBKey, '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a')
t.equal(result.srpPw.toString('hex'), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d')
t.equal(result.unwrapBKey.toString('hex'), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a')
},
function (err) {
t.fail(err)
}
)
.done(end, end)
Expand All @@ -26,14 +30,18 @@ test(
function (t) {
var salt = '00f000000000000000000000000000000000000000000000000000000000034d'
var email = 'ijqmkkafer3xsj5rzoq+msnxsacvkmqxabtsvxvj@some-test-domain-with-a-long-name-example.org'
var emailBuf = Buffer(email)
var password = 'mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ'
function end() { t.end() }

keyStretch.derive(email, password, salt)
keyStretch.derive(emailBuf, password, salt)
.then(
function (result) {
t.equal(result.srpPw, '261559a74f7b7199fef846c8138db08333bbcc7f5177194da5c965ba953a346b')
t.equal(result.unwrapBKey, 'cf48fbc1613a46c794d37c2fe5423c7813b70e5b6c525d5c4463056f267959ff')
t.equal(result.srpPw.toString('hex'), '261559a74f7b7199fef846c8138db08333bbcc7f5177194da5c965ba953a346b')
t.equal(result.unwrapBKey.toString('hex'), 'cf48fbc1613a46c794d37c2fe5423c7813b70e5b6c525d5c4463056f267959ff')
},
function (err) {
t.fail(err)
}
)
.done(end, end)
Expand All @@ -43,18 +51,15 @@ test(
test(
'false input both',
function (t) {
var email = ''
var password = ''
var salt = ''
function end() { t.end() }

keyStretch.derive(email, password, salt)
keyStretch.derive('', '', '')
.then(
function (stretchedPassword) {
t.fail('Got a stretchedPassword from false input')
},
function (err) {
t.equal(err, 'Bad password or email input')
t.equal(err, 'Bad password, salt or email input')
}
)
.done(end, end)
Expand All @@ -65,24 +70,22 @@ test(
'false input email',
function (t) {
var email = 'me@example.org'
var password = ''
var salt = ''

function end() { t.end() }

keyStretch.derive(email, password, salt)
keyStretch.derive(email, '', '')
.then(
function (stretchedPassword) {
t.fail('Got a stretchedPassword from false input')
},
function (err) {
t.equal(err, 'Bad password or email input')
t.equal(err, 'Bad password, salt or email input')
}
)
.done(end, end)
}
)


test(
'false input password',
function (t) {
Expand All @@ -97,7 +100,82 @@ test(
t.fail('Got a stretchedPassword from false input')
},
function (err) {
t.equal(err, 'Bad password or email input')
t.equal(err, 'Bad password, salt or email input')
}
)
.done(end, end)
}
)

test(
'undefined input',
function (t) {
var email
var password
var salt
function end() { t.end() }

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')
}
)
.done(end, end)
}
)

test(
'not enough arguments',
function (t) {
function end() { t.end() }

keyStretch.derive()
.then(
function (stretchedPassword) {
t.fail('Got a stretchedPassword from false input')
},
function (err) {
t.equal(err, 'Bad password, salt or email input')
}
)
.done(end, end)
}
)

test(
'one argument',
function (t) {
function end() { t.end() }

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')
}
)
.done(end, end)
}
)

test(
'null input',
function (t) {
function end() { t.end() }

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')
}
)
.done(end, end)
Expand Down