'use strict';

const common = require('../common');
if (!common.hasCrypto)
  common.skip('missing crypto');

const assert = require('assert');
const {
  constants,
  createPrivateKey,
  createSign,
  createVerify,
  generateKeyPair,
  generateKeyPairSync,
  getCurves,
  publicEncrypt,
  privateDecrypt,
  sign,
  verify
} = require('crypto');
const { inspect, promisify } = require('util');

// Asserts that the size of the given key (in chars or bytes) is within 10% of
// the expected size.
function assertApproximateSize(key, expectedSize) {
  const u = typeof key === 'string' ? 'chars' : 'bytes';
  const min = Math.floor(0.9 * expectedSize);
  const max = Math.ceil(1.1 * expectedSize);
  assert(key.length >= min,
         `Key (${key.length} ${u}) is shorter than expected (${min} ${u})`);
  assert(key.length <= max,
         `Key (${key.length} ${u}) is longer than expected (${max} ${u})`);
}

// Tests that a key pair can be used for encryption / decryption.
function testEncryptDecrypt(publicKey, privateKey) {
  const message = 'Hello Node.js world!';
  const plaintext = Buffer.from(message, 'utf8');
  for (const key of [publicKey, privateKey]) {
    const ciphertext = publicEncrypt(key, plaintext);
    const received = privateDecrypt(privateKey, ciphertext);
    assert.strictEqual(received.toString('utf8'), message);
  }
}

// Tests that a key pair can be used for signing / verification.
function testSignVerify(publicKey, privateKey) {
  const message = Buffer.from('Hello Node.js world!');

  function oldSign(algo, data, key) {
    return createSign(algo).update(data).sign(key);
  }

  function oldVerify(algo, data, key, signature) {
    return createVerify(algo).update(data).verify(key, signature);
  }

  for (const signFn of [sign, oldSign]) {
    const signature = signFn('SHA256', message, privateKey);
    for (const verifyFn of [verify, oldVerify]) {
      for (const key of [publicKey, privateKey]) {
        const okay = verifyFn('SHA256', message, key, signature);
        assert(okay);
      }
    }
  }
}

// Constructs a regular expression for a PEM-encoded key with the given label.
function getRegExpForPEM(label, cipher) {
  const head = `\\-\\-\\-\\-\\-BEGIN ${label}\\-\\-\\-\\-\\-`;
  const rfc1421Header = cipher == null ? '' :
    `\nProc-Type: 4,ENCRYPTED\nDEK-Info: ${cipher},[^\n]+\n`;
  const body = '([a-zA-Z0-9\\+/=]{64}\n)*[a-zA-Z0-9\\+/=]{1,64}';
  const end = `\\-\\-\\-\\-\\-END ${label}\\-\\-\\-\\-\\-`;
  return new RegExp(`^${head}${rfc1421Header}\n${body}\n${end}\n$`);
}

const pkcs1PubExp = getRegExpForPEM('RSA PUBLIC KEY');
const pkcs1PrivExp = getRegExpForPEM('RSA PRIVATE KEY');
const pkcs1EncExp = (cipher) => getRegExpForPEM('RSA PRIVATE KEY', cipher);
const spkiExp = getRegExpForPEM('PUBLIC KEY');
const pkcs8Exp = getRegExpForPEM('PRIVATE KEY');
const pkcs8EncExp = getRegExpForPEM('ENCRYPTED PRIVATE KEY');
const sec1Exp = getRegExpForPEM('EC PRIVATE KEY');
const sec1EncExp = (cipher) => getRegExpForPEM('EC PRIVATE KEY', cipher);

{
  // To make the test faster, we will only test sync key generation once and
  // with a relatively small key.
  const ret = generateKeyPairSync('rsa', {
    publicExponent: 3,
    modulusLength: 512,
    publicKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem'
    }
  });

  assert.strictEqual(Object.keys(ret).length, 2);
  const { publicKey, privateKey } = ret;

  assert.strictEqual(typeof publicKey, 'string');
  assert.match(publicKey, pkcs1PubExp);
  assertApproximateSize(publicKey, 162);
  assert.strictEqual(typeof privateKey, 'string');
  assert.match(privateKey, pkcs8Exp);
  assertApproximateSize(privateKey, 512);

  testEncryptDecrypt(publicKey, privateKey);
  testSignVerify(publicKey, privateKey);
}

{
  // Test sync key generation with key objects with a non-standard
  // publicExponent
  const { publicKey, privateKey } = generateKeyPairSync('rsa', {
    publicExponent: 3,
    modulusLength: 512
  });

  assert.strictEqual(typeof publicKey, 'object');
  assert.strictEqual(publicKey.type, 'public');
  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
  assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
    modulusLength: 512,
    publicExponent: 3n
  });

  assert.strictEqual(typeof privateKey, 'object');
  assert.strictEqual(privateKey.type, 'private');
  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
  assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
    modulusLength: 512,
    publicExponent: 3n
  });
}

{
  // Test sync key generation with key objects.
  const { publicKey, privateKey } = generateKeyPairSync('rsa', {
    modulusLength: 512
  });

  assert.strictEqual(typeof publicKey, 'object');
  assert.strictEqual(publicKey.type, 'public');
  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
  assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
    modulusLength: 512,
    publicExponent: 65537n
  });

  assert.strictEqual(typeof privateKey, 'object');
  assert.strictEqual(privateKey.type, 'private');
  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
  assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
    modulusLength: 512,
    publicExponent: 65537n
  });
}

