All files / src/setup kdf.js

100% Statements 63/63
100% Branches 55/55
100% Functions 1/1
100% Lines 42/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                    1x                                                                     345x 345x 344x         344x   8x 7x 6x 336x   80x 79x 78x     78x 77x 76x 256x   15x 14x 13x 241x   16x 15x 14x     14x 13x 12x     12x 11x 10x 225x   224x 223x 222x     222x 221x 220x     220x 219x 218x   1x   323x   1x  
/**
 * @file Key Derivation Function (KDF) Setup
 * @copyright Multifactor 2022 All Rights Reserved
 *
 * @description
 * Validate and setup a KDF configuration for a multi-factor derived key
 *
 * @author Vivek Nair (https://nair.me) <[email protected]>
 */
 
const defaults = require('../defaults')
 
/**
 * Validate and setup a KDF configuration for a multi-factor derived key
 *
 * @example
 * // setup kdf configuration
 * const config = await mfkdf.setup.kdf({
 *   kdf: 'pbkdf2',
 *   pbkdf2rounds: 100000,
 *   pbkdf2digest: 'sha256'
 * }); // -> { type: 'pbkdf2', params: { rounds: 100000, digest: 'sha256' } }
 *
 * // derive key
 * const key = await mfkdf.kdf('password', 'salt', 8, config);
 * key.toString('hex') // -> 0394a2ede332c9a1
 *
 * @param {Object} [options] - KDF configuration options
 * @param {string} [options.kdf='argon2id'] - KDF algorithm to use; hkdf, pbkdf2, bcrypt, scrypt, argon2i, argon2d, or argon2id
 * @param {string} [options.hkdfdigest='sha256'] - Hash function to use if using hkdf; sha1, sha256, sha384, or sha512
 * @param {number} [options.pbkdf2rounds=310000] - Number of rounds to use if using pbkdf2
 * @param {string} [options.pbkdf2digest='sha256'] - Hash function to use if using pbkdf2; sha1, sha256, sha384, or sha512
 * @param {number} [options.bcryptrounds=10] - Number of rounds to use if using bcrypt
 * @param {number} [options.scryptcost=16384] - Iterations count (N) to use if using scrypt
 * @param {number} [options.scryptblocksize=8] - Block size (r) to use if using scrypt
 * @param {number} [options.scryptparallelism=1] - Parallelism factor (p) to use if using scrypt
 * @param {number} [options.argon2time=2] - Iterations to use if using argon2
 * @param {number} [options.argon2mem=24576] - Memory to use if using argon2
 * @param {number} [options.argon2parallelism=1] - Parallelism to use if using argon2
 * @returns {object} A KDF configuration as a JSON object
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.7.0
 * @memberOf setup
 */
function kdf (options) {
  options = Object.assign(Object.assign({}, defaults.kdf), options)
  if (typeof options.kdf !== 'string') throw new TypeError('kdf must be a string')
  const config = {
    type: options.kdf,
    params: {}
  }
 
  if (options.kdf === 'hkdf') {
    // hdkf digest
    if (typeof options.hkdfdigest !== 'string') throw new TypeError('hkdfdigest must be a string')
    if (!['sha1', 'sha256', 'sha384', 'sha512'].includes(options.hkdfdigest)) throw new RangeError('hkdfdigest must be one of sha1, sha256, sha384, or sha512')
    config.params.digest = options.hkdfdigest
  } else if (options.kdf === 'pbkdf2') {
    // pbkdf2 rounds
    if (!(Number.isInteger(options.pbkdf2rounds))) throw new TypeError('pbkdf2rounds must be an integer')
    if (!(options.pbkdf2rounds > 0)) throw new RangeError('pbkdf2rounds must be positive')
    config.params.rounds = options.pbkdf2rounds
 
    // pbkdf2 digest
    if (typeof options.pbkdf2digest !== 'string') throw new TypeError('pbkdf2digest must be a string')
    if (!['sha1', 'sha256', 'sha384', 'sha512'].includes(options.pbkdf2digest)) throw new RangeError('pbkdf2digest must be one of sha1, sha256, sha384, or sha512')
    config.params.digest = options.pbkdf2digest
  } else if (options.kdf === 'bcrypt') {
    // bcrypt rounds
    if (!(Number.isInteger(options.bcryptrounds))) throw new TypeError('bcryptrounds must be an integer')
    if (!(options.bcryptrounds > 0)) throw new RangeError('bcryptrounds must be positive')
    config.params.rounds = options.bcryptrounds
  } else if (options.kdf === 'scrypt') {
    // scrypt rounds
    if (!(Number.isInteger(options.scryptcost))) throw new TypeError('scryptcost must be a positive integer')
    if (!(options.scryptcost > 0)) throw new RangeError('scryptcost must be positive')
    config.params.rounds = options.scryptcost
 
    // scrypt block size
    if (!(Number.isInteger(options.scryptblocksize))) throw new TypeError('scryptblocksize must be an integer')
    if (!(options.scryptblocksize > 0)) throw new RangeError('scryptblocksize must be positive')
    config.params.blocksize = options.scryptblocksize
 
    // scrypt parallelism
    if (!(Number.isInteger(options.scryptparallelism))) throw new TypeError('scryptparallelism must be an integer')
    if (!(options.scryptparallelism > 0)) throw new RangeError('scryptparallelism must be positive')
    config.params.parallelism = options.scryptparallelism
  } else if (options.kdf === 'argon2i' || options.kdf === 'argon2d' || options.kdf === 'argon2id') {
    // argon2 rounds
    if (!(Number.isInteger(options.argon2time))) throw new TypeError('argon2time must be an integer')
    if (!(options.argon2time > 0)) throw new RangeError('argon2time must be positive')
    config.params.rounds = options.argon2time
 
    // argon2 memory
    if (!(Number.isInteger(options.argon2mem))) throw new TypeError('argon2mem must be an integer')
    if (!(options.argon2mem > 0)) throw new RangeError('argon2mem must be positive')
    config.params.memory = options.argon2mem
 
    // argon2 parallelism
    if (!(Number.isInteger(options.argon2parallelism))) throw new TypeError('argon2parallelism must be an integer')
    if (!(options.argon2parallelism > 0)) throw new RangeError('argon2parallelism must be positive')
    config.params.parallelism = options.argon2parallelism
  } else {
    throw new RangeError('kdf must be one of pbkdf2, bcrypt, scrypt, argon2i, argon2d, or argon2id')
  }
  return config
}
module.exports.kdf = kdf