'use strict';

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

const assert = require('assert');
const {
  createCipheriv,
  createDecipheriv,
  createSign,
  createVerify,
  createSecretKey,
  createPublicKey,
  createPrivateKey,
  KeyObject,
  randomBytes,
  publicDecrypt,
  publicEncrypt,
  privateDecrypt,
  privateEncrypt,
  getCurves,
  generateKeySync,
  generateKeyPairSync,
} = require('crypto');

const { hasOpenSSL3 } = require('../common/crypto');

const fixtures = require('../common/fixtures');

const publicPem = fixtures.readKey('rsa_public.pem', 'ascii');
const privatePem = fixtures.readKey('rsa_private.pem', 'ascii');

const publicDsa = fixtures.readKey('dsa_public_1025.pem', 'ascii');
const privateDsa = fixtures.readKey('dsa_private_encrypted_1025.pem',
                                    'ascii');

{
  // Attempting to create a key of a wrong type should throw
  const TYPE = 'wrong_type';

  assert.throws(() => new KeyObject(TYPE), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_VALUE',
    message: `The argument 'type' is invalid. Received '${TYPE}'`
  });
}

{
  // Attempting to create a key with non-object handle should throw
  assert.throws(() => new KeyObject('secret', ''), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message:
      'The "handle" argument must be of type object. Received type ' +
      "string ('')"
  });
}

{
  assert.throws(() => KeyObject.from('invalid_key'), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message:
      'The "key" argument must be an instance of CryptoKey. Received type ' +
      "string ('invalid_key')"
  });
}

{
  const keybuf = randomBytes(32);
  const key = createSecretKey(keybuf);
  assert.strictEqual(key.type, 'secret');
  assert.strictEqual(key.toString(), '[object KeyObject]');
  assert.strictEqual(key.symmetricKeySize, 32);
  assert.strictEqual(key.asymmetricKeyType, undefined);
  assert.strictEqual(key.asymmetricKeyDetails, undefined);

  const exportedKey = key.export();
  assert(keybuf.equals(exportedKey));

  const plaintext = Buffer.from('Hello world', 'utf8');

  const cipher = createCipheriv('aes-256-ecb', key, null);
  const ciphertext = Buffer.concat([
    cipher.update(plaintext), cipher.final(),
  ]);

  const decipher = createDecipheriv('aes-256-ecb', key, null);
  const deciphered = Buffer.concat([
    decipher.update(ciphertext), decipher.final(),
  ]);

  assert(plaintext.equals(deciphered));
}

{
  // Passing an existing public key object to createPublicKey should throw.
  const publicKey = createPublicKey(publicPem);
  assert.throws(() => createPublicKey(publicKey), {
    name: 'TypeError',
    code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE',
    message: 'Invalid key object type public, expected private.'
  });

  // Constructing a private key from a public key should be impossible, even
  // if the public key was derived from a private key.
  assert.throws(() => createPrivateKey(createPublicKey(privatePem)), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
  });

  // Similarly, passing an existing private key object to createPrivateKey
  // should throw.
  const privateKey = createPrivateKey(privatePem);
  assert.throws(() => createPrivateKey(privateKey), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
  });
}