{
  const publicKeyEncoding = {
    type: 'pkcs1',
    format: 'der'
  };

  // Test async RSA key generation.
  generateKeyPair('rsa', {
    publicExponent: 0x10001,
    modulusLength: 512,
    publicKeyEncoding,
    privateKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    }
  }, common.mustSucceed((publicKeyDER, privateKey) => {
    assert(Buffer.isBuffer(publicKeyDER));
    assertApproximateSize(publicKeyDER, 74);

    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, pkcs1PrivExp);
    assertApproximateSize(privateKey, 512);

    const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));

  // Now do the same with an encrypted private key.
  generateKeyPair('rsa', {
    publicExponent: 0x10001,
    modulusLength: 512,
    publicKeyEncoding,
    privateKeyEncoding: {
      type: 'pkcs1',
      format: 'pem',
      cipher: 'aes-256-cbc',
      passphrase: 'secret'
    }
  }, common.mustSucceed((publicKeyDER, privateKey) => {
    assert(Buffer.isBuffer(publicKeyDER));
    assertApproximateSize(publicKeyDER, 74);

    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, pkcs1EncExp('AES-256-CBC'));

    // Since the private key is encrypted, signing shouldn't work anymore.
    const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
    const expectedError = common.hasOpenSSL3 ? {
      name: 'Error',
      message: 'error:07880109:common libcrypto routines::interrupted or ' +
               'cancelled'
    } : {
      name: 'TypeError',
      code: 'ERR_MISSING_PASSPHRASE',
      message: 'Passphrase required for encrypted key'
    };
    assert.throws(() => testSignVerify(publicKey, privateKey), expectedError);

    const key = { key: privateKey, passphrase: 'secret' };
    testEncryptDecrypt(publicKey, key);
    testSignVerify(publicKey, key);
  }));

  // Now do the same with an encrypted private key, but encoded as DER.
  generateKeyPair('rsa', {
    publicExponent: 0x10001,
    modulusLength: 512,
    publicKeyEncoding,
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'der',
      cipher: 'aes-256-cbc',
      passphrase: 'secret'
    }
  }, common.mustSucceed((publicKeyDER, privateKeyDER) => {
    assert(Buffer.isBuffer(publicKeyDER));
    assertApproximateSize(publicKeyDER, 74);

    assert(Buffer.isBuffer(privateKeyDER));

    // Since the private key is encrypted, signing shouldn't work anymore.
    const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
    assert.throws(() => {
      testSignVerify(publicKey, {
        key: privateKeyDER,
        format: 'der',
        type: 'pkcs8'
      });
    }, {
      name: 'TypeError',
      code: 'ERR_MISSING_PASSPHRASE',
      message: 'Passphrase required for encrypted key'
    });

    // Signing should work with the correct password.

    const privateKey = {
      key: privateKeyDER,
      format: 'der',
      type: 'pkcs8',
      passphrase: 'secret'
    };
    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));

  // Now do the same with an encrypted private key, but encoded as DER.
  generateKeyPair('rsa', {
    publicExponent: 0x10001,
    modulusLength: 512,
    publicKeyEncoding,
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'der'
    }
  }, common.mustSucceed((publicKeyDER, privateKeyDER) => {
    assert(Buffer.isBuffer(publicKeyDER));
    assertApproximateSize(publicKeyDER, 74);

    assert(Buffer.isBuffer(privateKeyDER));

    const publicKey = { key: publicKeyDER, ...publicKeyEncoding };
    const privateKey = {
      key: privateKeyDER,
      format: 'der',
      type: 'pkcs8',
      passphrase: 'secret'
    };
    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));
}

{
  // Test RSA-PSS.
  generateKeyPair('rsa-pss', {
    modulusLength: 512,
    saltLength: 16,
    hashAlgorithm: 'sha256',
    mgf1HashAlgorithm: 'sha256'
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 16
    });

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 16
    });

    // Unlike RSA, RSA-PSS does not allow encryption.
    assert.throws(() => {
      testEncryptDecrypt(publicKey, privateKey);
    }, /operation not supported for this keytype/);

    // RSA-PSS also does not permit signing with PKCS1 padding.
    assert.throws(() => {
      testSignVerify({
        key: publicKey,
        padding: constants.RSA_PKCS1_PADDING
      }, {
        key: privateKey,
        padding: constants.RSA_PKCS1_PADDING
      });
    }, /illegal or unsupported padding mode/);

    // The padding should correctly default to RSA_PKCS1_PSS_PADDING now.
    testSignVerify(publicKey, privateKey);
  }));
}

{
  // 'rsa-pss' should not add a RSASSA-PSS-params sequence by default.
  // Regression test for: https://github.com/nodejs/node/issues/39936

  generateKeyPair('rsa-pss', {
    modulusLength: 512
  }, common.mustSucceed((publicKey, privateKey) => {
    const expectedKeyDetails = {
      modulusLength: 512,
      publicExponent: 65537n
    };
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);

    // To allow backporting the fix to versions that do not support
    // asymmetricKeyDetails for RSA-PSS params, also verify that the exported
    // AlgorithmIdentifier member of the SubjectPublicKeyInfo has the expected
    // length of 11 bytes (as opposed to > 11 bytes if node added params).
    const spki = publicKey.export({ format: 'der', type: 'spki' });
    assert.strictEqual(spki[3], 11, spki.toString('hex'));
  }));
}

{
  // RFC 8017, 9.1.: "Assuming that the mask generation function is based on a
  // hash function, it is RECOMMENDED that the hash function be the same as the
  // one that is applied to the message."

  generateKeyPair('rsa-pss', {
    modulusLength: 512,
    hashAlgorithm: 'sha256',
    saltLength: 16
  }, common.mustSucceed((publicKey, privateKey) => {
    const expectedKeyDetails = {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 16
    };
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
  }));
}

