text: 'use strict';
  
  const { AsyncWrap, Providers } = process.binding('async_wrap');
  const {
    generateKeyPairRSA,
    generateKeyPairDSA,
    generateKeyPairEC,
    OPENSSL_EC_NAMED_CURVE,
    OPENSSL_EC_EXPLICIT_CURVE,
    PK_ENCODING_PKCS1,
    PK_ENCODING_PKCS8,
    PK_ENCODING_SPKI,
    PK_ENCODING_SEC1,
    PK_FORMAT_DER,
    PK_FORMAT_PEM
  } = process.binding('crypto');
  const { customPromisifyArgs } = require('internal/util');
  const { isUint32 } = require('internal/validators');
  const {
    ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS,
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
    ERR_INVALID_CALLBACK,
    ERR_INVALID_OPT_VALUE
  } = require('internal/errors').codes;
  
  function generateKeyPair(type, options, callback) {
    if (typeof options === 'function') {
      callback = options;
      options = undefined;
    }
  
    const impl = check(type, options);
  
    if (typeof callback !== 'function')
      throw new ERR_INVALID_CALLBACK();
  
    const wrap = new AsyncWrap(Providers.KEYPAIRGENREQUEST);
    wrap.ondone = (ex, pubkey, privkey) => {
      if (ex) return callback.call(wrap, ex);
      callback.call(wrap, null, pubkey, privkey);
    };
  
    handleError(impl, wrap);
  }
  
  Object.defineProperty(generateKeyPair, customPromisifyArgs, {
    value: ['publicKey', 'privateKey'],
    enumerable: false
  });
  
  function generateKeyPairSync(type, options) {
    const impl = check(type, options);
    return handleError(impl);
  }
  
  function handleError(impl, wrap) {
    const ret = impl(wrap);
    if (ret === undefined)
      return; // async
  
    const [err, publicKey, privateKey] = ret;
    if (err !== undefined)
      throw err;
  
    return { publicKey, privateKey };
  }
  
  function parseKeyEncoding(keyType, options) {
    const { publicKeyEncoding, privateKeyEncoding } = options;
  
    if (publicKeyEncoding == null || typeof publicKeyEncoding !== 'object')
      throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding', publicKeyEncoding);
  
    const { format: strPublicFormat, type: strPublicType } = publicKeyEncoding;
  
    let publicType;
    if (strPublicType === 'pkcs1') {
      if (keyType !== 'rsa') {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          strPublicType, 'can only be used for RSA keys');
      }
      publicType = PK_ENCODING_PKCS1;
    } else if (strPublicType === 'spki') {
      publicType = PK_ENCODING_SPKI;
    } else {
      throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.type', strPublicType);
    }
  
    let publicFormat;
    if (strPublicFormat === 'der') {
      publicFormat = PK_FORMAT_DER;
    } else if (strPublicFormat === 'pem') {
      publicFormat = PK_FORMAT_PEM;
    } else {
      throw new ERR_INVALID_OPT_VALUE('publicKeyEncoding.format',
                                      strPublicFormat);
    }
  
    if (privateKeyEncoding == null || typeof privateKeyEncoding !== 'object')
      throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding', privateKeyEncoding);
  
    const {
      cipher,
      passphrase,
      format: strPrivateFormat,
      type: strPrivateType
    } = privateKeyEncoding;
  
    let privateType;
    if (strPrivateType === 'pkcs1') {
      if (keyType !== 'rsa') {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          strPrivateType, 'can only be used for RSA keys');
      }
      privateType = PK_ENCODING_PKCS1;
    } else if (strPrivateType === 'pkcs8') {
      privateType = PK_ENCODING_PKCS8;
    } else if (strPrivateType === 'sec1') {
      if (keyType !== 'ec') {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          strPrivateType, 'can only be used for EC keys');
      }
      privateType = PK_ENCODING_SEC1;
    } else {
      throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.type', strPrivateType);
    }
  
    let privateFormat;
    if (strPrivateFormat === 'der') {
      privateFormat = PK_FORMAT_DER;
    } else if (strPrivateFormat === 'pem') {
      privateFormat = PK_FORMAT_PEM;
    } else {
      throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.format',
                                      strPrivateFormat);
    }
  
    if (cipher != null) {
      if (typeof cipher !== 'string')
        throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.cipher', cipher);
      if (privateFormat === PK_FORMAT_DER &&
          (privateType === PK_ENCODING_PKCS1 ||
           privateType === PK_ENCODING_SEC1)) {
        throw new ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS(
          strPrivateType, 'does not support encryption');
      }
      if (typeof passphrase !== 'string') {
        throw new ERR_INVALID_OPT_VALUE('privateKeyEncoding.passphrase',
                                        passphrase);
      }
    }
  
    return {
      cipher, passphrase, publicType, publicFormat, privateType, privateFormat
    };
  }
  
  function check(type, options, callback) {
    if (typeof type !== 'string')
      throw new ERR_INVALID_ARG_TYPE('type', 'string', type);
    if (options == null || typeof options !== 'object')
      throw new ERR_INVALID_ARG_TYPE('options', 'object', options);
  
    // These will be set after parsing the type and type-specific options to make
    // the order a bit more intuitive.
    let cipher, passphrase, publicType, publicFormat, privateType, privateFormat;
  
    let impl;
    switch (type) {
      case 'rsa':
        {
          const { modulusLength } = options;
          if (!isUint32(modulusLength))
            throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
  
          let { publicExponent } = options;
          if (publicExponent == null) {
            publicExponent = 0x10001;
          } else if (!isUint32(publicExponent)) {
            throw new ERR_INVALID_OPT_VALUE('publicExponent', publicExponent);
          }
  
          impl = (wrap) => generateKeyPairRSA(modulusLength, publicExponent,
                                              publicType, publicFormat,
                                              privateType, privateFormat,
                                              cipher, passphrase, wrap);
        }
        break;
      case 'dsa':
        {
          const { modulusLength } = options;
          if (!isUint32(modulusLength))
            throw new ERR_INVALID_OPT_VALUE('modulusLength', modulusLength);
  
          let { divisorLength } = options;
          if (divisorLength == null) {
            divisorLength = -1;
          } else if (!isUint32(divisorLength)) {
            throw new ERR_INVALID_OPT_VALUE('divisorLength', divisorLength);
          }
  
          impl = (wrap) => generateKeyPairDSA(modulusLength, divisorLength,
                                              publicType, publicFormat,
                                              privateType, privateFormat,
                                              cipher, passphrase, wrap);
        }
        break;
      case 'ec':
        {
          const { namedCurve } = options;
          if (typeof namedCurve !== 'string')
            throw new ERR_INVALID_OPT_VALUE('namedCurve', namedCurve);
          let { paramEncoding } = options;
          if (paramEncoding == null || paramEncoding === 'named')
            paramEncoding = OPENSSL_EC_NAMED_CURVE;
          else if (paramEncoding === 'explicit')
            paramEncoding = OPENSSL_EC_EXPLICIT_CURVE;
          else
            throw new ERR_INVALID_OPT_VALUE('paramEncoding', paramEncoding);
  
          impl = (wrap) => generateKeyPairEC(namedCurve, paramEncoding,
                                             publicType, publicFormat,
                                             privateType, privateFormat,
                                             cipher, passphrase, wrap);
        }
        break;
      default:
        throw new ERR_INVALID_ARG_VALUE('type', type,
                                        "must be one of 'rsa', 'dsa', 'ec'");
    }
  
    ({
      cipher,
      passphrase,
      publicType,
      publicFormat,
      privateType,
      privateFormat
    } = parseKeyEncoding(type, options));
  
    return impl;
  }
  
  module.exports = { generateKeyPair, generateKeyPairSync };
  