You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
81 lines
2.8 KiB
CoffeeScript
81 lines
2.8 KiB
CoffeeScript
scrypt = require "scrypt"
|
|
errors = require "errors"
|
|
Promise = require "bluebird"
|
|
|
|
# Some custom error types, since the `scrypt` library doesn't have proper error handling
|
|
errors.create name: "ScryptError"
|
|
errors.create {name: "ScryptInputError", parents: errors.ScryptError}
|
|
errors.create {name: "ScryptPasswordError", parents: errors.ScryptError}
|
|
errors.create {name: "ScryptInternalError", parents: errors.ScryptError}
|
|
|
|
scryptErrorMap = {
|
|
"getrlimit or sysctl(hw.usermem) failed": 1
|
|
"clock_getres or clock_gettime failed": 2
|
|
"error computing derived key": 3
|
|
"could not read salt from /dev/urandom": 4
|
|
"error in OpenSSL": 5
|
|
"malloc failed": 6
|
|
"data is not a valid scrypt-encrypted block": 7
|
|
"unrecognized scrypt format": 8
|
|
"decrypting file would take too much memory": 9
|
|
"decrypting file would take too long": 10
|
|
"password is incorrect": 11
|
|
"error writing output file": 12
|
|
"error reading input file": 13
|
|
"error unkown": -1
|
|
}
|
|
|
|
defaultParameters = Promise.promisify(scrypt.params)(0.1, undefined, undefined)
|
|
|
|
getDefaultParameters = (params) ->
|
|
# This wrapper function is to ensure that we only calculate the parameters once, but can still skip waiting for that if custom parameters were passed in anyway.
|
|
if params?
|
|
return params
|
|
else
|
|
return defaultParameters
|
|
|
|
normalizePassword = (password) ->
|
|
if Buffer.isBuffer(password)
|
|
return password
|
|
else
|
|
return new Buffer(password)
|
|
|
|
scryptHandler = (resolve, reject) ->
|
|
# Well, `scrypt` now returns real Error objects. Except now they don't have error codes anymore...
|
|
return (err, result) ->
|
|
if err?
|
|
errorObj = switch (scryptErrorMap[err.message] ? -1)
|
|
when 1, 2, 3, 4, 5, 6, 9, 10, 12, 13, -1 then errors.ScryptInternalError
|
|
when 7, 8 then errors.ScryptInputError
|
|
when 11 then errors.ScryptPasswordError
|
|
reject new errorObj(err.message)
|
|
else if result == true
|
|
resolve result
|
|
else if result == false
|
|
reject new errors.ScryptPasswordError("The password did not match.")
|
|
else
|
|
resolve result.toString("base64")
|
|
|
|
module.exports =
|
|
hash: (password, options = {}, callback) ->
|
|
# We will still manually promisify, because the behaviour of `scrypt` is not predictable. It may either synchronously throw an error or return a Promise, depending on available ECMAScript features...
|
|
Promise.try ->
|
|
getDefaultParameters(options.params)
|
|
.then (parameters) ->
|
|
new Promise (resolve, reject) ->
|
|
scrypt.kdf normalizePassword(password), parameters, scryptHandler(resolve, reject)
|
|
.nodeify(callback)
|
|
|
|
verifyHash: (password, hash, callback) ->
|
|
(new Promise (resolve, reject) ->
|
|
hashBuffer = new Buffer(hash, "base64")
|
|
scrypt.verifyKdf hashBuffer, normalizePassword(password), scryptHandler(resolve, reject)
|
|
).nodeify(callback)
|
|
|
|
ScryptError: errors.ScryptError
|
|
InputError: errors.ScryptInputError
|
|
PasswordError: errors.ScryptPasswordError
|
|
InternalError: errors.ScryptInternalError
|
|
|
|
scryptLib: scrypt
|