{
  // RFC 8017, A.2.3.: "For a given hashAlgorithm, the default value of
  // saltLength is the octet length of the hash value."

  generateKeyPair('rsa-pss', {
    modulusLength: 512,
    hashAlgorithm: 'sha512'
  }, common.mustSucceed((publicKey, privateKey) => {
    const expectedKeyDetails = {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha512',
      mgf1HashAlgorithm: 'sha512',
      saltLength: 64
    };
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
  }));

  // It is still possible to explicitly set saltLength to 0.
  generateKeyPair('rsa-pss', {
    modulusLength: 512,
    hashAlgorithm: 'sha512',
    saltLength: 0
  }, common.mustSucceed((publicKey, privateKey) => {
    const expectedKeyDetails = {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha512',
      mgf1HashAlgorithm: 'sha512',
      saltLength: 0
    };
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
  }));
}

{
  const privateKeyEncoding = {
    type: 'pkcs8',
    format: 'der'
  };

  // Test async DSA key generation.
  generateKeyPair('dsa', {
    modulusLength: common.hasOpenSSL3 ? 2048 : 512,
    divisorLength: 256,
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      cipher: 'aes-128-cbc',
      passphrase: 'secret',
      ...privateKeyEncoding
    }
  }, common.mustSucceed((publicKey, privateKeyDER) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    // The private key is DER-encoded.
    assert(Buffer.isBuffer(privateKeyDER));

    assertApproximateSize(publicKey, common.hasOpenSSL3 ? 1194 : 440);
    assertApproximateSize(privateKeyDER, common.hasOpenSSL3 ? 721 : 336);

    // Since the private key is encrypted, signing shouldn't work anymore.
    assert.throws(() => {
      return testSignVerify(publicKey, {
        key: privateKeyDER,
        ...privateKeyEncoding
      });
    }, {
      name: 'TypeError',
      code: 'ERR_MISSING_PASSPHRASE',
      message: 'Passphrase required for encrypted key'
    });

    // Signing should work with the correct password.
    testSignVerify(publicKey, {
      key: privateKeyDER,
      ...privateKeyEncoding,
      passphrase: 'secret'
    });
  }));
}
{
  // Test async DSA key object generation.
  generateKeyPair('dsa', {
    modulusLength: common.hasOpenSSL3 ? 2048 : 512,
    divisorLength: 256
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
      modulusLength: common.hasOpenSSL3 ? 2048 : 512,
      divisorLength: 256
    });

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
      modulusLength: common.hasOpenSSL3 ? 2048 : 512,
      divisorLength: 256
    });
  }));
}

{
  // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1
  // private key.
  generateKeyPair('ec', {
    namedCurve: 'prime256v1',
    paramEncoding: 'named',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'sec1',
      format: 'pem'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, sec1Exp);

    testSignVerify(publicKey, privateKey);
  }));

  // Test async elliptic curve key generation, e.g. for ECDSA, with a SEC1
  // private key with paramEncoding explicit.
  generateKeyPair('ec', {
    namedCurve: 'prime256v1',
    paramEncoding: 'explicit',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'sec1',
      format: 'pem'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, sec1Exp);

    testSignVerify(publicKey, privateKey);
  }));

  // Do the same with an encrypted private key.
  generateKeyPair('ec', {
    namedCurve: 'prime256v1',
    paramEncoding: 'named',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'sec1',
      format: 'pem',
      cipher: 'aes-128-cbc',
      passphrase: 'secret'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, sec1EncExp('AES-128-CBC'));

    // Since the private key is encrypted, signing shouldn't work anymore.
    assert.throws(() => testSignVerify(publicKey, privateKey),
                  common.hasOpenSSL3 ? {
                    message: 'error:07880109:common libcrypto ' +
                             'routines::interrupted or cancelled'
                  } : {
                    name: 'TypeError',
                    code: 'ERR_MISSING_PASSPHRASE',
                    message: 'Passphrase required for encrypted key'
                  });

    testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' });
  }));

  // Do the same with an encrypted private key with paramEncoding explicit.
  generateKeyPair('ec', {
    namedCurve: 'prime256v1',
    paramEncoding: 'explicit',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'sec1',
      format: 'pem',
      cipher: 'aes-128-cbc',
      passphrase: 'secret'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, sec1EncExp('AES-128-CBC'));

    // Since the private key is encrypted, signing shouldn't work anymore.
    assert.throws(() => testSignVerify(publicKey, privateKey),
                  common.hasOpenSSL3 ? {
                    message: 'error:07880109:common libcrypto ' +
                             'routines::interrupted or cancelled'
                  } : {
                    name: 'TypeError',
                    code: 'ERR_MISSING_PASSPHRASE',
                    message: 'Passphrase required for encrypted key'
                  });

    testSignVerify(publicKey, { key: privateKey, passphrase: 'secret' });
  }));
}

