@ 261107.7
current
// META: title=WebCrypto API: supports method tests
// META: script=../util/helpers.js
// META: script=util/supports.js

'use strict';

const standardAlgorithms = {
  // Asymmetric algorithms
  'RSASSA-PKCS1-v1_5': {
    operations: ['generateKey', 'importKey', 'sign', 'verify', 'getPublicKey'],
    keyGenParams: {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    signParams: { name: 'RSASSA-PKCS1-v1_5' },
  },
  'RSA-PSS': {
    operations: ['generateKey', 'importKey', 'sign', 'verify', 'getPublicKey'],
    keyGenParams: {
      name: 'RSA-PSS',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSA-PSS', hash: 'SHA-256' },
    signParams: { name: 'RSA-PSS', saltLength: 32 },
  },
  'RSA-OAEP': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt', 'getPublicKey'],
    keyGenParams: {
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    },
    importParams: { name: 'RSA-OAEP', hash: 'SHA-256' },
    encryptParams: { name: 'RSA-OAEP' },
  },
  ECDSA: {
    operations: ['generateKey', 'importKey', 'sign', 'verify', 'getPublicKey'],
    keyGenParams: { name: 'ECDSA', namedCurve: 'P-256' },
    importParams: { name: 'ECDSA', namedCurve: 'P-256' },
    signParams: { name: 'ECDSA', hash: 'SHA-256' },
  },
  ECDH: {
    operations: ['generateKey', 'importKey', 'deriveBits', 'getPublicKey'],
    keyGenParams: { name: 'ECDH', namedCurve: 'P-256' },
    importParams: { name: 'ECDH', namedCurve: 'P-256' },
    deriveBitsParamsFactory: async () => {
      const {publicKey} = await crypto.subtle.generateKey(
        {name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']);
      return {name: 'ECDH', public: publicKey};
    },
  },
  Ed25519: {
    operations: ['generateKey', 'importKey', 'sign', 'verify', 'getPublicKey'],
    keyGenParams: null,
    signParams: { name: 'Ed25519' },
  },
  X25519: {
    operations: ['generateKey', 'importKey', 'deriveBits', 'getPublicKey'],
    keyGenParams: null,
    deriveBitsParamsFactory: async () => {
      const {publicKey} = await crypto.subtle.generateKey(
        'X25519', false, ['deriveBits']);
      return {name: 'X25519', public: publicKey};
    },
  },

  // Symmetric algorithms
  'AES-CBC': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-CBC', length: 256 },
    encryptParams: { name: 'AES-CBC', iv: new Uint8Array(16) },
  },
  'AES-CTR': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-CTR', length: 256 },
    encryptParams: {
      name: 'AES-CTR',
      counter: new Uint8Array(16),
      length: 128,
    },
  },
  'AES-GCM': {
    operations: ['generateKey', 'importKey', 'encrypt', 'decrypt'],
    keyGenParams: { name: 'AES-GCM', length: 256 },
    encryptParams: { name: 'AES-GCM', iv: new Uint8Array(12) },
  },
  'AES-KW': {
    operations: ['generateKey', 'importKey'], // wrapKey/unwrapKey not in requested operations
    keyGenParams: { name: 'AES-KW', length: 256 },
  },
  HMAC: {
    operations: ['generateKey', 'importKey', 'sign', 'verify'],
    keyGenParams: { name: 'HMAC', hash: 'SHA-256' },
    importParams: { name: 'HMAC', hash: 'SHA-256' },
    signParams: { name: 'HMAC' },
  },

  // Hash algorithms
  'SHA-1': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-256': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-384': {
    operations: ['digest'],
    keyGenParams: null,
  },
  'SHA-512': {
    operations: ['digest'],
    keyGenParams: null,
  },

  // Key derivation algorithms
  HKDF: {
    operations: ['importKey', 'deriveBits'],
    keyGenParams: null,
    deriveBitsParams: {
      name: 'HKDF',
      hash: 'SHA-256',
      salt: new Uint8Array(16),
      info: new Uint8Array(0),
    },
  },
  PBKDF2: {
    operations: ['importKey', 'deriveBits'],
    keyGenParams: null,
    deriveBitsParams: {
      name: 'PBKDF2',
      hash: 'SHA-256',
      salt: new Uint8Array(16),
      iterations: 100000,
    },
  },
};

const operations = [
  'generateKey',
  'importKey',
  'sign',
  'verify',
  'encrypt',
  'decrypt',
  'deriveBits',
  'digest',
  'getPublicKey',
];

// Test that supports method exists and is a static method
testSupportsMethod();

// Test invalid operation names
test(() => {
  assert_false(
    SubtleCrypto.supports('invalidOperation', 'AES-GCM'),
    'Invalid operation should return false'
  );
  assert_false(
    SubtleCrypto.supports('', 'AES-GCM'),
    'Empty operation should return false'
  );
  assert_false(
    SubtleCrypto.supports('GENERATEKEY', 'AES-GCM'),
    'Case-sensitive operation check'
  );
}, 'supports returns false for invalid operations');

// Test invalid algorithm identifiers
test(() => {
  assert_false(
    SubtleCrypto.supports('generateKey', 'InvalidAlgorithm'),
    'Invalid algorithm should return false'
  );
  assert_false(
    SubtleCrypto.supports('generateKey', ''),
    'Empty algorithm should return false'
  );
}, 'supports returns false for invalid algorithms');

// Test standard WebCrypto algorithms for requested operations
runSupportsTests(standardAlgorithms, operations);

// Test algorithm objects (not just strings)
test(() => {
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 256 }),
    'Algorithm object should be supported'
  );
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'HMAC', hash: 'SHA-256' }),
    'Algorithm object with parameters should be supported'
  );
}, 'supports works with algorithm objects');

