Skip to content

Commit 65fdf6e

Browse files
committed
[WebCryptoAPI] Expand supports() validation coverage
Keep ECDH mismatched-curve cases independent of the existing overlength deriveBits tests. Add supports() coverage for RSA, HKDF, HMAC, EC derivation and import, ML-DSA contexts, and ML-KEM imported keys. Refs: w3c/webcrypto#560 Refs: w3c/webcrypto#558 Refs: WICG/webcrypto-modern-algos#76 Refs: WICG/webcrypto-modern-algos#77
1 parent ea5c6e9 commit 65fdf6e

5 files changed

Lines changed: 292 additions & 15 deletions

File tree

WebCryptoAPI/derive_bits_keys/derive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ function registerDeriveTests(options) {
102102
failureTest(
103103
test.name,
104104
"InvalidAccessError",
105-
() => derive(algorithmName, test.key, privateKey)
105+
() => derive(algorithmName, test.key, privateKey, test.length)
106106
);
107107
});
108108

WebCryptoAPI/derive_bits_keys/ecdh.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ async function defineEcdhTests(operation) {
6666
{
6767
name: namedCurve + " mismatched curves",
6868
key: keys[otherCurve].publicKey,
69+
length: 256,
6970
},
7071
{
7172
name: namedCurve +

WebCryptoAPI/supports-modern.tentative.https.any.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,70 @@ testSupportsMethod();
6565
// Test standard WebCrypto algorithms for requested operations
6666
runSupportsTests(modernAlgorithms, operations);
6767

68+
['ML-DSA-44', 'ML-DSA-65', 'ML-DSA-87'].forEach(name => {
69+
['sign', 'verify'].forEach(operation => {
70+
test(() => {
71+
assert_true(
72+
SubtleCrypto.supports(operation, {
73+
name,
74+
context: new Uint8Array(255),
75+
}),
76+
`${name} ${operation} supports a 255-byte context`
77+
);
78+
assert_false(
79+
SubtleCrypto.supports(operation, {
80+
name,
81+
context: new Uint8Array(256),
82+
}),
83+
`${name} ${operation} rejects a 256-byte context`
84+
);
85+
}, `supports validates ${name} ${operation} context length`);
86+
});
87+
});
88+
89+
const mlKemImportedKeyCases = [
90+
{
91+
description: 'a matching HMAC length',
92+
additionalAlgorithm: {name: 'HMAC', hash: 'SHA-256', length: 256},
93+
expected: true,
94+
},
95+
{
96+
description: 'an algorithm without raw-secret import',
97+
additionalAlgorithm: 'Ed25519',
98+
expected: false,
99+
},
100+
{
101+
description: 'an HMAC length shorter than the shared secret',
102+
additionalAlgorithm: {name: 'HMAC', hash: 'SHA-256', length: 128},
103+
expected: false,
104+
},
105+
{
106+
description: 'an HMAC length longer than the shared secret',
107+
additionalAlgorithm: {name: 'HMAC', hash: 'SHA-256', length: 512},
108+
expected: false,
109+
},
110+
];
111+
112+
['encapsulateKey', 'decapsulateKey'].forEach(operation => {
113+
mlKemImportedKeyCases.forEach(({
114+
description,
115+
additionalAlgorithm,
116+
expected,
117+
}) => {
118+
test(() => {
119+
assert_equals(
120+
SubtleCrypto.supports(
121+
operation,
122+
'ML-KEM-768',
123+
additionalAlgorithm
124+
),
125+
expected,
126+
`ML-KEM-768 ${operation} with ${description}`
127+
);
128+
}, `supports ${operation} with ${description}`);
129+
});
130+
});
131+
68132
// Test some algorithm objects with valid parameters
69133
test(() => {
70134
assert_true(

WebCryptoAPI/supports.tentative.https.any.js

Lines changed: 223 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,10 @@ const standardAlgorithms = {
4949
operations: ['generateKey', 'importKey', 'deriveBits', 'getPublicKey'],
5050
keyGenParams: { name: 'ECDH', namedCurve: 'P-256' },
5151
importParams: { name: 'ECDH', namedCurve: 'P-256' },
52-
deriveBitsParams: {
53-
name: 'ECDH',
54-
public: crypto.subtle.generateKey(
55-
{ name: 'ECDH', namedCurve: 'P-256' },
56-
false,
57-
['deriveBits']
58-
),
52+
deriveBitsParamsFactory: async () => {
53+
const {publicKey} = await crypto.subtle.generateKey(
54+
{name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']);
55+
return {name: 'ECDH', public: publicKey};
5956
},
6057
},
6158
Ed25519: {
@@ -66,9 +63,10 @@ const standardAlgorithms = {
6663
X25519: {
6764
operations: ['generateKey', 'importKey', 'deriveBits', 'getPublicKey'],
6865
keyGenParams: null,
69-
deriveBitsParams: {
70-
name: 'X25519',
71-
public: crypto.subtle.generateKey('X25519', false, ['deriveBits']),
66+
deriveBitsParamsFactory: async () => {
67+
const {publicKey} = await crypto.subtle.generateKey(
68+
'X25519', false, ['deriveBits']);
69+
return {name: 'X25519', public: publicKey};
7270
},
7371
},
7472

@@ -247,6 +245,13 @@ test(() => {
247245
tagLength: 100,
248246
}),
249247
'Invalid tag length for AES-GCM should return false');
248+
assert_false(
249+
SubtleCrypto.supports('decrypt', {
250+
name: 'AES-GCM',
251+
iv: new Uint8Array(16),
252+
tagLength: 100,
253+
}),
254+
'Invalid tag length for AES-GCM should return false');
250255
assert_false(
251256
SubtleCrypto.supports('generateKey', {name: 'ECDH', namedCurve: 'P-51'}),
252257
'Invalid curve for ECDH should return false');
@@ -348,6 +353,214 @@ test(() => {
348353

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

356+
[
357+
['SHA-1', 160],
358+
['SHA-256', 256],
359+
['SHA-384', 384],
360+
['SHA-512', 512],
361+
].forEach(([hash, hashLength]) => {
362+
test(() => {
363+
const algorithm = {
364+
name: 'HKDF',
365+
hash,
366+
salt: new Uint8Array(),
367+
info: new Uint8Array(),
368+
};
369+
const maximumLength = 255 * hashLength;
370+
371+
assert_true(
372+
SubtleCrypto.supports('deriveBits', algorithm, maximumLength),
373+
`HKDF with ${hash} supports its maximum output length`
374+
);
375+
assert_false(
376+
SubtleCrypto.supports('deriveBits', algorithm, maximumLength + 8),
377+
`HKDF with ${hash} rejects output longer than its maximum`
378+
);
379+
}, `supports validates HKDF ${hash} output length`);
380+
});
381+
382+
test(() => {
383+
assert_false(
384+
SubtleCrypto.supports(
385+
'deriveKey',
386+
{
387+
name: 'HKDF',
388+
hash: 'SHA-256',
389+
salt: new Uint8Array(),
390+
info: new Uint8Array(),
391+
},
392+
{name: 'HMAC', hash: 'SHA-256', length: 65288}
393+
),
394+
'HKDF rejects a derived key longer than 255 hash blocks'
395+
);
396+
}, 'supports validates HKDF output length for deriveKey');
397+
398+
test(() => {
399+
assert_false(
400+
SubtleCrypto.supports('importKey', {
401+
name: 'HMAC',
402+
hash: 'SHA-256',
403+
length: 0,
404+
}),
405+
'HMAC rejects an explicitly zero-length imported key'
406+
);
407+
}, 'supports validates HMAC import length');
408+
409+
const invalidRsaKeyGenParameters = [
410+
{
411+
description: 'a modulus shorter than 4 bits',
412+
modulusLength: 3,
413+
publicExponent: Uint8Array.of(3),
414+
},
415+
{
416+
description: 'a public exponent less than 3',
417+
modulusLength: 2048,
418+
publicExponent: Uint8Array.of(1),
419+
},
420+
{
421+
description: 'an even public exponent',
422+
modulusLength: 2048,
423+
publicExponent: Uint8Array.of(4),
424+
},
425+
{
426+
description: 'a public exponent equal to 2^modulusLength - 1',
427+
modulusLength: 2048,
428+
publicExponent: new Uint8Array(256).fill(0xff),
429+
},
430+
];
431+
432+
[
433+
'RSASSA-PKCS1-v1_5',
434+
'RSA-PSS',
435+
'RSA-OAEP',
436+
].forEach(name => {
437+
invalidRsaKeyGenParameters.forEach(({description, ...parameters}) => {
438+
test(() => {
439+
assert_false(
440+
SubtleCrypto.supports('generateKey', {
441+
name,
442+
...parameters,
443+
hash: 'SHA-256',
444+
}),
445+
`${name} rejects ${description}`
446+
);
447+
}, `supports rejects ${name} generateKey with ${description}`);
448+
});
449+
});
450+
451+
['ECDSA', 'ECDH'].forEach(name => {
452+
test(() => {
453+
assert_false(
454+
SubtleCrypto.supports('importKey', {
455+
name,
456+
namedCurve: 'not-a-curve',
457+
}),
458+
`${name} rejects an unknown named curve`
459+
);
460+
}, `supports validates ${name} import namedCurve`);
461+
});
462+
463+
[
464+
['P-256', 256],
465+
['P-384', 384],
466+
['P-521', 528],
467+
].forEach(([namedCurve, maximumLength]) => {
468+
promise_test(async () => {
469+
const {publicKey} = await crypto.subtle.generateKey(
470+
{name: 'ECDH', namedCurve}, false, ['deriveBits']);
471+
const algorithm = {name: 'ECDH', public: publicKey};
472+
473+
assert_true(
474+
SubtleCrypto.supports('deriveBits', algorithm, maximumLength),
475+
`ECDH ${namedCurve} supports its maximum output length`
476+
);
477+
assert_false(
478+
SubtleCrypto.supports('deriveBits', algorithm, maximumLength + 1),
479+
`ECDH ${namedCurve} rejects output longer than its maximum`
480+
);
481+
}, `supports validates ECDH ${namedCurve} deriveBits length`);
482+
});
483+
484+
promise_test(async () => {
485+
const {publicKey} = await crypto.subtle.generateKey(
486+
'X25519', false, ['deriveBits']);
487+
const algorithm = {name: 'X25519', public: publicKey};
488+
489+
assert_true(
490+
SubtleCrypto.supports('deriveBits', algorithm, 256),
491+
'X25519 supports its maximum output length'
492+
);
493+
assert_false(
494+
SubtleCrypto.supports('deriveBits', algorithm, 257),
495+
'X25519 rejects output longer than its maximum'
496+
);
497+
}, 'supports validates X25519 deriveBits length');
498+
499+
promise_test(async () => {
500+
const [ecdhKeyPair, x25519KeyPair] = await Promise.all([
501+
crypto.subtle.generateKey(
502+
{name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
503+
crypto.subtle.generateKey('X25519', false, ['deriveBits']),
504+
]);
505+
506+
assert_false(
507+
SubtleCrypto.supports(
508+
'deriveBits', {name: 'ECDH', public: ecdhKeyPair.privateKey}, 256),
509+
'ECDH rejects a private public property'
510+
);
511+
assert_false(
512+
SubtleCrypto.supports(
513+
'deriveBits', {name: 'ECDH', public: x25519KeyPair.publicKey}, 256),
514+
'ECDH rejects a public property for another algorithm'
515+
);
516+
}, 'supports validates the ECDH public key');
517+
518+
promise_test(async () => {
519+
const [x25519KeyPair, ecdhKeyPair] = await Promise.all([
520+
crypto.subtle.generateKey('X25519', false, ['deriveBits']),
521+
crypto.subtle.generateKey(
522+
{name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
523+
]);
524+
525+
assert_false(
526+
SubtleCrypto.supports(
527+
'deriveBits', {name: 'X25519', public: x25519KeyPair.privateKey}, 256),
528+
'X25519 rejects a private public property'
529+
);
530+
assert_false(
531+
SubtleCrypto.supports(
532+
'deriveBits', {name: 'X25519', public: ecdhKeyPair.publicKey}, 256),
533+
'X25519 rejects a public property for another algorithm'
534+
);
535+
}, 'supports validates the X25519 public key');
536+
537+
promise_test(async () => {
538+
const [p256KeyPair, p521KeyPair] = await Promise.all([
539+
crypto.subtle.generateKey(
540+
{name: 'ECDH', namedCurve: 'P-256'}, false, ['deriveBits']),
541+
crypto.subtle.generateKey(
542+
{name: 'ECDH', namedCurve: 'P-521'}, false, ['deriveBits']),
543+
]);
544+
const derivedKeyAlgorithm = {name: 'HMAC', hash: 'SHA-256'};
545+
546+
assert_false(
547+
SubtleCrypto.supports(
548+
'deriveKey',
549+
{name: 'ECDH', public: p256KeyPair.publicKey},
550+
derivedKeyAlgorithm
551+
),
552+
'ECDH P-256 cannot derive a 512-bit HMAC key'
553+
);
554+
assert_true(
555+
SubtleCrypto.supports(
556+
'deriveKey',
557+
{name: 'ECDH', public: p521KeyPair.publicKey},
558+
derivedKeyAlgorithm
559+
),
560+
'ECDH P-521 can derive a 512-bit HMAC key'
561+
);
562+
}, 'supports derives the ECDH output limit from the public curve');
563+
351564
// Test some specific combinations that should work
352565
test(() => {
353566
// RSA algorithms

WebCryptoAPI/util/supports.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,9 @@ function runSupportsTests(algorithms, operations) {
3333
algorithm = algorithmInfo.encryptParams || algorithmName;
3434
break;
3535
case 'deriveBits':
36-
algorithm = algorithmInfo.deriveBitsParams || algorithmName;
37-
if (algorithm?.public instanceof Promise) {
38-
algorithm.public = (await algorithm.public).publicKey;
39-
}
36+
algorithm = algorithmInfo.deriveBitsParamsFactory ?
37+
await algorithmInfo.deriveBitsParamsFactory() :
38+
algorithmInfo.deriveBitsParams || algorithmName;
4039
if (algorithmName === 'PBKDF2' || algorithmName === 'HKDF') {
4140
lengthOrAdditionalAlgorithm = 256;
4241
}

0 commit comments

Comments
 (0)