{
  // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted
  // private key.
  generateKeyPair('ec', {
    namedCurve: 'P-256',
    paramEncoding: 'named',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem',
      cipher: 'aes-128-cbc',
      passphrase: 'top secret'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, pkcs8EncExp);

    // Since the private key is encrypted, signing shouldn't work anymore.
    assert.throws(() => testSignVerify(publicKey, privateKey),
                  common.hasOpenSSL3 ? {
                    message: 'error:07880109:common libcrypto ' +
                             'routines::interrupted or cancelled'
                  } : {
                    name: 'TypeError',
                    code: 'ERR_MISSING_PASSPHRASE',
                    message: 'Passphrase required for encrypted key'
                  });

    testSignVerify(publicKey, {
      key: privateKey,
      passphrase: 'top secret'
    });
  }));

  // Test async elliptic curve key generation, e.g. for ECDSA, with an encrypted
  // private key with paramEncoding explicit.
  generateKeyPair('ec', {
    namedCurve: 'P-256',
    paramEncoding: 'explicit',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem',
      cipher: 'aes-128-cbc',
      passphrase: 'top secret'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, spkiExp);
    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, pkcs8EncExp);

    // Since the private key is encrypted, signing shouldn't work anymore.
    assert.throws(() => testSignVerify(publicKey, privateKey),
                  common.hasOpenSSL3 ? {
                    message: 'error:07880109:common libcrypto ' +
                             'routines::interrupted or cancelled'
                  } : {
                    name: 'TypeError',
                    code: 'ERR_MISSING_PASSPHRASE',
                    message: 'Passphrase required for encrypted key'
                  });

    testSignVerify(publicKey, {
      key: privateKey,
      passphrase: 'top secret'
    });
  }));

  // Test async elliptic curve key generation with 'jwk' encoding
  [
    ['ec', ['P-384', 'P-256', 'P-521', 'secp256k1']],
    ['rsa'],
    ['ed25519'],
    ['ed448'],
    ['x25519'],
    ['x448'],
  ].forEach((types) => {
    const [type, options] = types;
    switch (type) {
      case 'ec': {
        return options.forEach((curve) => {
          generateKeyPair(type, {
            namedCurve: curve,
            publicKeyEncoding: {
              format: 'jwk'
            },
            privateKeyEncoding: {
              format: 'jwk'
            }
          }, common.mustSucceed((publicKey, privateKey) => {
            assert.strictEqual(typeof publicKey, 'object');
            assert.strictEqual(typeof privateKey, 'object');
            assert.strictEqual(publicKey.x, privateKey.x);
            assert.strictEqual(publicKey.y, privateKey.y);
            assert(!publicKey.d);
            assert(privateKey.d);
            assert.strictEqual(publicKey.kty, 'EC');
            assert.strictEqual(publicKey.kty, privateKey.kty);
            assert.strictEqual(publicKey.crv, curve);
            assert.strictEqual(publicKey.crv, privateKey.crv);
          }));
        });
      }
      case 'rsa': {
        return generateKeyPair(type, {
          modulusLength: 4096,
          publicKeyEncoding: {
            format: 'jwk'
          },
          privateKeyEncoding: {
            format: 'jwk'
          }
        }, common.mustSucceed((publicKey, privateKey) => {
          assert.strictEqual(typeof publicKey, 'object');
          assert.strictEqual(typeof privateKey, 'object');
          assert.strictEqual(publicKey.kty, 'RSA');
          assert.strictEqual(publicKey.kty, privateKey.kty);
          assert.strictEqual(typeof publicKey.n, 'string');
          assert.strictEqual(publicKey.n, privateKey.n);
          assert.strictEqual(typeof publicKey.e, 'string');
          assert.strictEqual(publicKey.e, privateKey.e);
          assert.strictEqual(typeof privateKey.d, 'string');
          assert.strictEqual(typeof privateKey.p, 'string');
          assert.strictEqual(typeof privateKey.q, 'string');
          assert.strictEqual(typeof privateKey.dp, 'string');
          assert.strictEqual(typeof privateKey.dq, 'string');
          assert.strictEqual(typeof privateKey.qi, 'string');
        }));
      }
      case 'ed25519':
      case 'ed448':
      case 'x25519':
      case 'x448': {
        generateKeyPair(type, {
          publicKeyEncoding: {
            format: 'jwk'
          },
          privateKeyEncoding: {
            format: 'jwk'
          }
        }, common.mustSucceed((publicKey, privateKey) => {
          assert.strictEqual(typeof publicKey, 'object');
          assert.strictEqual(typeof privateKey, 'object');
          assert.strictEqual(publicKey.x, privateKey.x);
          assert(!publicKey.d);
          assert(privateKey.d);
          assert.strictEqual(publicKey.kty, 'OKP');
          assert.strictEqual(publicKey.kty, privateKey.kty);
          const expectedCrv = `${type.charAt(0).toUpperCase()}${type.slice(1)}`;
          assert.strictEqual(publicKey.crv, expectedCrv);
          assert.strictEqual(publicKey.crv, privateKey.crv);
        }));
      }
    }
  });
}

// Test invalid parameter encoding.
{
  assert.throws(() => generateKeyPairSync('ec', {
    namedCurve: 'P-256',
    paramEncoding: 'otherEncoding',
    publicKeyEncoding: {
      type: 'spki',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem',
      cipher: 'aes-128-cbc',
      passphrase: 'top secret'
    }
  }), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_VALUE',
    message: "The property 'options.paramEncoding' is invalid. " +
      "Received 'otherEncoding'"
  });
  assert.throws(() => generateKeyPairSync('dsa', {
    modulusLength: 4096,
    publicKeyEncoding: {
      format: 'jwk'
    },
    privateKeyEncoding: {
      format: 'jwk'
    }
  }), {
    name: 'Error',
    code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE',
    message: 'Unsupported JWK Key Type.'
  });
  assert.throws(() => generateKeyPairSync('ec', {
    namedCurve: 'secp224r1',
    publicKeyEncoding: {
      format: 'jwk'
    },
    privateKeyEncoding: {
      format: 'jwk'
    }
  }), {
    name: 'Error',
    code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
    message: 'Unsupported JWK EC curve: secp224r1.'
  });
}

