current commit
260925.23 · crypto: handle PBKDF2 iteration limits
'use strict';

const {
  ArrayBuffer,
  FunctionPrototypeCall,
  PromiseResolve,
} = primordials;

const { Buffer } = require('buffer');

const {
  PBKDF2Job,
  kCryptoJobAsync,
  kCryptoJobSync,
  kCryptoJobWebCrypto,
} = internalBinding('crypto');

const {
  isInt32,
  validateFunction,
  validateInt32,
  validateString,
} = require('internal/validators');

const {
  getArrayBufferOrView,
  normalizeHashName,
  jobPromise,
} = require('internal/crypto/util');

const {
  lazyDOMException,
} = require('internal/util');

const {
  getCryptoKeyHandle,
} = require('internal/crypto/keys');

function pbkdf2(password, salt, iterations, keylen, digest, callback) {
  if (typeof digest === 'function') {
    callback = digest;
    digest = undefined;
  }

  ({ password, salt, iterations, keylen, digest } =
    check(password, salt, iterations, keylen, digest));

  validateFunction(callback, 'callback');

  const job = new PBKDF2Job(
    kCryptoJobAsync,
    password,
    salt,
    iterations,
    keylen,
    digest);

  job.ondone = (err, result) => {
    if (err !== undefined)
      return FunctionPrototypeCall(callback, job, err);
    const buf = Buffer.from(result);
    return FunctionPrototypeCall(callback, job, null, buf);
  };

  job.run();
}

function pbkdf2Sync(password, salt, iterations, keylen, digest) {
  ({ password, salt, iterations, keylen, digest } =
    check(password, salt, iterations, keylen, digest));

  const job = new PBKDF2Job(
    kCryptoJobSync,
    password,
    salt,
    iterations,
    keylen,
    digest);

  const { 0: err, 1: result } = job.run();
  if (err !== undefined)
    throw err;

  return Buffer.from(result);
}

function check(password, salt, iterations, keylen, digest) {
  validateString(digest, 'digest');

  password = getArrayBufferOrView(password, 'password');
  salt = getArrayBufferOrView(salt, 'salt');
  // OpenSSL uses a signed int to represent these values, so we are restricted
  // to the 31-bit range here (which is plenty).
  validateInt32(iterations, 'iterations', 1);
  validateInt32(keylen, 'keylen', 0);
  // Coerce -0 to +0.
  keylen += 0;

  return { password, salt, iterations, keylen, digest };
}

function validatePbkdf2DeriveBits({ iterations }, length) {
  if (length === null)
    throw lazyDOMException('length cannot be null', 'OperationError');

  if (length % 8) {
    throw lazyDOMException(
      'length must be a multiple of 8',
      'OperationError');
  }
  if (iterations === 0)
    throw lazyDOMException('iterations cannot be zero', 'OperationError');
  if (length !== 0 && !isInt32(iterations)) {
    throw lazyDOMException(
      'iterations exceeds the implementation limit', 'OperationError');
  }
}

function pbkdf2DeriveBits(algorithm, baseKey, length) {
  validatePbkdf2DeriveBits(algorithm, length);
  const { iterations, hash, salt } = algorithm;

  if (length === 0)
    return PromiseResolve(new ArrayBuffer(0));

  return jobPromise(() => new PBKDF2Job(
    kCryptoJobWebCrypto,
    getCryptoKeyHandle(baseKey),
    salt,
    iterations,
    length / 8,
    normalizeHashName(hash.name)));
}

module.exports = {
  pbkdf2,
  pbkdf2Sync,
  pbkdf2DeriveBits,
  validatePbkdf2DeriveBits,
};