This commit is contained in:
vladikoff 2013-08-14 19:40:16 -07:00
Родитель cbd62774ac
Коммит 4ea38ab744
11 изменённых файлов: 683 добавлений и 89 удалений

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

@ -8,6 +8,7 @@ var srp = require('srp')
var ClientApi = require('./api')
var models = require('../models')({},{},{})
var keyStretch = require('./keystretch')
var tokens = models.tokens
var AuthBundle = models.AuthBundle
@ -81,23 +82,35 @@ function verifier(salt, email, password, algorithm) {
).toString('hex')
}
Client.prototype.setupCredentials = function (email, password) {
// TODO: password stretching
this.email = Buffer(email).toString('hex')
this.password = password
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.password,
this.srp.algorithm);
this.passwordSalt = crypto.randomBytes(32).toString('hex')
Client.prototype.setupCredentials = function (email, password, customSalt) {
var salt = customSalt ? customSalt : crypto.randomBytes(32).toString('hex')
return keyStretch.derive(email, password, salt)
.then(
function (result) {
this.email = Buffer(email).toString('hex')
this.srpPw = result.srpPw
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
}.bind(this)
)
}
Client.create = function (origin, email, password, callback) {
var c = new Client(origin)
c.setupCredentials(email, password)
var p = c.create()
var p = c.setupCredentials(email, password)
.then(
function() {
return c.create()
}
)
if (callback) {
p.done(callback.bind(null, null), callback)
}
@ -108,13 +121,15 @@ Client.create = function (origin, email, password, callback) {
Client.login = function (origin, email, password, callback) {
var c = new Client(origin)
c.setupCredentials(email, password)
c.email = Buffer(email).toString('hex')
c.password = password
var p = c.login()
.then(
function () {
return c
}
)
function () {
return c
}
)
if (callback) {
p.done(callback.bind(null, null), callback)
}
@ -157,12 +172,12 @@ Client.prototype.create = function (callback) {
salt: this.passwordSalt
}
)
.then(
function (a) {
this.uid = a.uid
return this
}.bind(this)
)
.then(
function (a) {
this.uid = a.uid
return this
}.bind(this)
)
if (callback) {
p.done(callback.bind(null, null), callback)
}
@ -183,27 +198,56 @@ Client.prototype._clear = function () {
}
Client.prototype.stringify = function () {
return JSON.stringify(this)
return JSON.stringify(this)
}
Client.prototype.auth = function (callback) {
var K = null
var session
var p = this.api.authStart(this.email)
.then(
function (srpSession) {
var x = getAMK(srpSession, this.email, this.password)
var k = P.defer()
if (!this.srpPw) {
session = srpSession
var emailStr = Buffer(this.email, 'hex').toString()
keyStretch.derive(emailStr, this.password, session.passwordStretching.salt)
.then(
function (result) {
this.srpPw = result.srpPw
this.passwordSalt = session.passwordStretching.salt
k.resolve(srpSession)
}.bind(this),
function (err) {
k.reject(err)
}
)
} else {
k.resolve(srpSession)
}
return k.promise
}.bind(this)
)
.then(
function (srpSession) {
var x = getAMK(srpSession, this.email, this.srpPw)
K = x.K
return this.api.authFinish(x.srpToken, x.A, x.M)
}.bind(this)
)
.then(
function (json) {
return AuthBundle.create(K, 'auth/finish')
.then(
function (b) {
return b.unbundle(json.bundle)
}
)
function (b) {
return b.unbundle(json.bundle)
}
)
}.bind(this)
)
.then(
@ -231,10 +275,10 @@ Client.prototype.login = function (callback) {
function (json) {
return tokens.AuthToken.fromHex(this.authToken)
.then(
function (t) {
return t.unbundleSession(json.bundle)
}
)
function (t) {
return t.unbundleSession(json.bundle)
}
)
}.bind(this)
)
.then(
@ -285,11 +329,11 @@ Client.prototype.verifyEmail = function (code, callback) {
Client.prototype.emailStatus = function (callback) {
var o = this.sessionToken ? P(null) : this.login()
var p = o.then(
function () {
return this.api.recoveryEmailStatus(this.sessionToken)
}.bind(this)
)
.then(
function () {
return this.api.recoveryEmailStatus(this.sessionToken)
}.bind(this)
)
.then(
function (status) {
// decode email
status.email = Buffer(status.email, 'hex').toString()
@ -322,11 +366,11 @@ Client.prototype.requestVerifyEmail = function (callback) {
Client.prototype.sign = function (publicKey, duration, callback) {
var o = this.sessionToken ? P(null) : this.login()
var p = o.then(
function () {
return this.api.certificateSign(this.sessionToken, publicKey, duration)
}.bind(this)
)
.then(
function () {
return this.api.certificateSign(this.sessionToken, publicKey, duration)
}.bind(this)
)
.then(
function (x) {
return x.cert
}
@ -350,10 +394,10 @@ Client.prototype.changePassword = function (newPassword, callback) {
function (json) {
return tokens.AuthToken.fromHex(this.authToken)
.then(
function (t) {
return t.unbundleAccountReset(json.bundle)
}
)
function (t) {
return t.unbundleAccountReset(json.bundle)
}
)
}.bind(this)
)
.then(
@ -368,11 +412,26 @@ Client.prototype.changePassword = function (newPassword, callback) {
return tokens.AccountResetToken.fromHex(this.accountResetToken)
}.bind(this)
)
.then(
function (token) {
var salt = crypto.randomBytes(32).toString('hex')
var emailStr = Buffer(this.email, 'hex').toString()
return keyStretch.derive(emailStr, newPassword, salt)
.then(
function (result) {
this.srpPw = result.srpPw
this.passwordSalt = salt
return token
}.bind(this)
)
}.bind(this)
)
.then(
function (token) {
this.srp.salt = crypto.randomBytes(32).toString('hex')
this.password = newPassword
this.srp.verifier = verifier(this.srp.salt, this.email, newPassword, this.srp.algorithm)
this.srp.verifier = verifier(this.srp.salt, this.email, this.srpPw, this.srp.algorithm)
var bundle = token.bundle(this.wrapKb, this.srp.verifier)
return this.api.accountReset(
this.accountResetToken,
@ -381,7 +440,15 @@ Client.prototype.changePassword = function (newPassword, callback) {
type: this.srp.type,
salt: this.srp.salt
},
this.passwordStretching
{
type: 'PBKDF2/scrypt/PBKDF2/v1',
PBKDF2_rounds_1: 20000,
scrypt_N: 65536,
scrypt_r: 8,
scrypt_p: 1,
PBKDF2_rounds_2: 20000,
salt: this.passwordSalt
}
)
}.bind(this)
)
@ -397,32 +464,32 @@ Client.prototype.changePassword = function (newPassword, callback) {
Client.prototype.keys = function (callback) {
var o = this.keyFetchToken ? P(null) : this.login()
var p = o.then(
function () {
return this.api.accountKeys(this.keyFetchToken)
}.bind(this)
)
.then(
function (data) {
return tokens.KeyFetchToken.fromHex(this.keyFetchToken)
.then(
function () {
return this.api.accountKeys(this.keyFetchToken)
}.bind(this)
)
.then(
function (data) {
return tokens.KeyFetchToken.fromHex(this.keyFetchToken)
.then(
function (token) {
return token.unbundle(data.bundle)
}
)
}.bind(this)
)
.then(
function (keys) {
this.keyFetchToken = null
this.kA = keys.kA
this.wrapKb = keys.wrapKb
return keys
}.bind(this),
function (err) {
this.keyFetchToken = null
throw err
}.bind(this)
)
}.bind(this)
)
.then(
function (keys) {
this.keyFetchToken = null
this.kA = keys.kA
this.wrapKb = keys.wrapKb
return keys
}.bind(this),
function (err) {
this.keyFetchToken = null
throw err
}.bind(this)
)
if (callback) {
p.done(callback.bind(null, null), callback)
@ -435,16 +502,16 @@ Client.prototype.keys = function (callback) {
Client.prototype.devices = function (callback) {
var o = this.sessionToken ? P(null) : this.login()
var p = o.then(
function () {
return this.api.accountDevices(this.sessionToken)
}.bind(this)
)
.then(
function (json) {
this._devices = json.devices
return this._devices
}.bind(this)
)
function () {
return this.api.accountDevices(this.sessionToken)
}.bind(this)
)
.then(
function (json) {
this._devices = json.devices
return this._devices
}.bind(this)
)
if (callback) {
p.done(callback.bind(null, null), callback)
}
@ -498,8 +565,13 @@ Client.prototype.reforgotPassword = function (callback) {
Client.prototype.resetPassword = function (code, password, callback) {
// this will generate a new wrapKb on the server
var wrapKb = '0000000000000000000000000000000000000000000000000000000000000000'
this.setupCredentials(Buffer(this.email, 'hex').toString(), password)
var p = this.api.passwordForgotVerifyCode(this.forgotPasswordToken, code)
var emailStr = Buffer(this.email, 'hex').toString()
var p = this.setupCredentials(emailStr, password)
.then(
function () {
return this.api.passwordForgotVerifyCode(this.forgotPasswordToken, code)
}.bind(this)
)
.then(
function (json) {
return tokens.AccountResetToken.fromHex(json.accountResetToken)
@ -515,7 +587,15 @@ Client.prototype.resetPassword = function (code, password, callback) {
type: this.srp.type,
salt: this.srp.salt
},
this.passwordStretching
{
type: 'PBKDF2/scrypt/PBKDF2/v1',
PBKDF2_rounds_1: 20000,
scrypt_N: 65536,
scrypt_r: 8,
scrypt_p: 1,
PBKDF2_rounds_2: 20000,
salt: this.passwordSalt
}
)
}.bind(this)
)

99
client/keystretch.js Normal file
Просмотреть файл

@ -0,0 +1,99 @@
/* 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 sjcl = require('sjcl')
var pbkdf2 = require('./pbkdf2')
var scrypt = require('./scrypt')
var hkdf = require('../hkdf')
var crypto = require('crypto')
// The namespace for the salt functions
const NAMESPACE = 'identity.mozilla.com/picl/v1/'
/** 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 {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
* 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')
return p.promise
}
// 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/')
}
)
.then(
function (K2) {
// combine the K2 hex string and a password UTF8 into a bit array
var scryptPassword = sjcl.bitArray.concat(
sjcl.codec.hex.toBits(K2),
sjcl.codec.utf8String.toBits(password)
)
// derive the second key from pbkdf2
return pbkdf2.derive(scryptPassword, KWE('second-PBKDF', email))
}
)
.then(
function (stretchedPw) {
var input = new Buffer (stretchedPw, 'hex')
// if there's existingSalt use it
var lengthHkdf = 2 * 32;
return hkdf(input, 'mainKDF', salt, lengthHkdf)
}
)
.done(
function (hkdfResult) {
var hkdfResultHex = hkdfResult.toString('hex')
var srpPw = hkdfResultHex.substring(0,64)
var unwrapBKey = hkdfResultHex.substring(64,128)
p.resolve({ srpPw: srpPw, unwrapBKey: unwrapBKey })
},
function (err) {
p.reject(err)
}
)
return p.promise
}
/** 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
*/
function KWE(name, email) {
return NAMESPACE + name + ':' + email
}
/** KW
*
* @param {String} name The name of the salt
* @return {string} the salt combination with the namespace
*/
function KW(name) {
return NAMESPACE + name
}
module.exports.derive = derive

24
client/pbkdf2.js Normal file
Просмотреть файл

@ -0,0 +1,24 @@
/* 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 sjcl = require('sjcl')
var P = require('p-promise')
const ITERATIONS = 20 * 1000
const LENGTH = 32 * 8
/** pbkdf2 string creator
*
* @param {bitArray|String} password The password.
* @param {String} salt The salt.
* @return {String} the derived key.
*/
function derive(password, salt) {
var saltBits = sjcl.codec.utf8String.toBits(salt)
var result = sjcl.misc.pbkdf2(password, saltBits, ITERATIONS, LENGTH, sjcl.misc.hmac)
return P(sjcl.codec.hex.fromBits(result))
}
module.exports.derive = derive

79
client/scrypt.js Normal file
Просмотреть файл

@ -0,0 +1,79 @@
/* 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 request = require('request')
var P = require('p-promise')
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 {String} url scrypt helper server url
* @returns {Object} d.promise Deferred promise
*/
function hash(input, salt, url) {
var p;
var payload = {
salt: salt,
N: 64 * 1024,
r: 8,
p: 1,
buflen: 32,
input: input
}
if (url) {
p = remoteScryptHelper(payload, url)
} else {
p = localScrypt(payload)
}
return p
}
/** localScrypt generates the scrypt hash locally
*
* @param {Object} payload the payload required to generate the hash
*/
function localScrypt(payload) {
var inputBinary = new Buffer(payload.input, "hex").toString("binary")
var spass = scrypt.scrypt(inputBinary, payload.salt, payload.N, payload.r, payload.p, payload.buflen)
var result = new Buffer(spass, 'base64').toString('hex');
return P(result)
}
/** remoteScryptHelper generates the scrypt hash using a remote helper
*
* @param {Object} payload The payload required to generate the hash
* @param {String} url The url of the remote helper
*/
function remoteScryptHelper(payload, url) {
var d = P.defer()
var method = 'POST'
var headers = {}
request(
{
url: url,
method: method,
headers: headers,
json: payload
},
function (err, res, body) {
if ((err || body.error)) {
d.reject(err || body)
}
else {
d.resolve(body.output)
}
}
)
return d.promise
}
module.exports.hash = hash

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

@ -22,6 +22,7 @@
"hapi": "1.9.0",
"hawk": "1.0.0",
"hkdf": "0.0.1",
"sjcl": "git://github.com/bitwiseshiftleft/sjcl.git#961316282a",
"compute-cluster": "0.0.7",
"jwcrypto": "0.4.3",
"handlebars": "1.0.10",
@ -33,7 +34,8 @@
"node-statsd": "0.0.7",
"toobusy": "0.2.3",
"nodemailer": "0.5.2",
"lout": "0.4.1"
"lout": "0.4.1",
"node-scrypt-js": "git://github.com/vladikoff/node-scrypt-js.git#512f37884ff9"
},
"devDependencies": {
"awsbox": "0.4.5",

62
test/run/hkdf_tests.js Normal file
Просмотреть файл

@ -0,0 +1,62 @@
var test = require('tap').test
var P = require('p-promise')
var hkdf = require('../../hkdf')
test(
'hkdf basic',
function (t) {
var stretchedPw = 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c'
stretchedPw = new Buffer (stretchedPw,'hex')
var info = 'mainKDF'
var salt = new Buffer ('00f000000000000000000000000000000000000000000000000000000000034d','hex')
var lengthHkdf = 2 * 32;
function end() { t.end() }
hkdf(stretchedPw, info, salt, lengthHkdf)
.then(
function (hkdfResult) {
var hkdfStr = hkdfResult.toString('hex')
t.equal(hkdfStr.substring(0,64), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d')
t.equal(hkdfStr.substring(64,128), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a')
return hkdfResult
},
function (err) {
t.fail(err, null)
}
)
.done(end, end)
}
)
test(
'hkdf basic with salt',
function (t) {
var stretchedPw = 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c'
stretchedPw = new Buffer (stretchedPw, 'hex')
var info = 'mainKDF'
var salt = new Buffer ('00f000000000000000000000000000000000000000000000000000000000034d', 'hex')
var lengthHkdf = 2 * 32;
function end() { t.end() }
hkdf(stretchedPw, info, salt, lengthHkdf)
.then(
function (hkdfResult, salt) {
var hkdfStr = hkdfResult.toString('hex')
t.equal(hkdfStr.substring(0,64), '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d')
t.equal(hkdfStr.substring(64,128), '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a')
t.equal(salt.toString('hex'), '00f000000000000000000000000000000000000000000000000000000000034d')
return hkdfResult
},
function (err) {
t.fail(err, null)
}
)
.done(end, end)
}
)

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

@ -63,10 +63,12 @@ function main() {
var newPassword = 'foobar'
var wrapKb = null
var client = null
var firstSrpPw
Client.create(config.public_url, email, password)
.then(
function (x) {
client = x
firstSrpPw = x.srpPw
return client.keys()
}
)
@ -82,7 +84,7 @@ function main() {
)
.then(
function () {
t.equal(client.password, newPassword, 'password has changed')
t.notEqual(client.srpPw, firstSrpPw, 'password has changed')
return client.keys()
}
)

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

@ -0,0 +1,105 @@
var test = require('tap').test
var P = require('p-promise')
var keystretch = require('../../client/keystretch')
test(
'basic key stretching, test vectors',
function (t) {
var email = 'andré@example.org'
var password = 'pässwörd'
var salt = '00f000000000000000000000000000000000000000000000000000000000034d'
function end() { t.end() }
keystretch.derive(email, password, salt)
.then(
function (result) {
t.equal(result.srpPw, '00f9b71800ab5337d51177d8fbc682a3653fa6dae5b87628eeec43a18af59a9d')
t.equal(result.unwrapBKey, '6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a')
}
)
.done(end, end)
}
)
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 password = 'mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ'
function end() { t.end() }
keystretch.derive(email, password, salt)
.then(
function (result) {
t.equal(result.srpPw, '261559a74f7b7199fef846c8138db08333bbcc7f5177194da5c965ba953a346b')
t.equal(result.unwrapBKey, 'cf48fbc1613a46c794d37c2fe5423c7813b70e5b6c525d5c4463056f267959ff')
}
)
.done(end, end)
}
)
test(
'false input both',
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 or email input')
}
)
.done(end, end)
}
)
test(
'false input email',
function (t) {
var email = 'me@example.org'
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 or email input')
}
)
.done(end, end)
}
)
test(
'false input password',
function (t) {
var email = ''
var password = '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 or email input')
}
)
.done(end, end)
}
)

66
test/run/pbkdf2_tests.js Normal file
Просмотреть файл

@ -0,0 +1,66 @@
/* 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 pbkdf2 = require('../../client/pbkdf2')
var P = require('p-promise')
var test = require('tap').test
var sjcl = require('sjcl')
test(
'pbkdf2 derive',
function (t) {
var salt = 'identity.mozilla.com/picl/v1/first-PBKDF:andré@example.org'
var password = 'pässwörd'
function end() { t.end() }
pbkdf2.derive(password, salt)
.then(
function (K1) {
t.equal(K1, 'f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd')
}
)
.done(end, end)
}
)
test(
'pbkdf2 derive long input',
function (t) {
var email = 'ijqmkkafer3xsj5rzoq+msnxsacvkmqxabtsvxvj@some-test-domain-with-a-long-name-example.org'
var password = 'mSnxsacVkMQxAbtSVxVjCCoWArNUsFhiJqmkkafER3XSJ5rzoQ'
var salt = 'identity.mozilla.com/picl/v1/first-PBKDF:' + email
function end() { t.end() }
pbkdf2.derive(password, salt)
.then(
function (K1) {
t.equal(K1, '5f99c22dfac713b6d73094604a05082e6d345f8a00d4947e57105733f51216eb')
}
)
.done(end, end)
}
)
test(
'pbkdf2 derive bit array',
function (t) {
var salt = 'identity.mozilla.com/picl/v1/second-PBKDF:andré@example.org'
var K2 = '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5'
var passwordString = 'pässwörd'
var password = sjcl.bitArray.concat(
sjcl.codec.hex.toBits(K2),
sjcl.codec.utf8String.toBits(passwordString)
)
function end() { t.end() }
pbkdf2.derive(password, salt)
.then(
function (K1) {
t.equal(K1, 'c16d46c31bee242cb31f916e9e38d60b76431d3f5304549cc75ae4bc20c7108c')
}
)
.done(end, end)
}
)

44
test/run/scrypt_tests.js Normal file
Просмотреть файл

@ -0,0 +1,44 @@
/* 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 scrypt = require('../../client/scrypt')
var P = require('p-promise')
var test = require('tap').test
test(
'scrypt basic with a remote helper',
function (t) {
function end() { t.end() }
var K1 = 'f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd'
var salt = 'identity.mozilla.com/picl/v1/scrypt'
var helper = 'http://scrypt.dev.lcip.org/'
scrypt.hash(K1, salt, helper)
.then(
function (K2) {
t.equal(K2, '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5')
}
)
.done(end, end)
}
)
test(
'scrypt basic with a local helper',
function (t) {
function end() { t.end() }
var K1 = 'f84913e3d8e6d624689d0a3e9678ac8dcc79d2c2f3d9641488cd9d6ef6cd83dd'
var salt = 'identity.mozilla.com/picl/v1/scrypt'
scrypt.hash(K1, salt)
.then(
function (K2) {
t.equal(K2, '5b82f146a64126923e4167a0350bb181feba61f63cb1714012b19cb0be0119c5')
}
)
.done(end, end)
}
)

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

@ -180,7 +180,6 @@ function main() {
)
.then(
function () {
t.equal(client.password, newPassword)
return client.keys()
}
)
@ -199,6 +198,38 @@ function main() {
}
)
test(
'Login flow for a new password',
function (t) {
var email = 'verification@example.com'
var password = 'ez'
var wrapKb = null
var client = null
Client.login(config.public_url, email, password)
.then(
function (x) {
client = x
return client.keys()
}
)
.then(
function (keys) {
t.equal(typeof(keys.kA), 'string', 'kA exists, login after password reset')
t.equal(typeof(keys.wrapKb), 'string', 'wrapKb exists, login after password reset')
}
)
.done(
function () {
t.end()
},
function (err) {
t.fail(err.message || err.error)
t.end()
}
)
}
)
test(
'forgot password limits verify attempts',
function (t) {