current commit
261025.12 · test,benchmark: drop OpenSSL 1.x-only coverage
'use strict';

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

if (!common.hasCrypto)
  common.skip('missing crypto');

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

const assert = require('assert');
const { subtle } = globalThis.crypto;
const {
  createPrivateKey,
  createPublicKey,
  createSecretKey,
  getFips,
} = require('crypto');

{
  async function test() {
    const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
    await Promise.all([1, null, undefined, {}, []].map((format) =>
      assert.rejects(
        subtle.importKey(format, keyData, {}, false, ['wrapKey']), {
          code: 'ERR_INVALID_ARG_VALUE'
        })
    ));
    await assert.rejects(
      subtle.importKey('not valid', keyData, {}, false, ['wrapKey']), {
        code: 'ERR_INVALID_ARG_VALUE'
      });
    await assert.rejects(
      subtle.importKey('KeyObject', keyData, {}, false, ['wrapKey']), {
        message: /'KeyObject' is not a valid enum value of type KeyFormat/,
        code: 'ERR_INVALID_ARG_VALUE'
      });
    await assert.rejects(
      subtle.importKey('raw', 1, {}, false, ['deriveBits']), {
        code: 'ERR_INVALID_ARG_TYPE'
      });
    await assert.rejects(
      subtle.importKey('raw', keyData, {
        name: 'HMAC'
      }, false, ['sign', 'verify']), {
        code: 'ERR_MISSING_OPTION'
      });
    await assert.rejects(
      subtle.importKey('raw', keyData, {
        name: 'HMAC',
        hash: 'SHA-256',
        length: 384,
      }, false, ['sign', 'verify']), {
        name: 'DataError',
        message: 'Invalid key length'
      });
    await assert.rejects(
      subtle.importKey('raw', keyData, {
        name: 'HMAC',
        hash: 'SHA-256'
      }, false, ['deriveBits']), {
        name: 'SyntaxError',
        message: 'Unsupported key usage for HMAC key'
      });
    await assert.rejects(
      subtle.importKey('raw', keyData, {
        name: 'HMAC',
        hash: 'SHA-256',
        length: 0
      }, false, ['sign', 'verify']), {
        name: 'DataError',
        message: 'HmacImportParams.length cannot be 0'
      });
    await assert.rejects(
      subtle.importKey('raw', keyData, {
        name: 'HMAC',
        hash: 'SHA-256',
        length: 1
      }, false, ['sign', 'verify']), {
        name: 'DataError',
        message: 'Invalid key length'
      });
    await assert.rejects(
      subtle.importKey('jwk', null, {
        name: 'HMAC',
        hash: 'SHA-256',
      }, false, ['sign', 'verify']), {
        name: 'DataError',
        message: 'Invalid keyData'
      });
  }

  test().then(common.mustCall());
}

// HMAC non-byte key lengths
{
  async function test() {
    const generated = await subtle.generateKey(
      { name: 'HMAC', hash: 'SHA-256', length: 9 },
      true,
      ['sign', 'verify']);
    const generatedRaw = await subtle.exportKey('raw', generated);
    assert.strictEqual(generated.algorithm.length, 9);
    assert.strictEqual(generatedRaw.byteLength, 2);
    assert.strictEqual(new Uint8Array(generatedRaw)[1] & 0b01111111, 0);

    const importedExplicit = await subtle.importKey(
      'raw',
      new Uint8Array([0xff, 0xff]),
      { name: 'HMAC', hash: 'SHA-256', length: 9 },
      true,
      ['sign', 'verify']);
    const importedExplicitRaw = await subtle.exportKey('raw', importedExplicit);
    assert.strictEqual(importedExplicit.algorithm.length, 9);
    assert.deepStrictEqual(
      new Uint8Array(importedExplicitRaw),
      new Uint8Array([0xff, 0x80]));

    const importedImplicit = await subtle.importKey(
      'raw',
      new Uint8Array([0xff, 0xff]),
      { name: 'HMAC', hash: 'SHA-256' },
      true,
      ['sign', 'verify']);
    const importedImplicitRaw = await subtle.exportKey('raw', importedImplicit);
    assert.strictEqual(importedImplicit.algorithm.length, 16);
    assert.deepStrictEqual(
      new Uint8Array(importedImplicitRaw),
      new Uint8Array([0xff, 0xff]));

    await assert.rejects(
      subtle.importKey(
        'raw',
        new Uint8Array([0xff]),
        { name: 'HMAC', hash: 'SHA-256', length: 9 },
        true,
        ['sign', 'verify']),
      { name: 'DataError', message: 'Invalid key length' });
  }

  test().then(common.mustCall());
}