{
  // Test the util.promisified API with async RSA key generation.
  promisify(generateKeyPair)('rsa', {
    publicExponent: 0x10001,
    modulusLength: 512,
    publicKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    }
  }).then(common.mustCall((keys) => {
    const { publicKey, privateKey } = keys;
    assert.strictEqual(typeof publicKey, 'string');
    assert.match(publicKey, pkcs1PubExp);
    assertApproximateSize(publicKey, 180);

    assert.strictEqual(typeof privateKey, 'string');
    assert.match(privateKey, pkcs1PrivExp);
    assertApproximateSize(privateKey, 512);

    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));
}

{
  // Test invalid key types.
  for (const type of [undefined, null, 0]) {
    assert.throws(() => generateKeyPairSync(type, {}), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message: 'The "type" argument must be of type string.' +
               common.invalidArgTypeHelper(type)
    });
  }

  assert.throws(() => generateKeyPairSync('rsa2', {}), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_VALUE',
    message: "The argument 'type' must be a supported key type. Received 'rsa2'"
  });
}

{
  // Test keygen without options object.
  assert.throws(() => generateKeyPair('rsa', common.mustNotCall()), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message: 'The "options" argument must be of type object. ' +
      'Received undefined'
  });

  // Even if no options are required, it should be impossible to pass anything
  // but an object (or undefined).
  assert.throws(() => generateKeyPair('ed448', 0, common.mustNotCall()), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message: 'The "options" argument must be of type object. ' +
      'Received type number (0)'
  });
}

{
  // If no publicKeyEncoding is specified, a key object should be returned.
  generateKeyPair('rsa', {
    modulusLength: 1024,
    privateKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(typeof publicKey, 'object');
    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');

    // The private key should still be a string.
    assert.strictEqual(typeof privateKey, 'string');

    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));

  // If no privateKeyEncoding is specified, a key object should be returned.
  generateKeyPair('rsa', {
    modulusLength: 1024,
    publicKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    // The public key should still be a string.
    assert.strictEqual(typeof publicKey, 'string');

    assert.strictEqual(typeof privateKey, 'object');
    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');

    testEncryptDecrypt(publicKey, privateKey);
    testSignVerify(publicKey, privateKey);
  }));
}

{
  // Invalid publicKeyEncoding.
  for (const enc of [0, 'a', true]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: enc,
      privateKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.publicKeyEncoding' is invalid. " +
        `Received ${inspect(enc)}`
    });
  }

  // Missing publicKeyEncoding.type.
  for (const type of [undefined, null, 0, true, {}]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type,
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.publicKeyEncoding.type' is invalid. " +
        `Received ${inspect(type)}`
    });
  }

  // Missing / invalid publicKeyEncoding.format.
  for (const format of [undefined, null, 0, false, 'a', {}]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format
      },
      privateKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.publicKeyEncoding.format' is invalid. " +
        `Received ${inspect(format)}`
    });
  }

  // Invalid privateKeyEncoding.
  for (const enc of [0, 'a', true]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      },
      privateKeyEncoding: enc
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding' is invalid. " +
        `Received ${inspect(enc)}`
    });
  }

  // Missing / invalid privateKeyEncoding.type.
  for (const type of [undefined, null, 0, true, {}]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      },
      privateKeyEncoding: {
        type,
        format: 'pem'
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding.type' is invalid. " +
        `Received ${inspect(type)}`
    });
  }

  // Missing / invalid privateKeyEncoding.format.
  for (const format of [undefined, null, 0, false, 'a', {}]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs1',
        format
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding.format' is invalid. " +
        `Received ${inspect(format)}`
    });
  }

  // Cipher of invalid type.
  for (const cipher of [0, true, {}]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs1',
        format: 'pem',
        cipher
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding.cipher' is invalid. " +
        `Received ${inspect(cipher)}`
    });
  }

  // Invalid cipher.
  assert.throws(() => generateKeyPairSync('rsa', {
    modulusLength: 4096,
    publicKeyEncoding: {
      type: 'pkcs1',
      format: 'pem'
    },
    privateKeyEncoding: {
      type: 'pkcs8',
      format: 'pem',
      cipher: 'foo',
      passphrase: 'secret'
    }
  }), {
    name: 'Error',
    code: 'ERR_CRYPTO_UNKNOWN_CIPHER',
    message: 'Unknown cipher'
  });

  // Cipher, but no valid passphrase.
  for (const passphrase of [undefined, null, 5, false, true]) {
    assert.throws(() => generateKeyPairSync('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'pkcs1',
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem',
        cipher: 'aes-128-cbc',
        passphrase
      }
    }), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding.passphrase' " +
        `is invalid. Received ${inspect(passphrase)}`
    });
  }

  // Test invalid callbacks.
  for (const cb of [undefined, null, 0, {}]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength: 512,
      publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
      privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
    }, cb), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE'
    });
  }
}

