current commit
260925.24 · crypto: separate conversion from validation
'use strict';

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

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

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

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

const {
  getBufferSourceByteLength,
  jobPromise,
  normalizeHashName,
  toBuf,
  validateByteSource,
  validateAlgorithm,
} = require('internal/crypto/util');

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

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

const {
  isAnyArrayBuffer,
  isArrayBufferView,
  isSharedArrayBuffer,
} = require('internal/util/types');

const {
  codes: {
    ERR_INVALID_ARG_TYPE,
    ERR_OUT_OF_RANGE,
  },
  hideStackFrames,
} = require('internal/errors');

function getByteSourceByteLength(source) {
  if (isSharedArrayBuffer(source)) {
    return TypedArrayPrototypeGetByteLength(new Uint8Array(source));
  }
  return getBufferSourceByteLength(source);
}

const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
  validateString.withoutStackTrace(hash, 'digest');
  key = prepareKey(key);
  salt = validateByteSource.withoutStackTrace(salt, 'salt');
  info = validateByteSource.withoutStackTrace(info, 'info');

  validateInteger.withoutStackTrace(length, 'length', 0, kMaxLength);
  // Coerce -0 to +0.
  length += 0;

  const infoByteLength = getByteSourceByteLength(info);
  if (infoByteLength > 1024) {
    throw new ERR_OUT_OF_RANGE.HideStackFramesError(
      'info',
      'must not contain more than 1024 bytes',
      infoByteLength);
  }

  return {
    hash,
    key,
    salt,
    info,
    length,
  };
});

function prepareKey(key) {
  if (isKeyObject(key))
    return prepareSecretKey(key);

  if (isAnyArrayBuffer(key))
    return key;

  key = toBuf(key);

  if (!isArrayBufferView(key)) {
    throw new ERR_INVALID_ARG_TYPE(
      'ikm',
      [
        'string',
        'SecretKeyObject',
        'ArrayBuffer',
        'TypedArray',
        'DataView',
        'Buffer',
      ],
      key);
  }

  return key;
}

function createHkdfJob(mode, params) {
  return new HKDFJob(
    mode,
    params.hash,
    params.key,
    params.salt,
    params.info,
    params.length);
}

function hkdf(hash, key, salt, info, length, callback) {
  const params = validateParameters(hash, key, salt, info, length);
  validateFunction(callback, 'callback');
  const job = createHkdfJob(kCryptoJobAsync, params);

  job.ondone = (error, bits) => {
    if (error) return FunctionPrototypeCall(callback, job, error);
    FunctionPrototypeCall(callback, job, null, bits);
  };

  job.run();
}

function hkdfSync(hash, key, salt, info, length) {
  const params = validateParameters(hash, key, salt, info, length);
  const job = createHkdfJob(kCryptoJobSync, params);
  const { 0: err, 1: bits } = job.run();
  if (err !== undefined)
    throw err;

  return bits;
}

function getDigestSizeInBytes(name) {
  switch (name) {
    case 'SHA-1':
      return 20;
    case 'SHA-256': // Fall through
    case 'SHA3-256':
      return 32;
    case 'SHA-384': // Fall through
    case 'SHA3-384':
      return 48;
    case 'SHA-512': // Fall through
    case 'SHA3-512':
      return 64;
  }
}

function validateHkdfDeriveBitsLength(length, hash) {
  if (length === null)
    throw lazyDOMException('length cannot be null', 'OperationError');
  if (length % 8) {
    throw lazyDOMException(
      'length must be a multiple of 8',
      'OperationError');
  }
  if (length > 255 * getDigestSizeInBytes(hash.name) * 8) {
    throw lazyDOMException(
      'length exceeds the maximum derived bit length',
      'OperationError');
  }
}

function hkdfDeriveBits(algorithm, baseKey, length) {
  const { hash, salt, info } = algorithm;
  validateHkdfDeriveBitsLength(length, hash);
  validateAlgorithm(algorithm, 'deriveBits');

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

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

module.exports = {
  hkdf,
  hkdfSync,
  hkdfDeriveBits,
  validateHkdfDeriveBitsLength,
};