@ 261106.2
current
'use strict';

const {
  ArrayBufferPrototypeGetByteLength,
  FunctionPrototypeCall,
  ObjectDefineProperty,
  TypedArrayPrototypeGetBuffer,
} = primordials;

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

const {
  DHBitsJob,
  DiffieHellman: _DiffieHellman,
  DiffieHellmanGroup: _DiffieHellmanGroup,
  ECDH: _ECDH,
  ECDHConvertKey: _ECDHConvertKey,
  kCryptoJobAsync,
  kCryptoJobSync,
  kCryptoJobWebCrypto,
} = internalBinding('crypto');

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

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

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

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

const {
  getCryptoKeyAlgorithm,
  getCryptoKeyHandle,
  getCryptoKeyType,
  preparePrivateKey,
  preparePublicOrPrivateKey,
} = require('internal/crypto/keys');

const {
  getArrayBufferOrView,
  jobPromise,
  jobPromiseThen,
  numBitsToBytes,
  toBuf,
  truncateToBitLength,
  validateAlgorithm,
  kHandle,
} = require('internal/crypto/util');

const {
  crypto: {
    POINT_CONVERSION_COMPRESSED,
    POINT_CONVERSION_HYBRID,
    POINT_CONVERSION_UNCOMPRESSED,
  },
} = internalBinding('constants');

const DH_GENERATOR = 2;

function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
  if (!(this instanceof DiffieHellman))
    return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);

  if (typeof sizeOrKey !== 'number' &&
      typeof sizeOrKey !== 'string' &&
      !isArrayBufferView(sizeOrKey) &&
      !isAnyArrayBuffer(sizeOrKey)) {
    throw new ERR_INVALID_ARG_TYPE(
      'sizeOrKey',
      ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
      sizeOrKey,
    );
  }

  // Sizes < 0 don't make sense but they _are_ accepted (and subsequently
  // rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code
  // in node_crypto.cc accepts values that are IsInt32() for that reason
  // and that's why we do that here too.
  if (typeof sizeOrKey === 'number') {
    validateInt32(sizeOrKey, 'sizeOrKey');
    // Coerce -0 to +0.
    sizeOrKey += 0;
  }

  if (keyEncoding && !Buffer.isEncoding(keyEncoding) &&
      keyEncoding !== 'buffer') {
    genEncoding = generator;
    generator = keyEncoding;
    keyEncoding = false;
  }

  if (typeof sizeOrKey !== 'number')
    sizeOrKey = toBuf(sizeOrKey, keyEncoding);

  if (!generator) {
    generator = DH_GENERATOR;
  } else if (typeof generator === 'number') {
    validateInt32(generator, 'generator');
  } else if (typeof generator === 'string') {
    generator = toBuf(generator, genEncoding);
  } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {
    throw new ERR_INVALID_ARG_TYPE(
      'generator',
      ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
      generator,
    );
  }


  this[kHandle] = new _DiffieHellman(sizeOrKey, generator);
  ObjectDefineProperty(this, 'verifyError', {
    __proto__: null,
    enumerable: true,
    value: this[kHandle].verifyError,
    writable: false,
  });
}


function DiffieHellmanGroup(name) {
  if (!(this instanceof DiffieHellmanGroup))
    return new DiffieHellmanGroup(name);
  this[kHandle] = new _DiffieHellmanGroup(name);
  ObjectDefineProperty(this, 'verifyError', {
    __proto__: null,
    enumerable: true,
    value: this[kHandle].verifyError,
    writable: false,
  });
}


DiffieHellmanGroup.prototype.generateKeys =
    DiffieHellman.prototype.generateKeys =
    dhGenerateKeys;

function dhGenerateKeys(encoding) {
  const keys = this[kHandle].generateKeys();
  return encode(keys, encoding);
}


DiffieHellmanGroup.prototype.computeSecret =
    DiffieHellman.prototype.computeSecret =
    dhComputeSecret;

function dhComputeSecret(key, inEnc, outEnc) {
  key = getArrayBufferOrView(key, 'key', inEnc);
  const ret = this[kHandle].computeSecret(key);
  if (typeof ret === 'string')
    throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
  return encode(ret, outEnc);
}


DiffieHellmanGroup.prototype.getPrime =
    DiffieHellman.prototype.getPrime =
    dhGetPrime;

function dhGetPrime(encoding) {
  const prime = this[kHandle].getPrime();
  return encode(prime, encoding);
}


DiffieHellmanGroup.prototype.getGenerator =
    DiffieHellman.prototype.getGenerator =
    dhGetGenerator;

function dhGetGenerator(encoding) {
  const generator = this[kHandle].getGenerator();
  return encode(generator, encoding);
}


DiffieHellmanGroup.prototype.getPublicKey =
    DiffieHellman.prototype.getPublicKey =
    dhGetPublicKey;

function dhGetPublicKey(encoding) {
  const key = this[kHandle].getPublicKey();
  return encode(key, encoding);
}


DiffieHellmanGroup.prototype.getPrivateKey =
    DiffieHellman.prototype.getPrivateKey =
    dhGetPrivateKey;