// Test with algorithm objects that have invalid parameters
test(() => {
  assert_false(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 100 }),
    'Invalid key length should return false'
  );
  assert_false(
    SubtleCrypto.supports('generateKey', {
      name: 'HMAC',
      hash: 'INVALID-HASH',
    }),
    'Invalid hash parameter should return false'
  );
  assert_false(
      SubtleCrypto.supports(
          'encrypt', {name: 'AES-CBC', iv: new Uint8Array(10)}),
      'Invalid IV for AES-CBC should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(10),
        length: 128,
      }),
      'Invalid IV for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(16),
        length: 0,
      }),
      'Invalid length=0 for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-CTR',
        counter: new Uint8Array(16),
        length: 129,
      }),
      'Invalid length=129 for AES-CTR should return false');
  assert_false(
      SubtleCrypto.supports('encrypt', {
        name: 'AES-GCM',
        iv: new Uint8Array(16),
        tagLength: 100,
      }),
      'Invalid tag length for AES-GCM should return false');
  assert_false(
      SubtleCrypto.supports('decrypt', {
        name: 'AES-GCM',
        iv: new Uint8Array(16),
        tagLength: 100,
      }),
      'Invalid tag length for AES-GCM should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {name: 'ECDH', namedCurve: 'P-51'}),
      'Invalid curve for ECDH should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-25',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          8),
      'Invalid hash for HKDF should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          11),
      'Invalid length for HKDF should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          }),
      'null length for HKDF should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'HMAC',
        hash: 'SHA-25',
      }),
      'Invalid hash for HMAC should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'HMAC',
        hash: 'SHA-256',
        length: 0,
      }),
      'Invalid length for HMAC should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-25',
            salt: new Uint8Array(16),
            iterations: 100000,
          },
          8),
      'Invalid hash for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            iterations: 100000,
          },
          11),
      'Invalid length for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports(
          'deriveBits', {
            name: 'PBKDF2',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            iterations: 100000,
          }),
      'null length for PBKDF2 should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSASSA-PKCS1-v1_5',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-56',
      }),
      'Invalid hash for RSA PKCS1 should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSA-PSS',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-56',
      }),
      'Invalid hash for RSA PSS should return false');
  assert_false(
      SubtleCrypto.supports('generateKey', {
        name: 'RSA-OAEP',
        modulusLength: 2048,
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: 'SHA-26',
      }),
      'Invalid hash for RSA OAEP should return false');

}, 'supports returns false for algorithm objects with invalid parameters');

