Skip to content

Commit 247d017

Browse files
panvatargos
authored andcommitted
crypto: add SHAKE Web Cryptography digest algorithms
PR-URL: #59365 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ethan Arrowood <ethan@arrowood.dev> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
1 parent f4fbcca commit 247d017

File tree

12 files changed

+197
-46
lines changed

12 files changed

+197
-46
lines changed

doc/api/webcrypto.md

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
<!-- YAML
44
changes:
5+
- version: REPLACEME
6+
pr-url: https://github.com/nodejs/node/pull/59365
7+
description: SHAKE algorithms are now supported.
58
- version: REPLACEME
69
pr-url: https://github.com/nodejs/node/pull/59365
710
description: ML-DSA algorithms are now supported.
@@ -91,6 +94,8 @@ WICG proposal:
9194

9295
Algorithms:
9396

97+
* `'cSHAKE128'`
98+
* `'cSHAKE256'`
9499
* `'ML-DSA-44'`[^openssl35]
95100
* `'ML-DSA-65'`[^openssl35]
96101
* `'ML-DSA-87'`[^openssl35]
@@ -472,6 +477,8 @@ implementation and the APIs supported for each:
472477
| `'AES-CTR'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
473478
| `'AES-GCM'` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | | | | | |
474479
| `'AES-KW'` | ✔ | ✔ | ✔ | | | ✔ | ✔ | | | | | |
480+
| `'cSHAKE128'`[^modern-algos] | | | | | | | | | | | | ✔ |
481+
| `'cSHAKE256'`[^modern-algos] | | | | | | | | | | | | ✔ |
475482
| `'ECDH'` | ✔ | ✔ | ✔ | | | | | ✔ | ✔ | | | |
476483
| `'ECDSA'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
477484
| `'Ed25519'` | ✔ | ✔ | ✔ | | | | | | | ✔ | ✔ | |
@@ -800,9 +807,13 @@ The algorithms currently supported include:
800807
801808
<!-- YAML
802809
added: v15.0.0
810+
changes:
811+
- version: REPLACEME
812+
pr-url: https://github.com/nodejs/node/pull/59365
813+
description: SHAKE algorithms are now supported.
803814
-->
804815
805-
* `algorithm` {string|Algorithm}
816+
* `algorithm` {string|Algorithm|CShakeParams}
806817
* `data` {ArrayBuffer|TypedArray|DataView|Buffer}
807818
* Returns: {Promise} Fulfills with an {ArrayBuffer} upon success.
808819
@@ -812,6 +823,8 @@ with an {ArrayBuffer} containing the computed digest.
812823
813824
If `algorithm` is provided as a {string}, it must be one of:
814825
826+
* `'cSHAKE128'`[^modern-algos]
827+
* `'cSHAKE256'`[^modern-algos]
815828
* `'SHA-1'`
816829
* `'SHA-256'`
817830
* `'SHA-384'`
@@ -1423,6 +1436,53 @@ the message.
14231436
The Node.js Web Crypto API implementation only supports zero-length context
14241437
which is equivalent to not providing context at all.
14251438
1439+
### Class: `CShakeParams`
1440+
1441+
<!-- YAML
1442+
added: REPLACEME
1443+
-->
1444+
1445+
#### `cShakeParams.customization`
1446+
1447+
<!-- YAML
1448+
added: REPLACEME
1449+
-->
1450+
1451+
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
1452+
1453+
The `customization` member represents the customization string.
1454+
The Node.js Web Crypto API implementation only supports zero-length customization
1455+
which is equivalent to not providing customization at all.
1456+
1457+
#### `cShakeParams.functionName`
1458+
1459+
<!-- YAML
1460+
added: REPLACEME
1461+
-->
1462+
1463+
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
1464+
1465+
The `functionName` member represents represents the function name, used by NIST to define
1466+
functions based on cSHAKE.
1467+
The Node.js Web Crypto API implementation only supports zero-length functionName
1468+
which is equivalent to not providing functionName at all.
1469+
1470+
#### `cShakeParams.length`
1471+
1472+
<!-- YAML
1473+
added: REPLACEME
1474+
-->
1475+
1476+
* Type: {number} represents the requested output length in bits.
1477+
1478+
#### `cShakeParams.name`
1479+
1480+
<!-- YAML
1481+
added: REPLACEME
1482+
-->
1483+
1484+
* Type: {string} Must be `'cSHAKE128'`[^modern-algos] or `'cSHAKE256'`[^modern-algos]
1485+
14261486
### Class: `EcdhKeyDeriveParams`
14271487
14281488
<!-- YAML

