// 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'); done();