// Test RSA parameters.
{
  // Test invalid modulus lengths. (non-number)
  for (const modulusLength of [undefined, null, 'a', true, {}, []]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
        'The "options.modulusLength" property must be of type number.' +
        common.invalidArgTypeHelper(modulusLength)
    });
  }

  // Test invalid modulus lengths. (non-integer)
  for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
      message:
        'The value of "options.modulusLength" is out of range. ' +
        'It must be an integer. ' +
        `Received ${inspect(modulusLength)}`
    });
  }

  // Test invalid modulus lengths. (out of range)
  for (const modulusLength of [-1, -9, 4294967297]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
    });
  }

  // Test invalid exponents. (non-number)
  for (const publicExponent of ['a', true, {}, []]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength: 4096,
      publicExponent
    }, common.mustNotCall()), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
        'The "options.publicExponent" property must be of type number.' +
        common.invalidArgTypeHelper(publicExponent)
    });
  }

  // Test invalid exponents. (non-integer)
  for (const publicExponent of [3.5, 1.1, 50.5, 510.5]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength: 4096,
      publicExponent
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
      message:
        'The value of "options.publicExponent" is out of range. ' +
        'It must be an integer. ' +
        `Received ${inspect(publicExponent)}`
    });
  }

  // Test invalid exponents. (out of range)
  for (const publicExponent of [-5, -3, 4294967297]) {
    assert.throws(() => generateKeyPair('rsa', {
      modulusLength: 4096,
      publicExponent
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
    });
  }

  // Test invalid exponents. (caught by OpenSSL)
  for (const publicExponent of [1, 1 + 0x10001]) {
    generateKeyPair('rsa', {
      modulusLength: 4096,
      publicExponent
    }, common.mustCall((err) => {
      assert.strictEqual(err.name, 'Error');
      assert.match(err.message, common.hasOpenSSL3 ? /exponent/ : /bad e value/);
    }));
  }
}

// Test DSA parameters.
{
  // Test invalid modulus lengths. (non-number)
  for (const modulusLength of [undefined, null, 'a', true, {}, []]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
        'The "options.modulusLength" property must be of type number.' +
        common.invalidArgTypeHelper(modulusLength)
    });
  }

  // Test invalid modulus lengths. (non-integer)
  for (const modulusLength of [512.1, 1.3, 1.1, 5000.9, 100.5]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
    });
  }

  // Test invalid modulus lengths. (out of range)
  for (const modulusLength of [-1, -9, 4294967297]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
    });
  }

  // Test invalid divisor lengths. (non-number)
  for (const divisorLength of ['a', true, {}, []]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength: 2048,
      divisorLength
    }, common.mustNotCall()), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
        'The "options.divisorLength" property must be of type number.' +
        common.invalidArgTypeHelper(divisorLength)
    });
  }

  // Test invalid divisor lengths. (non-integer)
  for (const divisorLength of [4096.1, 5.1, 6.9, 9.5]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength: 2048,
      divisorLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
      message:
        'The value of "options.divisorLength" is out of range. ' +
        'It must be an integer. ' +
        `Received ${inspect(divisorLength)}`
    });
  }

  // Test invalid divisor lengths. (out of range)
  for (const divisorLength of [-1, -6, -9, 2147483648]) {
    assert.throws(() => generateKeyPair('dsa', {
      modulusLength: 2048,
      divisorLength
    }, common.mustNotCall()), {
      name: 'RangeError',
      code: 'ERR_OUT_OF_RANGE',
      message:
        'The value of "options.divisorLength" is out of range. ' +
        'It must be >= 0 && <= 2147483647. ' +
        `Received ${inspect(divisorLength)}`
    });
  }
}

// Test EC parameters.
{
  // Test invalid curves.
  assert.throws(() => {
    generateKeyPairSync('ec', {
      namedCurve: 'abcdef',
      publicKeyEncoding: { type: 'spki', format: 'pem' },
      privateKeyEncoding: { type: 'sec1', format: 'pem' }
    });
  }, {
    name: 'TypeError',
    message: 'Invalid EC curve name'
  });

  // Test error type when curve is not a string
  for (const namedCurve of [true, {}, [], 123]) {
    assert.throws(() => {
      generateKeyPairSync('ec', {
        namedCurve,
        publicKeyEncoding: { type: 'spki', format: 'pem' },
        privateKeyEncoding: { type: 'sec1', format: 'pem' }
      });
    }, {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
        'The "options.namedCurve" property must be of type string.' +
        common.invalidArgTypeHelper(namedCurve)
    });
  }

  // It should recognize both NIST and standard curve names.
  generateKeyPair('ec', {
    namedCurve: 'P-256',
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
      namedCurve: 'prime256v1'
    });
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
      namedCurve: 'prime256v1'
    });
  }));

  generateKeyPair('ec', {
    namedCurve: 'secp256k1',
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
      namedCurve: 'secp256k1'
    });
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
      namedCurve: 'secp256k1'
    });
  }));
}

// Test EdDSA key generation.
{
  if (!/^1\.1\.0/.test(process.versions.openssl)) {
    ['ed25519', 'ed448', 'x25519', 'x448'].forEach((keyType) => {
      generateKeyPair(keyType, common.mustSucceed((publicKey, privateKey) => {
        assert.strictEqual(publicKey.type, 'public');
        assert.strictEqual(publicKey.asymmetricKeyType, keyType);
        assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {});

        assert.strictEqual(privateKey.type, 'private');
        assert.strictEqual(privateKey.asymmetricKeyType, keyType);
        assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {});
      }));
    });
  }
}