// Import/Export HMAC Secret Key
{
  async function test() {
    const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
    const key = await subtle.importKey(
      'raw',
      keyData, {
        name: 'HMAC',
        hash: 'SHA-256'
      }, true, ['sign', 'verify']);

    assert.strictEqual(key.algorithm, key.algorithm);
    assert.strictEqual(key.usages, key.usages);

    const raw = await subtle.exportKey('raw', key);

    assert.deepStrictEqual(
      Buffer.from(keyData).toString('hex'),
      Buffer.from(raw).toString('hex'));

    const jwk = await subtle.exportKey('jwk', key);
    assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
    assert(jwk.ext);
    assert.strictEqual(jwk.kty, 'oct');
    assert.strictEqual(jwk.alg, 'HS256');

    assert.deepStrictEqual(
      Buffer.from(jwk.k, 'base64').toString('hex'),
      Buffer.from(raw).toString('hex'));

    await subtle.importKey(
      'jwk',
      jwk,
      {
        name: 'HMAC',
        hash: 'SHA-256'
      },
      true,
      ['sign', 'verify']);

    await subtle.importKey(
      'jwk',
      { ...jwk, alg: undefined },
      {
        name: 'HMAC',
        hash: 'SHA-256'
      },
      true,
      ['sign', 'verify']);

    await assert.rejects(
      subtle.importKey(
        'jwk',
        { ...jwk, alg: 'HS384' },
        {
          name: 'HMAC',
          hash: 'SHA-256'
        },
        true,
        ['sign', 'verify']),
      { name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });

    await assert.rejects(
      subtle.importKey(
        'raw',
        keyData,
        {
          name: 'HMAC',
          hash: 'SHA-256'
        },
        true,
        [/* empty usages */]),
      { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
  }

  test().then(common.mustCall());
}

// Import/Export KMAC Secret Key
if (!isBoringSSL) {
  async function test(name) {
    const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
    const key = await subtle.importKey(
      'raw-secret',
      keyData, name, true, ['sign', 'verify']);

    assert.strictEqual(key.algorithm, key.algorithm);
    assert.strictEqual(key.usages, key.usages);

    const raw = await subtle.exportKey('raw-secret', key);

    assert.deepStrictEqual(
      Buffer.from(keyData).toString('hex'),
      Buffer.from(raw).toString('hex'));

    const jwk = await subtle.exportKey('jwk', key);
    assert.deepStrictEqual(jwk.key_ops, ['sign', 'verify']);
    assert(jwk.ext);
    assert.strictEqual(jwk.kty, 'oct');
    assert.strictEqual(jwk.alg, `K${name.substring(4)}`);

    assert.deepStrictEqual(
      Buffer.from(jwk.k, 'base64').toString('hex'),
      Buffer.from(raw).toString('hex'));

    await subtle.importKey(
      'jwk',
      jwk,
      name,
      true,
      ['sign', 'verify']);

    await subtle.importKey(
      'jwk',
      { ...jwk, alg: undefined },
      name,
      true,
      ['sign', 'verify']);

    await assert.rejects(
      subtle.importKey(
        'jwk',
        { ...jwk, alg: name === 'KMAC128' ? 'K256' : 'K128' },
        name,
        true,
        ['sign', 'verify']),
      { name: 'DataError', message: 'JWK "alg" does not match the requested algorithm' });

    await assert.rejects(
      subtle.importKey(
        'raw',
        keyData, name, true, ['sign', 'verify']),
      { name: 'NotSupportedError', message: `Unable to import ${name} using raw format` });

    await assert.rejects(
      subtle.importKey(
        'raw-secret',
        keyData,
        name,
        true,
        [/* empty usages */]),
      { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });

    for (const algorithm of [name, { name, length: 0 }]) {
      await assert.rejects(
        subtle.importKey(
          'raw-secret', new Uint8Array(), algorithm, true, ['sign', 'verify']),
        { name: 'NotSupportedError', message: 'Invalid key length' });
    }

    await assert.rejects(
      subtle.importKey(
        'raw-secret',
        new Uint8Array([0xff]),
        { name, length: 0 },
        true,
        ['sign', 'verify']),
      { name: 'NotSupportedError', message: 'Invalid key length' });

    await assert.rejects(
      subtle.generateKey({ name, length: 9 }, true, ['sign', 'verify']),
      { name: 'NotSupportedError', message: 'Invalid key length' });

    for (const byteLength of [1, 2]) {
      await assert.rejects(
        subtle.importKey(
          'raw-secret',
          new Uint8Array(byteLength).fill(0xff),
          { name, length: 9 },
          true,
          ['sign', 'verify']),
        { name: 'NotSupportedError', message: 'Invalid key length' });
    }
  }

  test('KMAC128').then(common.mustCall());
  test('KMAC256').then(common.mustCall());
}

// Import/Export AES Secret Key
{
  async function test() {
    const keyData = globalThis.crypto.getRandomValues(new Uint8Array(32));
    const key = await subtle.importKey(
      'raw',
      keyData, {
        name: 'AES-CTR',
        length: 256,
      }, true, ['encrypt', 'decrypt']);
    assert.strictEqual(key.algorithm, key.algorithm);
    assert.strictEqual(key.usages, key.usages);

    const raw = await subtle.exportKey('raw', key);

    assert.deepStrictEqual(
      Buffer.from(keyData).toString('hex'),
      Buffer.from(raw).toString('hex'));

    const jwk = await subtle.exportKey('jwk', key);
    assert.deepStrictEqual(jwk.key_ops, ['encrypt', 'decrypt']);
    assert(jwk.ext);
    assert.strictEqual(jwk.kty, 'oct');

    assert.deepStrictEqual(
      Buffer.from(jwk.k, 'base64').toString('hex'),
      Buffer.from(raw).toString('hex'));

    await assert.rejects(
      subtle.importKey(
        'raw',
        keyData,
        {
          name: 'AES-CTR',
          length: 256,
        },
        true,
        [/* empty usages */]),
      { name: 'SyntaxError', message: 'Usages cannot be empty when importing a secret key.' });
  }

  test().then(common.mustCall());
}

// Import/Export RSA Key Pairs
{
  async function test() {
    const { publicKey, privateKey } = await subtle.generateKey({
      name: 'RSA-PSS',
      modulusLength: getFips() === 1 ? 2048 : 1024,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-384'
    }, true, ['sign', 'verify']);

    const [
      spki,
      pkcs8,
      publicJwk,
      privateJwk,
    ] = await Promise.all([
      subtle.exportKey('spki', publicKey),
      subtle.exportKey('pkcs8', privateKey),
      subtle.exportKey('jwk', publicKey),
      subtle.exportKey('jwk', privateKey),
    ]);

    assert(spki);
    assert(pkcs8);
    assert(publicJwk);
    assert(privateJwk);

    const [
      importedSpkiPublicKey,
      importedPkcs8PrivateKey,
      importedJwkPublicKey,
      importedJwkPrivateKey,
    ] = await Promise.all([
      subtle.importKey('spki', spki, {
        name: 'RSA-PSS',
        hash: 'SHA-384',
      }, true, ['verify']),
      subtle.importKey('pkcs8', pkcs8, {
        name: 'RSA-PSS',
        hash: 'SHA-384',
      }, true, ['sign']),
      subtle.importKey('jwk', publicJwk, {
        name: 'RSA-PSS',
        hash: 'SHA-384',
      }, true, ['verify']),
      subtle.importKey('jwk', privateJwk, {
        name: 'RSA-PSS',
        hash: 'SHA-384',
      }, true, ['sign']),
    ]);

    assert(importedSpkiPublicKey);
    assert(importedPkcs8PrivateKey);
    assert(importedJwkPublicKey);
    assert(importedJwkPrivateKey);
  }

  test().then(common.mustCall());
}

// Import/Export EC Key Pairs
{
  async function test() {
    const { publicKey, privateKey } = await subtle.generateKey({
      name: 'ECDSA',
      namedCurve: 'P-384'
    }, true, ['sign', 'verify']);

    const [
      spki,
      pkcs8,
      publicJwk,
      privateJwk,
    ] = await Promise.all([
      subtle.exportKey('spki', publicKey),
      subtle.exportKey('pkcs8', privateKey),
      subtle.exportKey('jwk', publicKey),
      subtle.exportKey('jwk', privateKey),
    ]);

    assert(spki);
    assert(pkcs8);
    assert(publicJwk);
    assert(privateJwk);

    const [
      importedSpkiPublicKey,
      importedPkcs8PrivateKey,
      importedJwkPublicKey,
      importedJwkPrivateKey,
    ] = await Promise.all([
      subtle.importKey('spki', spki, {
        name: 'ECDSA',
        namedCurve: 'P-384'
      }, true, ['verify']),
      subtle.importKey('pkcs8', pkcs8, {
        name: 'ECDSA',
        namedCurve: 'P-384'
      }, true, ['sign']),
      subtle.importKey('jwk', publicJwk, {
        name: 'ECDSA',
        namedCurve: 'P-384'
      }, true, ['verify']),
      subtle.importKey('jwk', privateJwk, {
        name: 'ECDSA',
        namedCurve: 'P-384'
      }, true, ['sign']),
    ]);

    assert(importedSpkiPublicKey);
    assert(importedPkcs8PrivateKey);
    assert(importedJwkPublicKey);
    assert(importedJwkPrivateKey);
  }

  test().then(common.mustCall());
}

// SHA-3 hashes and JWK "alg"
{
  const rsa = fixtures.readKey('rsa_private_2048.pem');
  const privateKey = createPrivateKey(rsa);
  const publicKey = createPublicKey(privateKey);

  async function test(keyObject, algorithm, usages) {
    if (isBoringSSL) {
      assert.throws(() => keyObject.toCryptoKey(algorithm, true, usages), {
        name: 'NotSupportedError',
        message: 'SHA-3 is only supported for digest operations',
      });
      return;
    }
    const key = keyObject.toCryptoKey(algorithm, true, usages);
    const jwk = await subtle.exportKey('jwk', key);
    assert.strictEqual(jwk.alg, undefined);
  }

  for (const hash of ['SHA3-256', 'SHA3-384', 'SHA3-512']) {
    for (const name of ['RSA-OAEP', 'RSA-PSS', 'RSASSA-PKCS1-v1_5']) {
      test(publicKey, { name, hash }, []).then(common.mustCall());
      test(privateKey, { name, hash }, [name === 'RSA-OAEP' ? 'unwrapKey' : 'sign']).then(common.mustCall());
    }

    test(createSecretKey(Buffer.alloc(32)), { name: 'HMAC', hash }, ['sign'])
      .then(common.mustCall());
  }

  {
    const jwk = createSecretKey(Buffer.alloc(16)).export({ format: 'jwk' });
    // This is rejected for SHA-2 but ignored for SHA-3
    // Otherwise, if the name attribute of hash is defined in another applicable specification:
    // Perform any key import steps defined by other applicable specifications, passing format,
    // jwk and hash and obtaining hash.
    jwk.alg = 'HS3-256';

    assert.rejects(subtle.importKey('jwk', jwk, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign', 'verify']), {
      name: 'DataError',
      message: 'JWK "alg" does not match the requested algorithm',
    }).then(common.mustCall());

    const imported = subtle.importKey(
      'jwk', jwk, { name: 'HMAC', hash: 'SHA3-256' }, false, ['sign', 'verify']);
    if (isBoringSSL) {
      assert.rejects(imported, {
        name: 'NotSupportedError',
        message: 'SHA-3 is only supported for digest operations',
      }).then(common.mustCall());
    } else {
      imported.then(common.mustCall());
    }
  }
}

{
  const keyData = new Uint8Array(32);
  assert.rejects(
    subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false),
    { code: 'ERR_MISSING_ARGS' }
  ).then(common.mustCall());
}