function dhGetPrivateKey(encoding) {
  const key = this[kHandle].getPrivateKey();
  return encode(key, encoding);
}


DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
  key = getArrayBufferOrView(key, 'key', encoding);
  this[kHandle].setPublicKey(key);
  return this;
};


DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
  key = getArrayBufferOrView(key, 'key', encoding);
  this[kHandle].setPrivateKey(key);
  return this;
};


function ECDH(curve) {
  if (!(this instanceof ECDH))
    return new ECDH(curve);

  validateString(curve, 'curve');
  this[kHandle] = new _ECDH(curve);
}

ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret;
ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey;
ECDH.prototype.setPublicKey = deprecate(DiffieHellman.prototype.setPublicKey,
                                        'ecdh.setPublicKey() is deprecated.',
                                        'DEP0031');
ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey;

ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
  this[kHandle].generateKeys();

  return this.getPublicKey(encoding, format);
};

ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
  const f = getFormat(format);
  const key = this[kHandle].getPublicKey(f);
  return encode(key, encoding);
};

ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
  validateString(curve, 'curve');
  key = getArrayBufferOrView(key, 'key', inEnc);
  const f = getFormat(format);
  const convertedKey = _ECDHConvertKey(key, curve, f);
  return encode(convertedKey, outEnc);
};

function encode(buffer, encoding) {
  if (encoding && encoding !== 'buffer')
    buffer = buffer.toString(encoding);
  return buffer;
}

function getFormat(format) {
  if (format) {
    if (format === 'compressed')
      return POINT_CONVERSION_COMPRESSED;
    if (format === 'hybrid')
      return POINT_CONVERSION_HYBRID;
    if (format !== 'uncompressed')
      throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format);
  }
  return POINT_CONVERSION_UNCOMPRESSED;
}

function diffieHellman(options, callback) {
  validateObject(options, 'options');

  if (callback !== undefined)
    validateFunction(callback, 'callback');

  const { privateKey, publicKey } = options;

  const {
    data: pubData,
    format: pubFormat,
    type: pubType,
    passphrase: pubPassphrase,
    namedCurve: pubNamedCurve,
  } = preparePublicOrPrivateKey(publicKey, 'options.publicKey');

  const {
    data: privData,
    format: privFormat,
    type: privType,
    passphrase: privPassphrase,
    namedCurve: privNamedCurve,
  } = preparePrivateKey(privateKey, 'options.privateKey');

  const job = new DHBitsJob(
    callback ? kCryptoJobAsync : kCryptoJobSync,
    pubData,
    pubFormat,
    pubType,
    pubPassphrase,
    pubNamedCurve,
    privData,
    privFormat,
    privType,
    privPassphrase,
    privNamedCurve);

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

    return Buffer.from(secret);
  }

  job.ondone = (error, secret) => {
    if (error) return FunctionPrototypeCall(callback, job, error);
    FunctionPrototypeCall(callback, job, null, Buffer.from(secret));
  };
  job.run();
}

// The ecdhDeriveBits function is part of the Web Crypto API and serves both
// deriveKeys and deriveBits functions.
function ecdhDeriveBits(algorithm, baseKey, length) {
  validateAlgorithm(algorithm, 'deriveBits');
  const { 'public': key } = algorithm;

  if (getCryptoKeyType(baseKey) !== 'private') {
    throw lazyDOMException(
      'baseKey must be a private key', 'InvalidAccessError');
  }

  const keyAlgorithm = getCryptoKeyAlgorithm(key);
  const baseKeyAlgorithm = getCryptoKeyAlgorithm(baseKey);
  if (keyAlgorithm.name !== baseKeyAlgorithm.name) {
    throw lazyDOMException(
      'The public and private keys must be of the same type',
      'InvalidAccessError');
  }

  if (
    keyAlgorithm.name === 'ECDH' &&
    keyAlgorithm.namedCurve !== baseKeyAlgorithm.namedCurve
  ) {
    throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
  }

  const bits = jobPromise(() => new DHBitsJob(
    kCryptoJobWebCrypto,
    getCryptoKeyHandle(key),
    undefined,
    undefined,
    undefined,
    undefined,
    getCryptoKeyHandle(baseKey),
    undefined,
    undefined,
    undefined,
    undefined));

  // If a length is not specified, return the full derived secret
  if (length === null)
    return bits;

  return jobPromiseThen(bits, (bits) => {
    const sliceLength = numBitsToBytes(length);

    const byteLength = ArrayBufferPrototypeGetByteLength(bits);
    // If the length is larger than the derived secret, throw.
    if (byteLength < sliceLength)
      throw lazyDOMException('derived bit length is too small', 'OperationError');

    if (length % 8 === 0 && byteLength === sliceLength)
      return bits;

    return TypedArrayPrototypeGetBuffer(truncateToBitLength(length, bits));
  });
}

module.exports = {
  DiffieHellman,
  DiffieHellmanGroup,
  ECDH,
  diffieHellman,
  ecdhDeriveBits,
};