// Test classic Diffie-Hellman key generation.
{
  generateKeyPair('dh', {
    primeLength: 1024
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'dh');

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'dh');
  }));

  assert.throws(() => {
    generateKeyPair('dh', common.mustNotCall());
  }, {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message: 'The "options" argument must be of type object. Received undefined'
  });

  assert.throws(() => {
    generateKeyPair('dh', {}, common.mustNotCall());
  }, {
    name: 'TypeError',
    code: 'ERR_MISSING_OPTION',
    message: 'At least one of the group, prime, or primeLength options is ' +
             'required'
  });

  assert.throws(() => {
    generateKeyPair('dh', {
      group: 'modp0'
    }, common.mustNotCall());
  }, {
    name: 'Error',
    code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP',
    message: 'Unknown DH group'
  });

  assert.throws(() => {
    generateKeyPair('dh', {
      primeLength: 2147483648
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.primeLength" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received 2147483648',
  });

  assert.throws(() => {
    generateKeyPair('dh', {
      primeLength: -1
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.primeLength" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received -1',
  });

  assert.throws(() => {
    generateKeyPair('dh', {
      primeLength: 2,
      generator: 2147483648,
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.generator" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received 2147483648',
  });

  assert.throws(() => {
    generateKeyPair('dh', {
      primeLength: 2,
      generator: -1,
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.generator" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received -1',
  });

  // Test incompatible options.
  const allOpts = {
    group: 'modp5',
    prime: Buffer.alloc(0),
    primeLength: 1024,
    generator: 2
  };
  const incompatible = [
    ['group', 'prime'],
    ['group', 'primeLength'],
    ['group', 'generator'],
    ['prime', 'primeLength'],
  ];
  for (const [opt1, opt2] of incompatible) {
    assert.throws(() => {
      generateKeyPairSync('dh', {
        [opt1]: allOpts[opt1],
        [opt2]: allOpts[opt2]
      });
    }, {
      name: 'TypeError',
      code: 'ERR_INCOMPATIBLE_OPTION_PAIR',
      message: `Option "${opt1}" cannot be used in combination with option ` +
               `"${opt2}"`
    });
  }
}

// Test invalid key encoding types.
{
  // Invalid public key type.
  for (const type of ['foo', 'pkcs8', 'sec1']) {
    assert.throws(() => {
      generateKeyPairSync('rsa', {
        modulusLength: 4096,
        publicKeyEncoding: { type, format: 'pem' },
        privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
      });
    }, {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.publicKeyEncoding.type' is invalid. " +
        `Received ${inspect(type)}`
    });
  }

  // Invalid hash value.
  for (const hashValue of [123, true, {}, []]) {
    assert.throws(() => {
      generateKeyPairSync('rsa-pss', {
        modulusLength: 4096,
        hashAlgorithm: hashValue
      });
    }, {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message:
      'The "options.hashAlgorithm" property must be of type string.' +
        common.invalidArgTypeHelper(hashValue)
    });
  }

  // too long salt length
  assert.throws(() => {
    generateKeyPair('rsa-pss', {
      modulusLength: 512,
      saltLength: 2147483648,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256'
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.saltLength" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received 2147483648'
  });

  assert.throws(() => {
    generateKeyPair('rsa-pss', {
      modulusLength: 512,
      saltLength: -1,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256'
    }, common.mustNotCall());
  }, {
    name: 'RangeError',
    code: 'ERR_OUT_OF_RANGE',
    message: 'The value of "options.saltLength" is out of range. ' +
             'It must be >= 0 && <= 2147483647. ' +
             'Received -1'
  });

  // Invalid private key type.
  for (const type of ['foo', 'spki']) {
    assert.throws(() => {
      generateKeyPairSync('rsa', {
        modulusLength: 4096,
        publicKeyEncoding: { type: 'spki', format: 'pem' },
        privateKeyEncoding: { type, format: 'pem' }
      });
    }, {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_VALUE',
      message: "The property 'options.privateKeyEncoding.type' is invalid. " +
        `Received ${inspect(type)}`
    });
  }

  // Key encoding doesn't match key type.
  for (const type of ['dsa', 'ec']) {
    assert.throws(() => {
      generateKeyPairSync(type, {
        modulusLength: 4096,
        namedCurve: 'P-256',
        publicKeyEncoding: { type: 'pkcs1', format: 'pem' },
        privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
      });
    }, {
      name: 'Error',
      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
      message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
    });

    assert.throws(() => {
      generateKeyPairSync(type, {
        modulusLength: 4096,
        namedCurve: 'P-256',
        publicKeyEncoding: { type: 'spki', format: 'pem' },
        privateKeyEncoding: { type: 'pkcs1', format: 'pem' }
      });
    }, {
      name: 'Error',
      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
      message: 'The selected key encoding pkcs1 can only be used for RSA keys.'
    });
  }

  for (const type of ['rsa', 'dsa']) {
    assert.throws(() => {
      generateKeyPairSync(type, {
        modulusLength: 4096,
        publicKeyEncoding: { type: 'spki', format: 'pem' },
        privateKeyEncoding: { type: 'sec1', format: 'pem' }
      });
    }, {
      name: 'Error',
      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
      message: 'The selected key encoding sec1 can only be used for EC keys.'
    });
  }

  // Attempting to encrypt a DER-encoded, non-PKCS#8 key.
  for (const type of ['pkcs1', 'sec1']) {
    assert.throws(() => {
      generateKeyPairSync(type === 'pkcs1' ? 'rsa' : 'ec', {
        modulusLength: 4096,
        namedCurve: 'P-256',
        publicKeyEncoding: { type: 'spki', format: 'pem' },
        privateKeyEncoding: {
          type,
          format: 'der',
          cipher: 'aes-128-cbc',
          passphrase: 'hello'
        }
      });
    }, {
      name: 'Error',
      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS',
      message: `The selected key encoding ${type} does not support encryption.`
    });
  }
}

{
  // Test RSA-PSS.
  assert.throws(
    () => {
      generateKeyPair('rsa-pss', {
        modulusLength: 512,
        saltLength: 16,
        hashAlgorithm: 'sha256',
        mgf1HashAlgorithm: undefined
      });
    },
    {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
    }
  );

  for (const mgf1HashAlgorithm of [null, 0, false, {}, []]) {
    assert.throws(
      () => {
        generateKeyPair('rsa-pss', {
          modulusLength: 512,
          saltLength: 16,
          hashAlgorithm: 'sha256',
          mgf1HashAlgorithm
        }, common.mustNotCall());
      },
      {
        name: 'TypeError',
        code: 'ERR_INVALID_ARG_TYPE',
        message:
          'The "options.mgf1HashAlgorithm" property must be of type string.' +
          common.invalidArgTypeHelper(mgf1HashAlgorithm)

      }
    );
  }

  assert.throws(() => generateKeyPair('rsa-pss', {
    modulusLength: 512,
    hashAlgorithm: 'sha2',
  }, common.mustNotCall()), {
    name: 'TypeError',
    code: 'ERR_CRYPTO_INVALID_DIGEST',
    message: 'Invalid digest: sha2'
  });

  assert.throws(() => generateKeyPair('rsa-pss', {
    modulusLength: 512,
    mgf1HashAlgorithm: 'sha2',
  }, common.mustNotCall()), {
    name: 'TypeError',
    code: 'ERR_CRYPTO_INVALID_DIGEST',
    message: 'Invalid MGF1 digest: sha2'
  });
}

// Passing an empty passphrase string should not cause OpenSSL's default
// passphrase prompt in the terminal.
// See https://github.com/nodejs/node/issues/35898.

for (const type of ['pkcs1', 'pkcs8']) {
  generateKeyPair('rsa', {
    modulusLength: 1024,
    privateKeyEncoding: {
      type,
      format: 'pem',
      cipher: 'aes-256-cbc',
      passphrase: ''
    }
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(publicKey.type, 'public');

    for (const passphrase of ['', Buffer.alloc(0)]) {
      const privateKeyObject = createPrivateKey({
        passphrase,
        key: privateKey
      });
      assert.strictEqual(privateKeyObject.asymmetricKeyType, 'rsa');
    }

    // Encrypting with an empty passphrase is not the same as not encrypting
    // the key, and not specifying a passphrase should fail when decoding it.
    assert.throws(() => {
      return testSignVerify(publicKey, privateKey);
    }, common.hasOpenSSL3 ? {
      name: 'Error',
      code: 'ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED',
      message: 'error:07880109:common libcrypto routines::interrupted or cancelled'
    } : {
      name: 'TypeError',
      code: 'ERR_MISSING_PASSPHRASE',
      message: 'Passphrase required for encrypted key'
    });
  }));
}

// Passing an empty passphrase string should not throw ERR_OSSL_CRYPTO_MALLOC_FAILURE even on OpenSSL 3.
// Regression test for https://github.com/nodejs/node/issues/41428.
generateKeyPair('rsa', {
  modulusLength: 4096,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem',
    cipher: 'aes-256-cbc',
    passphrase: ''
  }
}, common.mustSucceed((publicKey, privateKey) => {
  assert.strictEqual(typeof publicKey, 'string');
  assert.strictEqual(typeof privateKey, 'string');
}));

{
  // This test creates EC key pairs on curves without associated OIDs.
  // Specifying a key encoding should not crash.

  if (process.versions.openssl >= '1.1.1i') {
    for (const namedCurve of ['Oakley-EC2N-3', 'Oakley-EC2N-4']) {
      if (!getCurves().includes(namedCurve))
        continue;

      const expectedErrorCode =
        common.hasOpenSSL3 ? 'ERR_OSSL_MISSING_OID' : 'ERR_OSSL_EC_MISSING_OID';
      const params = {
        namedCurve,
        publicKeyEncoding: {
          format: 'der',
          type: 'spki'
        }
      };

      assert.throws(() => {
        generateKeyPairSync('ec', params);
      }, {
        code: expectedErrorCode
      });

      generateKeyPair('ec', params, common.mustCall((err) => {
        assert.strictEqual(err.code, expectedErrorCode);
      }));
    }
  }
}

{
  // This test makes sure deprecated and new options may be used
  // simultaneously so long as they're identical values.

  generateKeyPair('rsa-pss', {
    modulusLength: 512,
    saltLength: 16,
    hash: 'sha256',
    hashAlgorithm: 'sha256',
    mgf1Hash: 'sha256',
    mgf1HashAlgorithm: 'sha256'
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 16
    });

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, {
      modulusLength: 512,
      publicExponent: 65537n,
      hashAlgorithm: 'sha256',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 16
    });
  }));
}

{
  // This test makes sure deprecated and new options must
  // be the same value.

  assert.throws(() => generateKeyPair('rsa-pss', {
    modulusLength: 512,
    saltLength: 16,
    mgf1Hash: 'sha256',
    mgf1HashAlgorithm: 'sha1'
  }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' });

  assert.throws(() => generateKeyPair('rsa-pss', {
    modulusLength: 512,
    saltLength: 16,
    hash: 'sha256',
    hashAlgorithm: 'sha1'
  }, common.mustNotCall()), { code: 'ERR_INVALID_ARG_VALUE' });
}

{
  // https://github.com/nodejs/node/issues/46102#issuecomment-1372153541

  generateKeyPair('rsa', {
    modulusLength: 513,
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513);
    assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513);
  }));

  generateKeyPair('rsa-pss', {
    modulusLength: 513,
  }, common.mustSucceed((publicKey, privateKey) => {
    assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 513);
    assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 513);
  }));

  if (common.hasOpenSSL3) {
    generateKeyPair('dsa', {
      modulusLength: 2049,
      divisorLength: 256,
    }, common.mustSucceed((publicKey, privateKey) => {
      assert.strictEqual(privateKey.asymmetricKeyDetails.modulusLength, 2049);
      assert.strictEqual(publicKey.asymmetricKeyDetails.modulusLength, 2049);
    }));
  }
}