lib/internal/crypto/hash.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,15 @@ async function asyncDigest(algorithm, data) {
211211
case 'SHA-384':
212212
// Fall through
213213
case 'SHA-512':
214+
// Fall through
215+
case 'cSHAKE128':
216+
// Fall through
217+
case 'cSHAKE256':
214218
return jobPromise(() => new HashJob(
215219
kCryptoJobAsync,
216220
normalizeHashName(algorithm.name),
217-
data));
221+
data,
222+
algorithm.length));
218223
}
219224

220225
throw lazyDOMException('Unrecognized algorithm name', 'NotSupportedError');

lib/internal/crypto/hashnames.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const kHashNames = {
4949
[kHashContextJwkRsaOaep]: 'RSA-OAEP-512',
5050
[kHashContextJwkHmac]: 'HS512',
5151
},
52+
shake128: {
53+
[kHashContextNode]: 'shake128',
54+
[kHashContextWebCrypto]: 'cSHAKE128',
55+
},
56+
shake256: {
57+
[kHashContextNode]: 'shake256',
58+
[kHashContextWebCrypto]: 'cSHAKE256',
59+
},
5260
};
5361

5462
{

lib/internal/crypto/mac.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async function hmacGenerateKey(algorithm, extractable, keyUsages) {
6262

6363
return new InternalCryptoKey(
6464
key,
65-
{ name, length, hash: { name: hash.name } },
65+
{ name, length, hash },
6666
ArrayFrom(usageSet),
6767
extractable);
6868
}

lib/internal/crypto/rsa.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ async function rsaKeyGenerate(
161161
name,
162162
modulusLength,
163163
publicExponent,
164-
hash: { name: hash.name },
164+
hash,
165165
};
166166

167167
let publicUsages;

lib/internal/crypto/util.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ const experimentalAlgorithms = ObjectEntries({
285285
importKey: null,
286286
exportKey: null,
287287
},
288+
'cSHAKE128': { digest: 'CShakeParams' },
289+
'cSHAKE256': { digest: 'CShakeParams' },
288290
});
289291