[
  ['SHA-1', 160],
  ['SHA-256', 256],
  ['SHA-384', 384],
  ['SHA-512', 512],
].forEach(([hash, hashLength]) => {
  test(() => {
    const algorithm = {
      name: 'HKDF',
      hash,
      salt: new Uint8Array(),
      info: new Uint8Array(),
    };
    const maximumLength = 255 * hashLength;

    assert_true(
      SubtleCrypto.supports('deriveBits', algorithm, maximumLength),
      `HKDF with ${hash} supports its maximum output length`
    );
    assert_false(
      SubtleCrypto.supports('deriveBits', algorithm, maximumLength + 8),
      `HKDF with ${hash} rejects output longer than its maximum`
    );
  }, `supports validates HKDF ${hash} output length`);
});

test(() => {
  assert_false(
    SubtleCrypto.supports(
      'deriveKey',
      {
        name: 'HKDF',
        hash: 'SHA-256',
        salt: new Uint8Array(),
        info: new Uint8Array(),
      },
      {name: 'HMAC', hash: 'SHA-256', length: 65288}
    ),
    'HKDF rejects a derived key longer than 255 hash blocks'
  );
}, 'supports validates HKDF output length for deriveKey');

test(() => {
  assert_false(
    SubtleCrypto.supports('importKey', {
      name: 'HMAC',
      hash: 'SHA-256',
      length: 0,
    }),
    'HMAC rejects an explicitly zero-length imported key'
  );
}, 'supports validates HMAC import length');

const invalidRsaKeyGenParameters = [
  {
    description: 'a modulus shorter than 4 bits',
    modulusLength: 3,
    publicExponent: Uint8Array.of(3),
  },
  {
    description: 'a public exponent less than 3',
    modulusLength: 2048,
    publicExponent: Uint8Array.of(1),
  },
  {
    description: 'an even public exponent',
    modulusLength: 2048,
    publicExponent: Uint8Array.of(4),
  },
  {
    description: 'a public exponent equal to 2^modulusLength - 1',
    modulusLength: 2048,
    publicExponent: new Uint8Array(256).fill(0xff),
  },
];

[
  'RSASSA-PKCS1-v1_5',
  'RSA-PSS',
  'RSA-OAEP',
].forEach(name => {
  invalidRsaKeyGenParameters.forEach(({description, ...parameters}) => {
    test(() => {
      assert_false(
        SubtleCrypto.supports('generateKey', {
          name,
          ...parameters,
          hash: 'SHA-256',
        }),
        `${name} rejects ${description}`
      );
    }, `supports rejects ${name} generateKey with ${description}`);
  });
});

['ECDSA', 'ECDH'].forEach(name => {
  test(() => {
    assert_false(
      SubtleCrypto.supports('importKey', {
        name,
        namedCurve: 'not-a-curve',
      }),
      `${name} rejects an unknown named curve`
    );
  }, `supports validates ${name} import namedCurve`);
});

[
  ['P-256', 256],
  ['P-384', 384],
  ['P-521', 528],
].forEach(([namedCurve, maximumLength]) => {
  promise_test(async () => {
    const {publicKey} = await crypto.subtle.generateKey(
      {name: 'ECDH', namedCurve}, false, ['deriveBits']);
    const algorithm = {name: 'ECDH', public: publicKey};

    assert_true(
      SubtleCrypto.supports('deriveBits', algorithm, maximumLength),
      `ECDH ${namedCurve} supports its maximum output length`
    );
    assert_false(
      SubtleCrypto.supports('deriveBits', algorithm, maximumLength + 1),
      `ECDH ${namedCurve} rejects output longer than its maximum`
    );
  }, `supports validates ECDH ${namedCurve} deriveBits length`);
});

promise_test(async () => {
  const {publicKey} = await crypto.subtle.generateKey(
    'X25519', false, ['deriveBits']);
  const algorithm = {name: 'X25519', public: publicKey};

  assert_true(
    SubtleCrypto.supports('deriveBits', algorithm, 256),
    'X25519 supports its maximum output length'
  );
  assert_false(
    SubtleCrypto.supports('deriveBits', algorithm, 257),
    'X25519 rejects output longer than its maximum'
  );
}, 'supports validates X25519 deriveBits length');