{
  const jwk = {
    e: 'AQAB',
    n: 't9xYiIonscC3vz_A2ceR7KhZZlDu_5bye53nCVTcKnWd2seY6UAdKersX6njr83Dd5OVe' +
       '1BW_wJvp5EjWTAGYbFswlNmeD44edEGM939B6Lq-_8iBkrTi8mGN4YCytivE24YI0D4XZ' +
       'MPfkLSpab2y_Hy4DjQKBq1ThZ0UBnK-9IhX37Ju_ZoGYSlTIGIhzyaiYBh7wrZBoPczIE' +
       'u6et_kN2VnnbRUtkYTF97ggcv5h-hDpUQjQW0ZgOMcTc8n-RkGpIt0_iM_bTjI3Tz_gsF' +
       'di6hHcpZgbopPL630296iByyigQCPJVzdusFrQN5DeC-zT_nGypQkZanLb4ZspSx9Q',
    d: 'ktnq2LvIMqBj4txP82IEOorIRQGVsw1khbm8A-cEpuEkgM71Yi_0WzupKktucUeevQ5i0' +
       'Yh8w9e1SJiTLDRAlJz66kdky9uejiWWl6zR4dyNZVMFYRM43ijLC-P8rPne9Fz16IqHFW' +
       '5VbJqA1xCBhKmuPMsD71RNxZ4Hrsa7Kt_xglQTYsLbdGIwDmcZihId9VGXRzvmCPsDRf2' +
       'fCkAj7HDeRxpUdEiEDpajADc-PWikra3r3b40tVHKWm8wxJLivOIN7GiYXKQIW6RhZgH-' +
       'Rk45JIRNKxNagxdeXUqqyhnwhbTo1Hite0iBDexN9tgoZk0XmdYWBn6ElXHRZ7VCDQ',
    p: '8UovlB4nrBm7xH-u7XXBMbqxADQm5vaEZxw9eluc-tP7cIAI4sglMIvL_FMpbd2pEeP_B' +
       'kR76NTDzzDuPAZvUGRavgEjy0O9j2NAs_WPK4tZF-vFdunhnSh4EHAF4Ij9kbsUi90NOp' +
       'bGfVqPdOaHqzgHKoR23Cuusk9wFQ2XTV8',
    q: 'wxHdEYT9xrpfrHPqSBQPpO0dWGKJEkrWOb-76rSfuL8wGR4OBNmQdhLuU9zTIh22pog-X' +
       'PnLPAecC-4yu_wtJ2SPCKiKDbJBre0CKPyRfGqzvA3njXwMxXazU4kGs-2Fg-xu_iKbaI' +
       'jxXrclBLhkxhBtySrwAFhxxOk6fFcPLSs',
    dp: 'qS_Mdr5CMRGGMH0bKhPUWEtAixUGZhJaunX5wY71Xoc_Gh4cnO-b7BNJ_-5L8WZog0vr' +
       '6PgiLhrqBaCYm2wjpyoG2o2wDHm-NAlzN_wp3G2EFhrSxdOux-S1c0kpRcyoiAO2n29rN' +
       'Da-jOzwBBcU8ACEPdLOCQl0IEFFJO33tl8',
    dq: 'WAziKpxLKL7LnL4dzDcx8JIPIuwnTxh0plCDdCffyLaT8WJ9lXbXHFTjOvt8WfPrlDP_' +
       'Ylxmfkw5BbGZOP1VLGjZn2DkH9aMiwNmbDXFPdG0G3hzQovx_9fajiRV4DWghLHeT9wzJ' +
       'fZabRRiI0VQR472300AVEeX4vgbrDBn600',
    qi: 'k7czBCT9rHn_PNwCa17hlTy88C4vXkwbz83Oa-aX5L4e5gw5lhcR2ZuZHLb2r6oMt9rl' +
       'D7EIDItSs-u21LOXWPTAlazdnpYUyw_CzogM_PN-qNwMRXn5uXFFhmlP2mVg2EdELTahX' +
       'ch8kWqHaCSX53yvqCtRKu_j76V31TfQZGM',
    kty: 'RSA',
  };
  const publicJwk = { kty: jwk.kty, e: jwk.e, n: jwk.n };

  const publicKey = createPublicKey(publicPem);
  assert.strictEqual(publicKey.type, 'public');
  assert.strictEqual(publicKey.toString(), '[object KeyObject]');
  assert.strictEqual(publicKey.asymmetricKeyType, 'rsa');
  assert.strictEqual(publicKey.symmetricKeySize, undefined);

  const privateKey = createPrivateKey(privatePem);
  assert.strictEqual(privateKey.type, 'private');
  assert.strictEqual(privateKey.toString(), '[object KeyObject]');
  assert.strictEqual(privateKey.asymmetricKeyType, 'rsa');
  assert.strictEqual(privateKey.symmetricKeySize, undefined);

  // It should be possible to derive a public key from a private key.
  const derivedPublicKey = createPublicKey(privateKey);
  assert.strictEqual(derivedPublicKey.type, 'public');
  assert.strictEqual(derivedPublicKey.toString(), '[object KeyObject]');
  assert.strictEqual(derivedPublicKey.asymmetricKeyType, 'rsa');
  assert.strictEqual(derivedPublicKey.symmetricKeySize, undefined);

  // The private key should not be extractable from the derived public key.
  assert.throws(() => derivedPublicKey.export({ format: 'pem', type: 'pkcs8' }),
                { code: 'ERR_INVALID_ARG_VALUE' });
  assert.throws(() => derivedPublicKey.export({ format: 'der', type: 'pkcs8' }),
                { code: 'ERR_INVALID_ARG_VALUE' });
  // JWK export should only contain public components, no 'd'.
  {
    const jwkExport = derivedPublicKey.export({ format: 'jwk' });
    assert.strictEqual(jwkExport.kty, 'RSA');
    assert.strictEqual(jwkExport.d, undefined);
    assert.strictEqual(jwkExport.dp, undefined);
    assert.strictEqual(jwkExport.dq, undefined);
    assert.strictEqual(jwkExport.qi, undefined);
    assert.strictEqual(jwkExport.p, undefined);
    assert.strictEqual(jwkExport.q, undefined);
  }

  const publicKeyFromJwk = createPublicKey({ key: publicJwk, format: 'jwk' });
  assert.strictEqual(publicKeyFromJwk.type, 'public');
  assert.strictEqual(publicKeyFromJwk.toString(), '[object KeyObject]');
  assert.strictEqual(publicKeyFromJwk.asymmetricKeyType, 'rsa');
  assert.strictEqual(publicKeyFromJwk.symmetricKeySize, undefined);

  const privateKeyFromJwk = createPrivateKey({ key: jwk, format: 'jwk' });
  assert.strictEqual(privateKeyFromJwk.type, 'private');
  assert.strictEqual(privateKeyFromJwk.toString(), '[object KeyObject]');
  assert.strictEqual(privateKeyFromJwk.asymmetricKeyType, 'rsa');
  assert.strictEqual(privateKeyFromJwk.symmetricKeySize, undefined);

  // It should also be possible to import an encrypted private key as a public
  // key.
  const decryptedKey = createPublicKey({
    key: privateKey.export({
      type: 'pkcs8',
      format: 'pem',
      passphrase: '123',
      cipher: 'aes-128-cbc'
    }),
    format: 'pem',
    passphrase: '123'
  });
  assert.strictEqual(decryptedKey.type, 'public');
  assert.strictEqual(decryptedKey.asymmetricKeyType, 'rsa');

  // Test exporting with an invalid options object, this should throw.
  for (const opt of [undefined, null, 'foo', 0, NaN]) {
    assert.throws(() => publicKey.export(opt), {
      name: 'TypeError',
      code: 'ERR_INVALID_ARG_TYPE',
      message: /^The "options" argument must be of type object/
    });
  }

  for (const keyObject of [publicKey, derivedPublicKey, publicKeyFromJwk]) {
    assert.deepStrictEqual(
      keyObject.export({ format: 'jwk' }),
      { kty: 'RSA', n: jwk.n, e: jwk.e }
    );
  }

  for (const keyObject of [privateKey, privateKeyFromJwk]) {
    assert.deepStrictEqual(
      keyObject.export({ format: 'jwk' }),
      jwk
    );
  }

  // Exporting the key using JWK should not work since this format does not
  // support key encryption
  assert.throws(() => {
    privateKey.export({ format: 'jwk', passphrase: 'secret' });
  }, {
    message: 'The selected key encoding jwk does not support encryption.',
    code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
  });

  // Importing an RSA private JWK where n does not equal p * q should fail.
  assert.throws(
    () => createPrivateKey({ key: { ...jwk, n: `A${publicJwk.n.slice(1)}` }, format: 'jwk' }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing a public-only RSA JWK as a private key should fail.
  assert.throws(
    () => createPrivateKey({ key: publicJwk, format: 'jwk' }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  const publicDER = publicKey.export({
    format: 'der',
    type: 'pkcs1'
  });

  const privateDER = privateKey.export({
    format: 'der',
    type: 'pkcs1'
  });

  assert(Buffer.isBuffer(publicDER));
  assert(Buffer.isBuffer(privateDER));

  const plaintext = Buffer.from('Hello world', 'utf8');
  const testDecryption = common.mustCall((fn, ciphertexts, decryptionKeys) => {
    for (const ciphertext of ciphertexts) {
      for (const key of decryptionKeys) {
        const deciphered = fn(key, ciphertext);
        assert.deepStrictEqual(deciphered, plaintext);
      }
    }
  }, 2);

  testDecryption(privateDecrypt, [
    // Encrypt using the public key.
    publicEncrypt(publicKey, plaintext),
    publicEncrypt({ key: publicKey }, plaintext),
    publicEncrypt({ key: publicJwk, format: 'jwk' }, plaintext),

    // Encrypt using the private key.
    publicEncrypt(privateKey, plaintext),
    publicEncrypt({ key: privateKey }, plaintext),
    publicEncrypt({ key: jwk, format: 'jwk' }, plaintext),

    // Encrypt using a public key derived from the private key.
    publicEncrypt(derivedPublicKey, plaintext),
    publicEncrypt({ key: derivedPublicKey }, plaintext),

    // Test distinguishing PKCS#1 public and private keys based on the
    // DER-encoded data only.
    publicEncrypt({ format: 'der', type: 'pkcs1', key: publicDER }, plaintext),
    publicEncrypt({ format: 'der', type: 'pkcs1', key: privateDER }, plaintext),
  ], [
    privateKey,
    { format: 'pem', key: privatePem },
    { format: 'der', type: 'pkcs1', key: privateDER },
    { key: jwk, format: 'jwk' },
  ]);

  testDecryption(publicDecrypt, [
    privateEncrypt(privateKey, plaintext),
  ], [
    // Decrypt using the public key.
    publicKey,
    { format: 'pem', key: publicPem },
    { format: 'der', type: 'pkcs1', key: publicDER },
    { key: publicJwk, format: 'jwk' },

    // Decrypt using the private key.
    privateKey,
    { format: 'pem', key: privatePem },
    { format: 'der', type: 'pkcs1', key: privateDER },
    { key: jwk, format: 'jwk' },
  ]);
}

{
  // This should not cause a crash: https://github.com/nodejs/node/issues/25247
  assert.throws(() => {
    createPrivateKey({ key: '' });
  }, hasOpenSSL3 ? {
    message: 'error:1E08010C:DECODER routines::unsupported',
  } : process.features.openssl_is_boringssl ? {
    message: 'error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE',
    code: 'ERR_OSSL_PEM_NO_START_LINE',
    reason: 'NO_START_LINE',
    library: 'PEM routines',
    function: 'OPENSSL_internal',
  } : {
    message: 'error:0909006C:PEM routines:get_name:no start line',
    code: 'ERR_OSSL_PEM_NO_START_LINE',
    reason: 'no start line',
    library: 'PEM routines',
    function: 'get_name',
  });

  // This should not abort either: https://github.com/nodejs/node/issues/29904
  assert.throws(() => {
    createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'spki' });
  }, {
    code: 'ERR_INVALID_ARG_VALUE',
    message: "The property 'key.type' is invalid. Received 'spki'"
  });

  // Unlike SPKI, PKCS#1 is a valid encoding for private keys (and public keys),
  // so it should be accepted by createPrivateKey, but OpenSSL won't parse it.
  assert.throws(() => {
    const key = createPublicKey(publicPem).export({
      format: 'der',
      type: 'pkcs1'
    });
    createPrivateKey({ key, format: 'der', type: 'pkcs1' });
  }, hasOpenSSL3 ? {
    message: /error:1E08010C:DECODER routines::unsupported/,
    library: 'DECODER routines'
  } : process.features.openssl_is_boringssl ? {
    library: 'public key routines',
    message: 'error:06000066:public key routines:OPENSSL_internal:DECODE_ERROR'
  } : {
    message: /asn1 encoding/,
    library: 'asn1 encoding routines'
  });
}

for (const info of [
  { private: fixtures.readKey('ed25519_private.pem', 'ascii'),
    public: fixtures.readKey('ed25519_public.pem', 'ascii'),
    keyType: 'ed25519',
    jwk: {
      crv: 'Ed25519',
      x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',
      d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',
      kty: 'OKP'
    } },
  { private: fixtures.readKey('ed448_private.pem', 'ascii'),
    public: fixtures.readKey('ed448_public.pem', 'ascii'),
    keyType: 'ed448',
    jwk: {
      crv: 'Ed448',
      x: 'oX_ee5-jlcU53-BbGRsGIzly0V-SZtJ_oGXY0udf84q2hTW2RdstLktvwpkVJOoNb7o' +
         'Dgc2V5ZUA',
      d: '060Ke71sN0GpIc01nnGgMDkp0sFNQ09woVo4AM1ffax1-mjnakK0-p-S7-Xf859QewX' +
         'jcR9mxppY',
      kty: 'OKP'
    } },
  { private: fixtures.readKey('x25519_private.pem', 'ascii'),
    public: fixtures.readKey('x25519_public.pem', 'ascii'),
    keyType: 'x25519',
    jwk: {
      crv: 'X25519',
      x: 'aSb8Q-RndwfNnPeOYGYPDUN3uhAPnMLzXyfi-mqfhig',
      d: 'mL_IWm55RrALUGRfJYzw40gEYWMvtRkesP9mj8o8Omc',
      kty: 'OKP'
    } },
  { private: fixtures.readKey('x448_private.pem', 'ascii'),
    public: fixtures.readKey('x448_public.pem', 'ascii'),
    keyType: 'x448',
    jwk: {
      crv: 'X448',
      x: 'ioHSHVpTs6hMvghosEJDIR7ceFiE3-Xccxati64oOVJ7NWjfozE7ae31PXIUFq6cVYg' +
         'vSKsDFPA',
      d: 'tMNtrO_q8dlY6Y4NDeSTxNQ5CACkHiPvmukidPnNIuX_EkcryLEXt_7i6j6YZMKsrWy' +
         'S0jlSYJk',
      kty: 'OKP'
    } },
]) {
  const keyType = info.keyType;

  if (process.features.openssl_is_boringssl && keyType.endsWith('448')) {
    common.printSkipMessage(`Skipping unsupported ${keyType} test case`);
    continue;
  }

  {
    const key = createPrivateKey(info.private);
    assert.strictEqual(key.type, 'private');
    assert.strictEqual(key.asymmetricKeyType, keyType);
    assert.strictEqual(key.symmetricKeySize, undefined);
    assert.strictEqual(
      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
    assert.deepStrictEqual(
      key.export({ format: 'jwk' }), info.jwk);
  }

  {
    const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
    assert.strictEqual(key.type, 'private');
    assert.strictEqual(key.asymmetricKeyType, keyType);
    assert.strictEqual(key.symmetricKeySize, undefined);
    assert.strictEqual(
      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
    assert.deepStrictEqual(
      key.export({ format: 'jwk' }), info.jwk);
  }

  {
    for (const input of [
      info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
      const key = createPublicKey(input);
      assert.strictEqual(key.type, 'public');
      assert.strictEqual(key.asymmetricKeyType, keyType);
      assert.strictEqual(key.symmetricKeySize, undefined);
      assert.strictEqual(
        key.export({ type: 'spki', format: 'pem' }), info.public);
      const jwk = { ...info.jwk };
      delete jwk.d;
      assert.deepStrictEqual(
        key.export({ format: 'jwk' }), jwk);
    }
  }

  // Raw format round-trip
  {
    const privKey = createPrivateKey(info.private);
    const pubKey = createPublicKey(info.public);

    const rawPriv = privKey.export({ format: 'raw-private' });
    const rawPub = pubKey.export({ format: 'raw-public' });
    assert(Buffer.isBuffer(rawPriv));
    assert(Buffer.isBuffer(rawPub));

    const importedPriv = createPrivateKey({
      key: rawPriv, format: 'raw-private', asymmetricKeyType: keyType,
    });
    assert.strictEqual(importedPriv.type, 'private');
    assert.strictEqual(importedPriv.asymmetricKeyType, keyType);
    assert.deepStrictEqual(
      importedPriv.export({ format: 'raw-private' }), rawPriv);

    const importedPub = createPublicKey({
      key: rawPub, format: 'raw-public', asymmetricKeyType: keyType,
    });
    assert.strictEqual(importedPub.type, 'public');
    assert.strictEqual(importedPub.asymmetricKeyType, keyType);
    assert.deepStrictEqual(
      importedPub.export({ format: 'raw-public' }), rawPub);
  }
}

// Importing an OKP private JWK where x does not match d should fail.
{
  const okpJwk = {
    crv: 'Ed25519',
    x: 'K1wIouqnuiA04b3WrMa-xKIKIpfHetNZRv3h9fBf768',
    d: 'wVK6M3SMhQh3NK-7GRrSV-BVWQx1FO5pW8hhQeu_NdA',
    kty: 'OKP'
  };

  assert.throws(
    () => createPrivateKey({
      key: { ...okpJwk, x: `A${okpJwk.x.slice(1)}` },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing a public-only OKP JWK as a private key should fail.
  assert.throws(
    () => createPrivateKey({
      key: { kty: okpJwk.kty, crv: okpJwk.crv, x: okpJwk.x },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing an OKP JWK with missing crv should fail.
  assert.throws(
    () => createPublicKey({
      key: { kty: okpJwk.kty, x: okpJwk.x },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing an OKP JWK with invalid crv should fail.
  assert.throws(
    () => createPublicKey({
      key: { ...okpJwk, crv: 'invalid' },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );
}

for (const info of [
  { private: fixtures.readKey('ec_p256_private.pem', 'ascii'),
    public: fixtures.readKey('ec_p256_public.pem', 'ascii'),
    keyType: 'ec',
    namedCurve: 'prime256v1',
    jwk: {
      crv: 'P-256',
      d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',
      kty: 'EC',
      x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',
      y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI'
    } },
  { private: fixtures.readKey('ec_secp256k1_private.pem', 'ascii'),
    public: fixtures.readKey('ec_secp256k1_public.pem', 'ascii'),
    keyType: 'ec',
    namedCurve: 'secp256k1',
    jwk: {
      crv: 'secp256k1',
      d: 'c34ocwTwpFa9NZZh3l88qXyrkoYSxvC0FEsU5v1v4IM',
      kty: 'EC',
      x: 'cOzhFSpWxhalCbWNdP2H_yUkdC81C9T2deDpfxK7owA',
      y: '-A3DAZTk9IPppN-f03JydgHaFvL1fAHaoXf4SX4NXyo'
    } },
  { private: fixtures.readKey('ec_p384_private.pem', 'ascii'),
    public: fixtures.readKey('ec_p384_public.pem', 'ascii'),
    keyType: 'ec',
    namedCurve: 'secp384r1',
    jwk: {
      crv: 'P-384',
      d: 'dwfuHuAtTlMRn7ZBCBm_0grpc1D_4hPeNAgevgelljuC0--k_LDFosDgBlLLmZsi',
      kty: 'EC',
      x: 'hON3nzGJgv-08fdHpQxgRJFZzlK-GZDGa5f3KnvM31cvvjJmsj4UeOgIdy3rDAjV',
      y: 'fidHhtecNCGCfLqmrLjDena1NSzWzWH1u_oUdMKGo5XSabxzD7-8JZxjpc8sR9cl'
    } },
  { private: fixtures.readKey('ec_p521_private.pem', 'ascii'),
    public: fixtures.readKey('ec_p521_public.pem', 'ascii'),
    keyType: 'ec',
    namedCurve: 'secp521r1',
    jwk: {
      crv: 'P-521',
      d: 'ABIIbmn3Gm_Y11uIDkC3g2ijpRxIrJEBY4i_JJYo5OougzTl3BX2ifRluPJMaaHcNer' +
         'bQH_WdVkLLX86ShlHrRyJ',
      kty: 'EC',
      x: 'AaLFgjwZtznM3N7qsfb86awVXe6c6djUYOob1FN-kllekv0KEXV0bwcDjPGQz5f6MxL' +
         'CbhMeHRavUS6P10rsTtBn',
      y: 'Ad3flexBeAfXceNzRBH128kFbOWD6W41NjwKRqqIF26vmgW_8COldGKZjFkOSEASxPB' +
         'cvA2iFJRUyQ3whC00j0Np'
    } },
]) {
  const { keyType, namedCurve } = info;

  if (process.features.openssl_is_boringssl && !getCurves().includes(namedCurve)) {
    common.printSkipMessage(`Skipping unsupported ${keyType} test case`);
    continue;
  }

  {
    const key = createPrivateKey(info.private);
    assert.strictEqual(key.type, 'private');
    assert.strictEqual(key.asymmetricKeyType, keyType);
    assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
    assert.strictEqual(key.symmetricKeySize, undefined);
    assert.strictEqual(
      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
    assert.deepStrictEqual(
      key.export({ format: 'jwk' }), info.jwk);
  }

  {
    const key = createPrivateKey({ key: info.jwk, format: 'jwk' });
    assert.strictEqual(key.type, 'private');
    assert.strictEqual(key.asymmetricKeyType, keyType);
    assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
    assert.strictEqual(key.symmetricKeySize, undefined);
    assert.strictEqual(
      key.export({ type: 'pkcs8', format: 'pem' }), info.private);
    assert.deepStrictEqual(
      key.export({ format: 'jwk' }), info.jwk);
  }

  {
    for (const input of [
      info.private, info.public, { key: info.jwk, format: 'jwk' }]) {
      const key = createPublicKey(input);
      assert.strictEqual(key.type, 'public');
      assert.strictEqual(key.asymmetricKeyType, keyType);
      assert.deepStrictEqual(key.asymmetricKeyDetails, { namedCurve });
      assert.strictEqual(key.symmetricKeySize, undefined);
      assert.strictEqual(
        key.export({ type: 'spki', format: 'pem' }), info.public);
      const jwk = { ...info.jwk };
      delete jwk.d;
      assert.deepStrictEqual(
        key.export({ format: 'jwk' }), jwk);

      // Private key material must not be extractable from a derived public key.
      assert.throws(() => key.export({ format: 'pem', type: 'pkcs8' }),
                    { code: 'ERR_INVALID_ARG_VALUE' });
      assert.throws(() => key.export({ format: 'pem', type: 'sec1' }),
                    { code: 'ERR_INVALID_ARG_VALUE' });
      assert.throws(() => key.export({ format: 'der', type: 'pkcs8' }),
                    { code: 'ERR_INVALID_ARG_VALUE' });
      assert.throws(() => key.export({ format: 'der', type: 'sec1' }),
                    { code: 'ERR_INVALID_ARG_VALUE' });
    }
  }

  // Raw format round-trip
  {
    const privKey = createPrivateKey(info.private);
    const pubKey = createPublicKey(info.public);

    const rawPriv = privKey.export({ format: 'raw-private' });
    const rawPub = pubKey.export({ format: 'raw-public' });
    assert(Buffer.isBuffer(rawPriv));
    assert(Buffer.isBuffer(rawPub));

    const importedPriv = createPrivateKey({
      key: rawPriv, format: 'raw-private',
      asymmetricKeyType: keyType, namedCurve,
    });
    assert.strictEqual(importedPriv.type, 'private');
    assert.strictEqual(importedPriv.asymmetricKeyType, keyType);
    assert.deepStrictEqual(
      importedPriv.export({ format: 'raw-private' }), rawPriv);

    const importedPub = createPublicKey({
      key: rawPub, format: 'raw-public',
      asymmetricKeyType: keyType, namedCurve,
    });
    assert.strictEqual(importedPub.type, 'public');
    assert.strictEqual(importedPub.asymmetricKeyType, keyType);
    assert.deepStrictEqual(
      importedPub.export({ format: 'raw-public' }), rawPub);
  }
}

// Importing an EC private JWK where x does not match d should fail.
{
  const ecJwk = {
    crv: 'P-256',
    d: 'DxBsPQPIgMuMyQbxzbb9toew6Ev6e9O6ZhpxLNgmAEo',
    kty: 'EC',
    x: 'X0mMYR_uleZSIPjNztIkAS3_ud5LhNpbiIFp6fNf2Gs',
    y: 'UbJuPy2Xi0lW7UYTBxPK3yGgDu9EAKYIecjkHX5s2lI'
  };

  assert.throws(
    () => createPrivateKey({
      key: { ...ecJwk, x: `A${ecJwk.x.slice(1)}` },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing a public-only EC JWK as a private key should fail.
  assert.throws(
    () => createPrivateKey({
      key: { kty: ecJwk.kty, crv: ecJwk.crv, x: ecJwk.x, y: ecJwk.y },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing an EC JWK with missing crv should fail.
  assert.throws(
    () => createPublicKey({
      key: { kty: ecJwk.kty, x: ecJwk.x, y: ecJwk.y },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_JWK' }
  );

  // Importing an EC JWK with invalid crv should fail.
  assert.throws(
    () => createPublicKey({
      key: { ...ecJwk, crv: 'invalid' },
      format: 'jwk',
    }),
    { code: 'ERR_CRYPTO_INVALID_CURVE' }
  );
}

{
  // Reading an encrypted key without a passphrase should fail.
  assert.throws(() => createPrivateKey(privateDsa), hasOpenSSL3 ? {
    name: 'Error',
    message: 'error:07880109:common libcrypto routines::interrupted or ' +
             'cancelled',
  } : {
    name: 'TypeError',
    code: 'ERR_MISSING_PASSPHRASE',
    message: 'Passphrase required for encrypted key'
  });

  // Reading an encrypted key with a passphrase that exceeds OpenSSL's buffer
  // size limit should fail with an appropriate error code.
  assert.throws(() => createPrivateKey({
    key: privateDsa,
    format: 'pem',
    passphrase: Buffer.alloc(1025, 'a')
  }), hasOpenSSL3 ? { name: 'Error' } : {
    code: 'ERR_OSSL_PEM_BAD_PASSWORD_READ',
    name: 'Error'
  });

  // The buffer has a size of 1024 bytes, so this passphrase should be permitted
  // (but will fail decryption).
  assert.throws(() => createPrivateKey({
    key: privateDsa,
    format: 'pem',
    passphrase: Buffer.alloc(1024, 'a')
  }), {
    message: /bad decrypt|BAD_DECRYPT/
  });

  const publicKey = createPublicKey(publicDsa);
  assert.strictEqual(publicKey.type, 'public');
  assert.strictEqual(publicKey.asymmetricKeyType, 'dsa');
  assert.strictEqual(publicKey.symmetricKeySize, undefined);
  assert.throws(
    () => publicKey.export({ format: 'jwk' }),
    { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });

  const privateKey = createPrivateKey({
    key: privateDsa,
    format: 'pem',
    passphrase: 'secret'
  });
  assert.strictEqual(privateKey.type, 'private');
  assert.strictEqual(privateKey.asymmetricKeyType, 'dsa');
  assert.strictEqual(privateKey.symmetricKeySize, undefined);
  assert.throws(
    () => privateKey.export({ format: 'jwk' }),
    { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
}

if (!process.features.openssl_is_boringssl) {
  // Test RSA-PSS.
  {
    // This key pair does not restrict the message digest algorithm or salt
    // length.
    const publicPem = fixtures.readKey('rsa_pss_public_2048.pem');
    const privatePem = fixtures.readKey('rsa_pss_private_2048.pem');

    const publicKey = createPublicKey(publicPem);
    const privateKey = createPrivateKey(privatePem);

    // Because no RSASSA-PSS-params appears in the PEM, no defaults should be
    // added for the PSS parameters. This is different from an empty
    // RSASSA-PSS-params sequence (see test below).
    const expectedKeyDetails = {
      modulusLength: 2048,
      publicExponent: 65537n
    };

    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);

    assert.throws(
      () => publicKey.export({ format: 'jwk' }),
      { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });
    assert.throws(
      () => privateKey.export({ format: 'jwk' }),
      { code: 'ERR_CRYPTO_JWK_UNSUPPORTED_KEY_TYPE' });

    for (const key of [privatePem, privateKey]) {
      // Any algorithm should work.
      for (const algo of ['sha1', 'sha256']) {
        // Any salt length should work.
        for (const saltLength of [undefined, 8, 10, 12, 16, 18, 20]) {
          const signature = createSign(algo)
                            .update('foo')
                            .sign({ key, saltLength });

          for (const pkey of [key, publicKey, publicPem]) {
            const okay = createVerify(algo)
                         .update('foo')
                         .verify({ key: pkey, saltLength }, signature);

            assert.ok(okay);
          }
        }
      }
    }

    // Exporting the key using PKCS#1 should not work since this would discard
    // any algorithm restrictions.
    assert.throws(() => {
      publicKey.export({ format: 'pem', type: 'pkcs1' });
    }, {
      code: 'ERR_CRYPTO_INCOMPATIBLE_KEY_OPTIONS'
    });
  }

  {
    // This key pair enforces sha1 as the message digest and the MGF1
    // message digest and a salt length of 20 bytes.

    const publicPem = fixtures.readKey('rsa_pss_public_2048_sha1_sha1_20.pem');
    const privatePem =
        fixtures.readKey('rsa_pss_private_2048_sha1_sha1_20.pem');

    const publicKey = createPublicKey(publicPem);
    const privateKey = createPrivateKey(privatePem);

    // Unlike the previous key pair, this key pair contains an RSASSA-PSS-params
    // sequence. However, because all values in the RSASSA-PSS-params are set to
    // their defaults (see RFC 3447), the ASN.1 structure contains an empty
    // sequence. Node.js should add the default values to the key details.
    const expectedKeyDetails = {
      modulusLength: 2048,
      publicExponent: 65537n,
      hashAlgorithm: 'sha1',
      mgf1HashAlgorithm: 'sha1',
      saltLength: 20
    };

    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);
  }

  {
    // This key pair enforces sha256 as the message digest and the MGF1
    // message digest and a salt length of at least 16 bytes.
    const publicPem =
      fixtures.readKey('rsa_pss_public_2048_sha256_sha256_16.pem');
    const privatePem =
      fixtures.readKey('rsa_pss_private_2048_sha256_sha256_16.pem');

    const publicKey = createPublicKey(publicPem);
    const privateKey = createPrivateKey(privatePem);

    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');

    for (const key of [privatePem, privateKey]) {
      // Signing with anything other than sha256 should fail.
      assert.throws(() => {
        createSign('sha1').sign(key);
      }, /digest not allowed/);

      // Signing with salt lengths less than 16 bytes should fail.
      for (const saltLength of [8, 10, 12]) {
        assert.throws(() => {
          createSign('sha1').sign({ key, saltLength });
        }, /pss saltlen too small/);
      }

      // Signing with sha256 and appropriate salt lengths should work.
      for (const saltLength of [undefined, 16, 18, 20]) {
        const signature = createSign('sha256')
                          .update('foo')
                          .sign({ key, saltLength });

        for (const pkey of [key, publicKey, publicPem]) {
          const okay = createVerify('sha256')
                       .update('foo')
                       .verify({ key: pkey, saltLength }, signature);

          assert.ok(okay);
        }
      }
    }
  }

  {
    // This key enforces sha512 as the message digest and sha256 as the MGF1
    // message digest.
    const publicPem =
      fixtures.readKey('rsa_pss_public_2048_sha512_sha256_20.pem');
    const privatePem =
      fixtures.readKey('rsa_pss_private_2048_sha512_sha256_20.pem');

    const publicKey = createPublicKey(publicPem);
    const privateKey = createPrivateKey(privatePem);

    const expectedKeyDetails = {
      modulusLength: 2048,
      publicExponent: 65537n,
      hashAlgorithm: 'sha512',
      mgf1HashAlgorithm: 'sha256',
      saltLength: 20
    };

    assert.strictEqual(publicKey.type, 'public');
    assert.strictEqual(publicKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(publicKey.asymmetricKeyDetails, expectedKeyDetails);

    assert.strictEqual(privateKey.type, 'private');
    assert.strictEqual(privateKey.asymmetricKeyType, 'rsa-pss');
    assert.deepStrictEqual(privateKey.asymmetricKeyDetails, expectedKeyDetails);

    // Node.js usually uses the same hash function for the message and for MGF1.
    // However, when a different MGF1 message digest algorithm has been
    // specified as part of the key, it should automatically switch to that.
    // This behavior is required by sections 3.1 and 3.3 of RFC4055.
    for (const key of [privatePem, privateKey]) {
      // sha256 matches the MGF1 hash function and should be used internally,
      // but it should not be permitted as the main message digest algorithm.
      for (const algo of ['sha1', 'sha256']) {
        assert.throws(() => {
          createSign(algo).sign(key);
        }, /digest not allowed/);
      }

      // sha512 should produce a valid signature.
      const signature = createSign('sha512')
                        .update('foo')
                        .sign(key);

      for (const pkey of [key, publicKey, publicPem]) {
        const okay = createVerify('sha512')
                     .update('foo')
                     .verify(pkey, signature);

        assert.ok(okay);
      }
    }
  }
} else {
  common.printSkipMessage('Skipping unsupported RSA-PSS test case');
}

{
  // Exporting an encrypted private key requires a cipher
  const privateKey = createPrivateKey(privatePem);
  assert.throws(() => {
    privateKey.export({
      format: 'pem', type: 'pkcs8', passphrase: 'super-secret'
    });
  }, {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_VALUE',
    message: "The property 'options.cipher' is invalid. Received undefined"
  });
}

{
  // SecretKeyObject export buffer format (default)
  const buffer = Buffer.from('Hello World');
  const keyObject = createSecretKey(buffer);
  assert.deepStrictEqual(keyObject.export(), buffer);
  assert.deepStrictEqual(keyObject.export({}), buffer);
  assert.deepStrictEqual(keyObject.export({ format: 'buffer' }), buffer);
  assert.deepStrictEqual(keyObject.export({ format: undefined }), buffer);
}

{
  // Exporting an "oct" JWK from a SecretKeyObject
  const buffer = Buffer.from('Hello World');
  const keyObject = createSecretKey(buffer);
  assert.deepStrictEqual(
    keyObject.export({ format: 'jwk' }),
    { kty: 'oct', k: 'SGVsbG8gV29ybGQ' }
  );
}

{
  // Exporting a JWK unsupported curve EC key
  const supported = ['prime256v1', 'secp256k1', 'secp384r1', 'secp521r1'];
  // Find an unsupported curve regardless of whether a FIPS compliant crypto
  // provider is currently in use.
  const namedCurve = getCurves().find((curve) => !supported.includes(curve));
  assert(namedCurve);
  const keyPair = generateKeyPairSync('ec', { namedCurve });
  const { publicKey, privateKey } = keyPair;
  assert.throws(
    () => publicKey.export({ format: 'jwk' }),
    {
      code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
      message: `Unsupported JWK EC curve: ${namedCurve}.`
    });
  assert.throws(
    () => privateKey.export({ format: 'jwk' }),
    {
      code: 'ERR_CRYPTO_JWK_UNSUPPORTED_CURVE',
      message: `Unsupported JWK EC curve: ${namedCurve}.`
    });
}

{
  const first = Buffer.from('Hello');
  const second = Buffer.from('World');
  const keyObject = createSecretKey(first);
  assert(createSecretKey(first).equals(createSecretKey(first)));
  assert(!createSecretKey(first).equals(createSecretKey(second)));

  assert.throws(() => keyObject.equals(0), {
    name: 'TypeError',
    code: 'ERR_INVALID_ARG_TYPE',
    message: 'The "otherKeyObject" argument must be an instance of KeyObject. Received type number (0)'
  });

  assert(keyObject.equals(keyObject));
  assert(!keyObject.equals(createPublicKey(publicPem)));
  assert(!keyObject.equals(createPrivateKey(privatePem)));
}

{
  const first = generateKeyPairSync('ed25519');
  const second = generateKeyPairSync('ed25519');
  const secret = generateKeySync('aes', { length: 128 });

  assert(first.publicKey.equals(first.publicKey));
  assert(first.publicKey.equals(createPublicKey(
    first.publicKey.export({ format: 'pem', type: 'spki' }))));
  assert(!first.publicKey.equals(second.publicKey));
  assert(!first.publicKey.equals(second.privateKey));
  assert(!first.publicKey.equals(secret));

  assert(first.privateKey.equals(first.privateKey));
  assert(first.privateKey.equals(createPrivateKey(
    first.privateKey.export({ format: 'pem', type: 'pkcs8' }))));
  assert(!first.privateKey.equals(second.privateKey));
  assert(!first.privateKey.equals(second.publicKey));
  assert(!first.privateKey.equals(secret));
}

{
  const first = generateKeyPairSync('ed25519');
  const second = generateKeyPairSync('x25519');

  assert(!first.publicKey.equals(second.publicKey));
  assert(!first.publicKey.equals(second.privateKey));
  assert(!first.privateKey.equals(second.privateKey));
  assert(!first.privateKey.equals(second.publicKey));
}

{
  const first = createSecretKey(Buffer.alloc(0));
  const second = createSecretKey(new ArrayBuffer(0));
  const third = createSecretKey(Buffer.alloc(1));
  assert(first.equals(first));
  assert(first.equals(second));
  assert(!first.equals(third));
  assert(!third.equals(first));
}

{
  // This should not cause a crash: https://github.com/nodejs/node/issues/44471
  for (const key of ['', 'foo', null, undefined, true, Boolean]) {
    assert.throws(() => {
      createPublicKey({ key, format: 'jwk' });
    }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
    assert.throws(() => {
      createPrivateKey({ key, format: 'jwk' });
    }, { code: 'ERR_INVALID_ARG_TYPE', message: /The "key\.key" property must be of type object/ });
  }
}

// Test that createPublicKey/createPrivateKey error messages use 'key.<property>' paths
{
  // createPrivateKey with invalid format
  assert.throws(() => {
    createPrivateKey({ key: Buffer.alloc(0), format: 'banana', type: 'pkcs8' });
  }, {
    code: 'ERR_INVALID_ARG_VALUE',
    message: /key\.format/,
  });

  // createPrivateKey with invalid type
  assert.throws(() => {
    createPrivateKey({ key: Buffer.alloc(0), format: 'der', type: 'banana' });
  }, {
    code: 'ERR_INVALID_ARG_VALUE',
    message: /key\.type/,
  });

  // createPublicKey with invalid format
  assert.throws(() => {
    createPublicKey({ key: Buffer.alloc(0), format: 'banana', type: 'spki' });
  }, {
    code: 'ERR_INVALID_ARG_VALUE',
    message: /key\.format/,
  });

  // createPublicKey with invalid type
  assert.throws(() => {
    createPublicKey({ key: Buffer.alloc(0), format: 'der', type: 'banana' });
  }, {
    code: 'ERR_INVALID_ARG_VALUE',
    message: /key\.type/,
  });
}