290292
for (const { 0: algorithm, 1: nid } of [
@@ -338,6 +340,10 @@ const simpleAlgorithmDictionaries = {
338340
RsaOaepParams: { label: 'BufferSource' },
339341
RsaHashedImportParams: { hash: 'HashAlgorithmIdentifier' },
340342
EcKeyImportParams: {},
343+
CShakeParams: {
344+
functionName: 'BufferSource',
345+
customization: 'BufferSource',
346+
},
341347
};
342348

343349
function validateMaxBufferLength(data, name) {

lib/internal/crypto/webidl.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ converters.object = (V, opts) => {
192192

193193
const isNonSharedArrayBuffer = isArrayBuffer;
194194

195+
function ensureSHA(V, label) {
196+
if (
197+
typeof V === 'string' ?
198+
!V.toLowerCase().startsWith('sha') :
199+
V.name?.toLowerCase?.().startsWith('sha') === false
200+
)
201+
throw lazyDOMException(
202+
`Only SHA hashes are supported in ${label}`, 'NotSupportedError');
203+
}
204+
195205
converters.Uint8Array = (V, opts = kEmptyObject) => {
196206
if (!ArrayBufferIsView(V) ||
197207
TypedArrayPrototypeGetSymbolToStringTag(V) !== 'Uint8Array') {
@@ -393,6 +403,7 @@ converters.RsaHashedKeyGenParams = createDictionaryConverter(
393403
{
394404
key: 'hash',
395405
converter: converters.HashAlgorithmIdentifier,
406+
validator: (V, dict) => ensureSHA(V, 'RsaHashedKeyGenParams'),
396407
required: true,
397408
},
398409
]);
@@ -403,6 +414,7 @@ converters.RsaHashedImportParams = createDictionaryConverter(
403414
{
404415
key: 'hash',
405416
converter: converters.HashAlgorithmIdentifier,
417+
validator: (V, dict) => ensureSHA(V, 'RsaHashedImportParams'),
406418
required: true,
407419
},
408420
]);
@@ -449,6 +461,7 @@ converters.HmacKeyGenParams = createDictionaryConverter(
449461
{
450462
key: 'hash',
451463
converter: converters.HashAlgorithmIdentifier,
464+
validator: (V, dict) => ensureSHA(V, 'HmacKeyGenParams'),
452465
required: true,
453466
},
454467
{
@@ -503,6 +516,7 @@ converters.EcdsaParams = createDictionaryConverter(
503516
{
504517
key: 'hash',
505518
converter: converters.HashAlgorithmIdentifier,
519+
validator: (V, dict) => ensureSHA(V, 'EcdsaParams'),
506520
required: true,
507521
},
508522
]);
@@ -513,6 +527,7 @@ converters.HmacImportParams = createDictionaryConverter(
513527
{
514528
key: 'hash',
515529
converter: converters.HashAlgorithmIdentifier,
530+
validator: (V, dict) => ensureSHA(V, 'HmacImportParams'),
516531
required: true,
517532
},
518533
{
@@ -573,6 +588,7 @@ converters.HkdfParams = createDictionaryConverter(
573588
{
574589
key: 'hash',
575590
converter: converters.HashAlgorithmIdentifier,
591+
validator: (V, dict) => ensureSHA(V, 'HkdfParams'),
576592
required: true,
577593
},
578594
{
@@ -587,12 +603,40 @@ converters.HkdfParams = createDictionaryConverter(
587603
},
588604
]);
589605

606+
converters.CShakeParams = createDictionaryConverter(
607+
'CShakeParams', [
608+
...new SafeArrayIterator(dictAlgorithm),
609+
{
610+
key: 'length',
611+
converter: (V, opts) =>
612+
converters['unsigned long'](V, { ...opts, enforceRange: true }),
613+
validator: (V, opts) => {
614+
// The Web Crypto spec allows for SHAKE output length that are not multiples of
615+
// 8. We don't.
616+
if (V % 8)
617+
throw lazyDOMException('Unsupported CShakeParams length', 'NotSupportedError');
618+
},
619+
required: true,
620+
},
621+
{
622+
key: 'functionName',
623+
converter: converters.BufferSource,
624+
validator: validateZeroLength('CShakeParams.functionName'),
625+
},
626+
{
627+
key: 'customization',
628+
converter: converters.BufferSource,
629+
validator: validateZeroLength('CShakeParams.customization'),
630+
},
631+
]);
632+
590633
converters.Pbkdf2Params = createDictionaryConverter(
591634
'Pbkdf2Params', [
592635
...new SafeArrayIterator(dictAlgorithm),
593636
{
594637
key: 'hash',
595638
converter: converters.HashAlgorithmIdentifier,
639+
validator: (V, dict) => ensureSHA(V, 'Pbkdf2Params'),
596640
required: true,
597641
},
598642
{

test/parallel/test-webcrypto-derivebits-hkdf.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,6 @@ async function testDeriveBitsBadHash(
306306
hash: 'PBKDF2'
307307
},
308308
baseKeys[size], 256), {
309-
message: /Unrecognized algorithm name/,
310309
name: 'NotSupportedError',
311310
}),
312311
]);
@@ -437,10 +436,7 @@ async function testDeriveKeyBadHash(
437436
keyType,
438437
true,
439438
usages),
440-
{
441-
message: /Unrecognized algorithm name/,
442-
name: 'NotSupportedError',
443-
}),
439+
{ name: 'NotSupportedError' }),
444440
assert.rejects(
445441
subtle.deriveKey(
446442
{
@@ -451,10 +447,7 @@ async function testDeriveKeyBadHash(
451447
keyType,
452448
true,
453449
usages),
454-
{
455-
message: /Unrecognized algorithm name/,
456-
name: 'NotSupportedError',
457-
}),
450+
{ name: 'NotSupportedError' }),
458451
]);
459452
}
460453

0 commit comments

Comments
 (0)