1 | 'use strict'
|
2 | const assert = require('assert')
|
3 | const { randomBytes, timingSafeEqual } = require('crypto')
|
4 | const { promisify } = require('util')
|
5 |
|
6 | const binary = require('node-pre-gyp')
|
7 | const path = require('path')
|
8 | const bindingPath = binary.find(path.resolve(path.join(__dirname, './package.json')))
|
9 | const { hash: _hash, limits, types, names, version } = require(bindingPath)
|
10 |
|
11 | const { deserialize, serialize } = require('@phc/format')
|
12 |
|
13 | const defaults = Object.freeze({
|
14 | hashLength: 32,
|
15 | saltLength: 16,
|
16 | timeCost: 3,
|
17 | memoryCost: 1 << 12,
|
18 | parallelism: 1,
|
19 | type: types.argon2i,
|
20 | version
|
21 | })
|
22 |
|
23 | const bindingsHash = promisify(_hash)
|
24 | const generateSalt = promisify(randomBytes)
|
25 |
|
26 | const assertLimits = options => ([key, { max, min }]) => {
|
27 | const value = options[key]
|
28 | assert(min <= value && value <= max, `Invalid ${key}, must be between ${min} and ${max}.`)
|
29 | }
|
30 |
|
31 | const hash = async (plain, { raw, salt, ...options } = {}) => {
|
32 | options = { ...defaults, ...options }
|
33 |
|
34 | Object.entries(limits).forEach(assertLimits(options))
|
35 |
|
36 | salt = salt || await generateSalt(options.saltLength)
|
37 |
|
38 | const hash = await bindingsHash(Buffer.from(plain), salt, options)
|
39 | if (raw) {
|
40 | return hash
|
41 | }
|
42 |
|
43 | const { type, version, memoryCost: m, timeCost: t, parallelism: p, associatedData: data } = options
|
44 | return serialize({ id: names[type], version, params: { m, t, p, ...(data ? { data } : {}) }, salt, hash })
|
45 | }
|
46 |
|
47 | const needsRehash = (digest, options) => {
|
48 | const { memoryCost, timeCost, version } = { ...defaults, ...options }
|
49 |
|
50 | const { version: v, params: { m, t } } = deserialize(digest)
|
51 | return +v !== +version || +m !== +memoryCost || +t !== +timeCost
|
52 | }
|
53 |
|
54 | const verify = async (digest, plain, options) => {
|
55 | const { id, version = 0x10, params: { m, t, p, data }, salt, hash } = deserialize(digest)
|
56 |
|
57 | return timingSafeEqual(await bindingsHash(Buffer.from(plain), salt, {
|
58 | ...options,
|
59 | type: types[id],
|
60 | version: +version,
|
61 | hashLength: hash.length,
|
62 | memoryCost: +m,
|
63 | timeCost: +t,
|
64 | parallelism: +p,
|
65 | ...(data ? { associatedData: Buffer.from(data, 'base64') } : {})
|
66 | }), hash)
|
67 | }
|
68 |
|
69 | module.exports = { defaults, limits, hash, needsRehash, verify, ...types }
|