2023-09-25 13:13:20 -04:00
|
|
|
const chai = require('chai')
|
|
|
|
chai.use(require('chai-as-promised'))
|
|
|
|
chai.should()
|
|
|
|
const { expect } = chai
|
|
|
|
const scrypt = require('..')
|
|
|
|
|
|
|
|
describe('hashing and verification', function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
it('works', async function () {
|
|
|
|
const pass = 'b4dpa$s'
|
|
|
|
const derived = await scrypt.hash(pass)
|
|
|
|
const [cost, salt, key] = derived.split('.')
|
|
|
|
cost.should.eq('e')
|
|
|
|
salt.length.should.eq(32)
|
|
|
|
key.length.should.eq(86)
|
|
|
|
Buffer.from(key, 'base64url').length.should.eq(64)
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt.verify(derived, pass).should.eventually.be.true
|
2023-09-26 08:45:29 -04:00
|
|
|
})
|
2023-09-25 13:13:20 -04:00
|
|
|
})
|
|
|
|
describe('hashSync() and sync verification', function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
it('works', function () {
|
|
|
|
const pass = 'b4dpa$s'
|
|
|
|
const derived = scrypt.hashSync(pass)
|
|
|
|
const [cost, salt, key] = derived.split('.')
|
|
|
|
cost.should.eq('e')
|
|
|
|
salt.length.should.eq(32)
|
|
|
|
key.length.should.eq(86)
|
|
|
|
Buffer.from(key, 'base64url').length.should.eq(64)
|
|
|
|
scrypt.verifySync(derived, pass).should.be.true
|
|
|
|
})
|
2023-09-25 13:13:20 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('hash() error cases', function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
it('throws an error when it receives an invalid cost value', async function () {
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt
|
2023-09-26 08:45:29 -04:00
|
|
|
.hash('bad password', { cost: 2.3 })
|
|
|
|
.should.be.rejectedWith(scrypt.InvalidOptions)
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt
|
2023-09-26 08:45:29 -04:00
|
|
|
.hash('bad password', { cost: 2 /* too small */ })
|
|
|
|
.should.be.rejectedWith(scrypt.InvalidOptions)
|
|
|
|
})
|
|
|
|
it('throws an error when it receives an invalid keyLength value', async function () {
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt
|
2023-09-26 08:45:29 -04:00
|
|
|
.hash('bad password', { keyLength: 2.3 })
|
|
|
|
.should.be.rejectedWith(scrypt.InvalidOptions)
|
|
|
|
})
|
|
|
|
it('throws an error when it receives an invalid saltLength value', async function () {
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt
|
2023-09-26 08:45:29 -04:00
|
|
|
.hash('bad password', { saltLength: 2.3 })
|
|
|
|
.should.be.rejectedWith(scrypt.InvalidOptions)
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt
|
2023-09-26 08:45:29 -04:00
|
|
|
.hash('bad password', { saltLength: 15 /* too small */ })
|
|
|
|
.should.be.rejectedWith(scrypt.InvalidOptions)
|
|
|
|
})
|
2023-10-11 06:40:30 -04:00
|
|
|
it('throws an error when the password is not the right thing', async function () {
|
|
|
|
await Promise.all([
|
|
|
|
scrypt.hash(undefined).should.be.rejected,
|
|
|
|
scrypt.hash(null).should.be.rejected,
|
|
|
|
scrypt.hash(123.4).should.be.rejected,
|
|
|
|
scrypt.hash(NaN).should.be.rejected,
|
|
|
|
scrypt.hash(['some', 'arbitrary', 'values']).should.be.rejected,
|
|
|
|
scrypt.hash({ some: 'arbitrary values' }).should.be.rejected,
|
|
|
|
scrypt.hash(Symbol('something else')).should.be.rejected,
|
|
|
|
scrypt.hash(() => "no, you can't do this either").should.be.rejected,
|
|
|
|
])
|
2023-09-26 08:45:29 -04:00
|
|
|
})
|
2023-09-25 13:13:20 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('hashSync() error cases', function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
it('throws an error when it receives an invalid cost value', async function () {
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, 'bad password', { cost: 2.3 })
|
|
|
|
.should.throw(scrypt.InvalidOptions)
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, 'bad password', { cost: 2 /* too small */ })
|
|
|
|
.should.throw(scrypt.InvalidOptions)
|
|
|
|
})
|
|
|
|
it('throws an error when it receives an invalid keyLength value', async function () {
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, 'bad password', { keyLength: 2.3 })
|
|
|
|
.should.throw(scrypt.InvalidOptions)
|
|
|
|
})
|
|
|
|
it('throws an error when it receives an invalid saltLength value', async function () {
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, 'bad password', { saltLength: 2.3 })
|
|
|
|
.should.throw(scrypt.InvalidOptions)
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, 'bad password', { saltLength: 15 })
|
|
|
|
.should.throw(scrypt.InvalidOptions)
|
|
|
|
})
|
|
|
|
it('throws an error when the password is not the right thing', function () {
|
|
|
|
scrypt.hashSync.bind(undefined, undefined).should.throw()
|
|
|
|
scrypt.hashSync.bind(undefined, null).should.throw()
|
|
|
|
scrypt.hashSync.bind(undefined, 123.4).should.throw()
|
|
|
|
scrypt.hashSync.bind(undefined, NaN).should.throw()
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, ['some', 'arbitrary', 'values'])
|
|
|
|
.should.throw()
|
|
|
|
scrypt.hashSync.bind(undefined, { some: 'arbitrary values' }).should.throw()
|
|
|
|
scrypt.hashSync.bind(undefined, Symbol('something else')).should.throw()
|
|
|
|
scrypt.hashSync
|
|
|
|
.bind(undefined, () => "no, you can't do this either")
|
|
|
|
.should.throw()
|
|
|
|
})
|
2023-09-25 13:13:20 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('verify() error cases', function () {
|
2023-10-11 06:40:30 -04:00
|
|
|
it('throws an error when the password is not the right thing', async function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
const validHash =
|
|
|
|
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
|
2023-10-11 06:40:30 -04:00
|
|
|
await Promise.all([
|
|
|
|
scrypt.verify(validHash, undefined).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, null).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, 123.4).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, NaN).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, ['some', 'arbitrary', 'values']).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, { some: 'arbitrary values' }).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, Symbol('something else')).should.be.rejected,
|
|
|
|
scrypt.verify(validHash, () => "no, you can't do this either").should.be
|
|
|
|
.rejected,
|
|
|
|
scrypt.verify(validHash, Promise.resolve("no, you can't do this either"))
|
|
|
|
.should.be.rejected,
|
|
|
|
])
|
2023-09-26 08:45:29 -04:00
|
|
|
})
|
|
|
|
it('throws an error when the hash is not the expected format', async function () {
|
|
|
|
const validHash =
|
|
|
|
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
|
|
|
|
const hashParts = validHash.split('.')
|
|
|
|
async function checkThrows(value, cause) {
|
|
|
|
let thrown
|
|
|
|
try {
|
|
|
|
await scrypt.verify(value, 'bad password')
|
|
|
|
thrown = false
|
|
|
|
} catch (e) {
|
|
|
|
thrown = true
|
|
|
|
e.should.be.an.instanceof(scrypt.ParseError)
|
|
|
|
expect(e.malformedString).to.eq(value)
|
|
|
|
if (cause) expect(e.cause).to.be.an.instanceof(cause)
|
|
|
|
else expect(e.cause).to.be.undefined
|
|
|
|
}
|
|
|
|
thrown.should.be.true
|
|
|
|
}
|
|
|
|
await checkThrows(undefined, TypeError)
|
|
|
|
await checkThrows(1234, TypeError)
|
|
|
|
await checkThrows(null, TypeError)
|
|
|
|
await checkThrows([1, 2, 3], TypeError)
|
2023-10-11 06:40:30 -04:00
|
|
|
await scrypt.verify(
|
2023-09-26 08:45:29 -04:00
|
|
|
{
|
|
|
|
// This is technically valid, why try to prevent it? 🤷
|
|
|
|
split: () => hashParts,
|
|
|
|
},
|
|
|
|
'bad password',
|
|
|
|
).should.eventually.be.true
|
|
|
|
await checkThrows({}, TypeError)
|
2023-09-25 13:13:20 -04:00
|
|
|
|
2023-09-26 08:45:29 -04:00
|
|
|
await checkThrows(validHash.substring(1)) // No cost
|
|
|
|
// no salt
|
|
|
|
await checkThrows('e..' + hashParts[2])
|
|
|
|
// no key
|
|
|
|
await checkThrows(hashParts[0] + '.' + hashParts[1])
|
|
|
|
await checkThrows('...')
|
|
|
|
await checkThrows('')
|
|
|
|
})
|
2023-09-25 13:13:20 -04:00
|
|
|
})
|
|
|
|
|
|
|
|
describe('verifySync() error cases', function () {
|
2023-09-26 08:45:29 -04:00
|
|
|
it('throws an error when the password is not the right thing', function () {
|
|
|
|
const validHash =
|
|
|
|
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
|
|
|
|
scrypt.verifySync.bind(undefined, validHash, undefined).should.throw()
|
|
|
|
scrypt.verifySync.bind(undefined, validHash, null).should.throw()
|
|
|
|
scrypt.verifySync.bind(undefined, validHash, 123.4).should.throw()
|
|
|
|
scrypt.verifySync.bind(undefined, validHash, NaN).should.throw()
|
|
|
|
scrypt.verifySync
|
|
|
|
.bind(undefined, validHash, ['some', 'arbitrary', 'values'])
|
|
|
|
.should.throw()
|
|
|
|
scrypt.verifySync
|
|
|
|
.bind(undefined, validHash, { some: 'arbitrary values' })
|
|
|
|
.should.throw()
|
|
|
|
scrypt.verifySync
|
|
|
|
.bind(undefined, validHash, Symbol('something else'))
|
|
|
|
.should.throw()
|
|
|
|
scrypt.verifySync
|
|
|
|
.bind(undefined, validHash, () => "no, you can't do this either")
|
|
|
|
.should.throw()
|
|
|
|
scrypt.verifySync
|
|
|
|
.bind(
|
|
|
|
undefined,
|
|
|
|
validHash,
|
|
|
|
Promise.resolve("no, you can't do this either"),
|
|
|
|
)
|
|
|
|
.should.throw()
|
|
|
|
})
|
|
|
|
it('throws an error when the hash is not the expected format', function () {
|
|
|
|
const validHash =
|
|
|
|
'e.WCu8zB9FHLhXaAf5Svn7NE4ySqB5X45X.ZLpxVwLz1816kmshqXHn12X_4_lZD_0Yl-27KSLLzdhXJ0Fr2huSD7BvoMlBMOMUQBKyKXPzhKI01_Ot-C_w8g'
|
|
|
|
const hashParts = validHash.split('.')
|
|
|
|
function checkThrows(value, cause) {
|
|
|
|
let thrown
|
|
|
|
try {
|
|
|
|
scrypt.verifySync(value, 'bad password')
|
|
|
|
thrown = false
|
|
|
|
} catch (e) {
|
|
|
|
thrown = true
|
|
|
|
e.should.be.an.instanceof(scrypt.ParseError)
|
|
|
|
expect(e.malformedString).to.eq(value)
|
|
|
|
if (cause) expect(e.cause).to.be.an.instanceof(cause)
|
|
|
|
else expect(e.cause).to.be.undefined
|
|
|
|
}
|
|
|
|
thrown.should.be.true
|
|
|
|
}
|
|
|
|
checkThrows(undefined, TypeError)
|
|
|
|
checkThrows(1234, TypeError)
|
|
|
|
checkThrows(null, TypeError)
|
|
|
|
checkThrows([1, 2, 3], TypeError)
|
|
|
|
scrypt.verify(
|
|
|
|
{
|
|
|
|
// This is technically valid, why try to prevent it? 🤷
|
|
|
|
split: () => hashParts,
|
|
|
|
},
|
|
|
|
'bad password',
|
|
|
|
).should.eventually.be.true
|
|
|
|
checkThrows({}, TypeError)
|
2023-09-25 13:13:20 -04:00
|
|
|
|
2023-09-26 08:45:29 -04:00
|
|
|
checkThrows(validHash.substring(1)) // No cost
|
|
|
|
// no salt
|
|
|
|
checkThrows('e..' + hashParts[2])
|
|
|
|
// no key
|
|
|
|
checkThrows(hashParts[0] + '.' + hashParts[1])
|
|
|
|
checkThrows('...')
|
|
|
|
checkThrows('')
|
|
|
|
})
|
|
|
|
})
|
2023-10-11 06:40:47 -04:00
|
|
|
|
|
|
|
describe('normalization of unicode characters', function () {
|
|
|
|
it('works, async', async () => {
|
|
|
|
const angstromChar = '\xC5ngstrom'
|
|
|
|
const composed = 'A\u030Angstrom'
|
|
|
|
const withRingAbove = '\u212Bngstrom'
|
|
|
|
let hash = await scrypt.hash(angstromChar)
|
|
|
|
const tests = []
|
|
|
|
tests.push(scrypt.verify(hash, composed).should.eventually.be.true)
|
|
|
|
tests.push(scrypt.verify(hash, withRingAbove).should.eventually.be.true)
|
|
|
|
hash = await scrypt.hash(composed)
|
|
|
|
tests.push(scrypt.verify(hash, withRingAbove).should.eventually.be.true)
|
|
|
|
await Promise.all(tests)
|
|
|
|
})
|
|
|
|
it('works, sync', () => {
|
|
|
|
const angstromChar = '\xC5ngstrom'
|
|
|
|
const composed = 'A\u030Angstrom'
|
|
|
|
const withRingAbove = '\u212Bngstrom'
|
|
|
|
let hash = scrypt.hashSync(angstromChar)
|
|
|
|
scrypt.verifySync(hash, composed).should.be.true
|
|
|
|
scrypt.verifySync(hash, withRingAbove).should.be.true
|
|
|
|
hash = scrypt.hashSync(composed)
|
|
|
|
scrypt.verifySync(hash, withRingAbove).should.be.true
|
|
|
|
})
|
|
|
|
})
|