Skip to content

Commit 327618c

Browse files
committed
crypto: add SHAKE Web Cryptography digest algorithms
PR-URL: nodejs#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 90ec543 commit 327618c

12 files changed

Lines changed: 197 additions & 46 deletions

File tree

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.
@@ -92,6 +95,8 @@ WICG proposal:
9295

9396
Algorithms:
9497

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

lib/internal/crypto/hash.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,10 +209,15 @@ async function asyncDigest(algorithm, data) {
209209
case 'SHA-384':
210210
// Fall through
211211
case 'SHA-512':
212+
// Fall through
213+
case 'cSHAKE128':
214+
// Fall through
215+
case 'cSHAKE256':
212216
return jobPromise(() => new HashJob(
213217
kCryptoJobAsync,
214218
normalizeHashName(algorithm.name),
215-
data));
219+
data,
220+
algorithm.length));
216221
}
217222

218223
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)