promise_test(async () => {
  const [ecdhKeyPair, x25519KeyPair] = await Promise.all([
    crypto.subtle.generateKey(
      {name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
    crypto.subtle.generateKey('X25519', false, ['deriveBits']),
  ]);

  assert_false(
    SubtleCrypto.supports(
      'deriveBits', {name: 'ECDH', public: ecdhKeyPair.privateKey}, 256),
    'ECDH rejects a private public property'
  );
  assert_false(
    SubtleCrypto.supports(
      'deriveBits', {name: 'ECDH', public: x25519KeyPair.publicKey}, 256),
    'ECDH rejects a public property for another algorithm'
  );
}, 'supports validates the ECDH public key');

promise_test(async () => {
  const [x25519KeyPair, ecdhKeyPair] = await Promise.all([
    crypto.subtle.generateKey('X25519', false, ['deriveBits']),
    crypto.subtle.generateKey(
      {name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
  ]);

  assert_false(
    SubtleCrypto.supports(
      'deriveBits', {name: 'X25519', public: x25519KeyPair.privateKey}, 256),
    'X25519 rejects a private public property'
  );
  assert_false(
    SubtleCrypto.supports(
      'deriveBits', {name: 'X25519', public: ecdhKeyPair.publicKey}, 256),
    'X25519 rejects a public property for another algorithm'
  );
}, 'supports validates the X25519 public key');

promise_test(async () => {
  const [p256KeyPair, p521KeyPair] = await Promise.all([
    crypto.subtle.generateKey(
      {name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
    crypto.subtle.generateKey(
      {name: 'ECDH', namedCurve: 'P-521'}, false, ['deriveBits']),
  ]);
  const derivedKeyAlgorithm = {name: 'HMAC', hash: 'SHA-256'};

  assert_false(
    SubtleCrypto.supports(
      'deriveKey',
      {name: 'ECDH', public: p256KeyPair.publicKey},
      derivedKeyAlgorithm
    ),
    'ECDH P-256 cannot derive a 512-bit HMAC key'
  );
  assert_true(
    SubtleCrypto.supports(
      'deriveKey',
      {name: 'ECDH', public: p521KeyPair.publicKey},
      derivedKeyAlgorithm
    ),
    'ECDH P-521 can derive a 512-bit HMAC key'
  );
}, 'supports derives the ECDH output limit from the public curve');

// Test some specific combinations that should work
test(() => {
  // RSA algorithms
  assert_true(
    SubtleCrypto.supports('generateKey', {
      name: 'RSASSA-PKCS1-v1_5',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-256',
    }),
    'RSASSA-PKCS1-v1_5 generateKey'
  );
  assert_true(
    SubtleCrypto.supports('sign', { name: 'RSASSA-PKCS1-v1_5' }),
    'RSASSA-PKCS1-v1_5 sign'
  );
  assert_true(
    SubtleCrypto.supports('verify', { name: 'RSASSA-PKCS1-v1_5' }),
    'RSASSA-PKCS1-v1_5 verify'
  );

  // ECDSA
  assert_true(
    SubtleCrypto.supports('generateKey', {
      name: 'ECDSA',
      namedCurve: 'P-256',
    }),
    'ECDSA generateKey'
  );
  assert_true(
    SubtleCrypto.supports('sign', { name: 'ECDSA', hash: 'SHA-256' }),
    'ECDSA sign'
  );
  assert_true(
    SubtleCrypto.supports('verify', { name: 'ECDSA', hash: 'SHA-256' }),
    'ECDSA verify'
  );

  // AES-GCM
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'AES-GCM', length: 256 }),
    'AES-GCM generateKey'
  );
  assert_true(
    SubtleCrypto.supports('encrypt', {
      name: 'AES-GCM',
      iv: new Uint8Array(12),
    }),
    'AES-GCM encrypt'
  );
  assert_true(
    SubtleCrypto.supports('decrypt', {
      name: 'AES-GCM',
      iv: new Uint8Array(12),
    }),
    'AES-GCM decrypt'
  );

  // HMAC
  assert_true(
    SubtleCrypto.supports('generateKey', { name: 'HMAC', hash: 'SHA-256' }),
    'HMAC generateKey'
  );
  assert_true(SubtleCrypto.supports('sign', { name: 'HMAC' }), 'HMAC sign');
  assert_true(SubtleCrypto.supports('verify', { name: 'HMAC' }), 'HMAC verify');
}, 'Common algorithm and operation combinations work');

// Test some specific combinations that should not work
test(() => {
  // Hash algorithms don't support key operations
  assert_false(
    SubtleCrypto.supports('generateKey', 'SHA-256'),
    'SHA-256 generateKey should fail'
  );
  assert_false(
    SubtleCrypto.supports('sign', 'SHA-256'),
    'SHA-256 sign should fail'
  );

  // AES can't sign/verify (these require algorithm parameters due to normalization)
  assert_false(
    SubtleCrypto.supports('sign', 'AES-GCM'),
    'AES-GCM sign should fail'
  );
  assert_false(
    SubtleCrypto.supports('verify', 'AES-GCM'),
    'AES-GCM verify should fail'
  );

  // ECDSA can't encrypt/decrypt
  assert_false(
    SubtleCrypto.supports('encrypt', 'ECDSA'),
    'ECDSA encrypt should fail'
  );
  assert_false(
    SubtleCrypto.supports('decrypt', 'ECDSA'),
    'ECDSA decrypt should fail'
  );

  // HMAC can't encrypt/decrypt
  assert_false(
    SubtleCrypto.supports('encrypt', 'HMAC'),
    'HMAC encrypt should fail'
  );
  assert_false(
    SubtleCrypto.supports('decrypt', 'HMAC'),
    'HMAC decrypt should fail'
  );

  // Non-hash algorithms can't digest
  assert_false(
    SubtleCrypto.supports('digest', 'AES-GCM'),
    'AES-GCM digest should fail'
  );
  assert_false(
    SubtleCrypto.supports('digest', 'ECDSA'),
    'ECDSA digest should fail'
  );
  assert_false(
    SubtleCrypto.supports('digest', 'HMAC'),
    'HMAC digest should fail'
  );
}, 'Invalid algorithm and operation combinations fail');

// Test supports for deriveKey op
test(() => {
  assert_true(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'HKDF',
            hash: 'SHA-256',
            salt: new Uint8Array(16),
            info: new Uint8Array(0),
          },
          {name: 'HMAC', hash: 'SHA-256'}),

      'deriveKey HKDF-HMAC should pass');
}, 'deriveKey tests');

promise_test(async (t) => {
  let keypair = await crypto.subtle.generateKey(
      {
        name: 'X25519',
      },
      false, ['deriveKey', 'deriveBits']);

  assert_true(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'X25519',
            public: keypair.publicKey,
          },
          {name: 'AES-GCM', length: 256}),

      'deriveKey X25519-AES-GCM-256 should pass');

  assert_false(
      SubtleCrypto.supports(
          'deriveKey', {
            name: 'X25519',
            public: keypair.publicKey,
          },
          {name: 'HMAC', hash: 'SHA-256'}),

      'deriveKey X25519-HMAC-SHA-256 should fail');
}, 'deriveKey promise tests');

// Test supports for exportKey op
test(() => {
  assert_true(SubtleCrypto.supports('exportKey', 'AES-GCM'),
              'exportKey AES-GCM should pass');
  assert_false(SubtleCrypto.supports('exportKey', 'HKDF'),
               'exportKey HKDF should fail');
  assert_false(SubtleCrypto.supports('exportKey', 'PBKDF2'),
               'exportKey PBKDF2 should fail');
}, 'exportKey tests');

// Test supports for wrapKey op
test(() => {
  assert_true(SubtleCrypto.supports('wrapKey', 'AES-KW', 'AES-GCM'),
              'wrapKey AES-KW-AES-GCM should pass');
  assert_false(SubtleCrypto.supports('wrapKey', 'AES-KW', 'HKDF'),
               'wrapKey AES-KW-HKDF should fail');
  assert_false(SubtleCrypto.supports('wrapKey', 'AES-KW', 'PBKDF2'),
               'wrapKey AES-KW-PBKDF2 should fail');
}, 'wrapKey tests');


done();