Skip to content

Commit 32ba6d5

Browse files
panvaaduh95
authored andcommitted
crypto: use backend cSHAKE and KMAC
Require cSHAKE and KMAC output lengths and KMAC key lengths to be multiples of 8 bits. KMAC keys must be at least 32 bits. Share these restrictions between operations and supports. Use OpenSSL's KMAC provider for all supported inputs and its cSHAKE implementation for non-empty function names or customization strings. Keep using SHAKE when both cSHAKE parameters are empty. Remove the custom Keccak framing, partial-bit handling, and short-key fallback. Document the OpenSSL 4.0 requirement for non-empty cSHAKE parameters and reject customization strings containing null bytes. Keep the documented 512-byte customization limit and let backend failures reach callers. Signed-off-by: Filip Skokan <panva.ip@gmail.com> Assisted-by: Codex PR-URL: #66237 Backport-PR-URL: #66391 Reviewed-By: Richard Lau <richard.lau@ibm.com> Reviewed-By: Xuguang Mei <meixuguang@gmail.com>
1 parent 67ccff6 commit 32ba6d5

25 files changed

Lines changed: 367 additions & 955 deletions

‎doc/api/webcrypto.md‎

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,9 @@ changes:
18301830
description: Renamed `cShakeParams.length` to `cShakeParams.outputLength`.
18311831
-->
18321832
1833+
When both `functionName` and `customization` are empty or `undefined`, cSHAKE is
1834+
equivalent to plain SHAKE.
1835+
18331836
#### `cShakeParams.name`
18341837
18351838
<!-- YAML
@@ -1844,7 +1847,8 @@ added: v24.7.0
18441847
added: v25.9.0
18451848
-->
18461849
1847-
* Type: {number} represents the requested output length in bits.
1850+
* Type: {number} represents the requested output length in bits. Must be a
1851+
multiple of 8.
18481852
18491853
#### `cShakeParams.functionName`
18501854
@@ -1859,9 +1863,10 @@ changes:
18591863
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
18601864
18611865
The `functionName` member represents the NIST function-name byte string used to
1862-
domain-separate functions built on top of cSHAKE. Accepted values are:
1866+
domain-separate functions built on top of cSHAKE. Non-empty values require
1867+
OpenSSL 4.0 or later. Accepted values are:
18631868
1864-
* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
1869+
* empty or `undefined`
18651870
* the ASCII byte sequence `'KMAC'`
18661871
* the ASCII byte sequence `'TupleHash'`
18671872
* the ASCII byte sequence `'ParallelHash'`
@@ -1878,11 +1883,11 @@ changes:
18781883
18791884
* Type: {ArrayBuffer|TypedArray|DataView|Buffer|undefined}
18801885
1881-
The `customization` member represents the customization data. Accepted
1882-
values are:
1886+
The `customization` member represents the customization data. Non-empty values
1887+
require OpenSSL 4.0 or later. Accepted values are:
18831888
1884-
* empty or `undefined`, in which case cSHAKE is equivalent to plain SHAKE
1885-
* up to 512 bytes of arbitrary data
1889+
* empty or `undefined`
1890+
* up to 512 bytes of data without null bytes
18861891
18871892
### Class: `EcdhKeyDeriveParams`
18881893
@@ -2332,7 +2337,7 @@ added: v24.8.0
23322337
* Type: {number}
23332338
23342339
The optional number of bits in the KMAC key. This is optional and should
2335-
be omitted for most cases.
2340+
be omitted for most cases. The key length must be at least 32 and a multiple of 8.
23362341
23372342
#### `kmacImportParams.name`
23382343
@@ -2382,7 +2387,8 @@ added: v24.8.0
23822387
23832388
The number of bits to generate for the KMAC key. If omitted,
23842389
the length will be determined by the KMAC algorithm used.
2385-
This is optional and should be omitted for most cases.
2390+
This is optional and should be omitted for most cases. Must be at least 32 and a
2391+
multiple of 8.
23862392
23872393
#### `kmacKeyGenParams.name`
23882394
@@ -2416,7 +2422,8 @@ added: v24.8.0
24162422
added: v25.9.0
24172423
-->
24182424
2419-
* Type: {number} represents the requested output length in bits.
2425+
* Type: {number} represents the requested output length in bits. Must be a
2426+
multiple of 8.
24202427
24212428
#### `kmacParams.customization`
24222429

‎lib/internal/crypto/hash.js‎

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ const {
66
StringPrototypeReplace,
77
StringPrototypeToLowerCase,
88
Symbol,
9-
TypedArrayPrototypeGetBuffer,
109
TypedArrayPrototypeIncludes,
1110
} = primordials;
1211

1312
const {
14-
CShakeJob,
1513
Hash: _Hash,
1614
HashJob,
1715
Hmac: _Hmac,
@@ -24,10 +22,7 @@ const {
2422
const {
2523
getStringOption,
2624
jobPromise,
27-
jobPromiseThen,
2825
normalizeHashName,
29-
numBitsToBytes,
30-
truncateToBitLength,
3126
validateAlgorithm,
3227
validateMaxBufferLength,
3328
kHandle,
@@ -43,7 +38,6 @@ const {
4338
} = require('internal/crypto/keys');
4439

4540
const {
46-
lazyDOMException,
4741
normalizeEncoding,
4842
encodingsMap,
4943
getDeprecationWarningEmitter,
@@ -284,30 +278,20 @@ function asyncDigest(algorithm, data) {
284278
const outputLength = algorithm.outputLength;
285279
if (getOptionalByteLength(algorithm.functionName) ||
286280
getOptionalByteLength(algorithm.customization)) {
287-
if (CShakeJob === undefined) {
288-
throw lazyDOMException(
289-
'Non-empty CShakeParams functionName or customization is not supported',
290-
'NotSupportedError');
291-
}
292-
293-
return jobPromise(() => new CShakeJob(
281+
return jobPromise(() => new HashJob(
294282
kCryptoJobWebCrypto,
295-
algorithm.name,
283+
StringPrototypeToLowerCase(algorithm.name),
296284
data,
285+
outputLength,
297286
algorithm.functionName,
298-
algorithm.customization,
299-
outputLength));
287+
algorithm.customization));
300288
}
301289

302-
const bits = jobPromise(() => new HashJob(
290+
return jobPromise(() => new HashJob(
303291
kCryptoJobWebCrypto,
304292
normalizeHashName(algorithm.name),
305293
data,
306-
numBitsToBytes(outputLength) * 8));
307-
if (outputLength % 8 === 0)
308-
return bits;
309-
return jobPromiseThen(bits, (bits) =>
310-
TypedArrayPrototypeGetBuffer(truncateToBitLength(outputLength, bits)));
294+
outputLength));
311295
}
312296
case 'TurboSHAKE128':
313297
// Fall through

‎lib/internal/crypto/mac.js‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,6 @@ function kmacSignVerify(key, data, algorithm, signature) {
193193
getCryptoKeyHandle(key),
194194
algorithm.name,
195195
algorithm.customization,
196-
getCryptoKeyAlgorithm(key).length,
197196
algorithm.outputLength,
198197
data,
199198
signature));

‎lib/internal/crypto/util.js‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ function validateMaxBufferLength(data, name, max = kMaxBufferLength) {
721721
}
722722

723723
function validateKmacKeyLength(length) {
724-
if ((length < 32 || length % 8) && isFips())
724+
if (length < 32 || length % 8 !== 0)
725725
throw lazyDOMException('Invalid key length', 'NotSupportedError');
726726
}
727727

‎lib/internal/crypto/webcrypto.js‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const {
2323
} = primordials;
2424

2525
const {
26-
CShakeJob,
2726
kWebCryptoKeyFormatRaw,
2827
kWebCryptoKeyFormatPKCS8,
2928
kWebCryptoKeyFormatSPKI,
@@ -72,7 +71,6 @@ const {
7271
prepareWebCryptoResult,
7372
validateAlgorithm,
7473
validateMaxBufferLength,
75-
getOptionalByteLength,
7674
} = require('internal/crypto/util');
7775

7876
const {
@@ -1923,15 +1921,7 @@ function check(op, alg, length) {
19231921
}
19241922

19251923
switch (op) {
1926-
case 'digest': {
1927-
if ((normalizedAlgorithm.name === 'cSHAKE128' ||
1928-
normalizedAlgorithm.name === 'cSHAKE256') &&
1929-
(getOptionalByteLength(normalizedAlgorithm.functionName) ||
1930-
getOptionalByteLength(normalizedAlgorithm.customization))) {
1931-
return CShakeJob !== undefined;
1932-
}
1933-
return true;
1934-
}
1924+
case 'digest':
19351925
case 'decapsulate':
19361926
case 'decrypt':
19371927
case 'encapsulate':

‎lib/internal/crypto/webidl.js‎

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ const {
1111
StringPrototypeCharCodeAt,
1212
StringPrototypeSplit,
1313
StringPrototypeToLowerCase,
14+
TypedArrayPrototypeIncludes,
1415
} = primordials;
1516

1617
const {
1718
lazyDOMException,
1819
kEmptyObject,
1920
} = require('internal/util');
20-
const {
21-
isUint32,
22-
} = require('internal/validators');
2321
const {
2422
getCryptoKeyAlgorithm,
2523
getCryptoKeyType,
@@ -30,9 +28,9 @@ const {
3028
validateMaxBufferLength,
3129
getBufferSourceByteLength,
3230
getBufferSourceBytes,
31+
getHashes,
3332
isFips,
3433
kNamedCurveAliases,
35-
numBitsToBytes,
3634
validateKmacKeyLength,
3735
} = require('internal/crypto/util');
3836
const {
@@ -317,20 +315,20 @@ function validateZeroLength(parameterName) {
317315
}
318316

319317
function validateCShakeOutputLength(V) {
320-
if (!isUint32(numBitsToBytes(V) * 8)) {
318+
if (V % 8 !== 0) {
321319
throw lazyDOMException(
322320
'Invalid CShakeParams outputLength',
323-
'OperationError');
321+
'NotSupportedError');
324322
}
325323
}
326324

327325
const kCShakeFunctionNames = ['KMAC', 'TupleHash', 'ParallelHash'];
328326

329-
function validateCShakeFunctionName(V) {
327+
function validateCShakeFunctionName(V, dict) {
330328
const length = getBufferSourceByteLength(V);
331329
if (length === 0) return;
332330

333-
if (!isFips()) {
331+
if (ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name))) {
334332
const bytes = getBufferSourceBytes(V);
335333
for (let i = 0; i < kCShakeFunctionNames.length; i++) {
336334
const functionName = kCShakeFunctionNames[i];
@@ -349,12 +347,17 @@ function validateCShakeFunctionName(V) {
349347
'NotSupportedError');
350348
}
351349

352-
function validateCShakeCustomization(V) {
353-
if (isFips() && getBufferSourceByteLength(V) !== 0)
350+
function validateCShakeCustomization(V, dict) {
351+
if (getBufferSourceByteLength(V) === 0) return;
352+
if (!ArrayPrototypeIncludes(getHashes(), StringPrototypeToLowerCase(dict.name)))
354353
throw lazyDOMException(
355354
'Unsupported CShakeParams customization',
356355
'NotSupportedError');
357356
validateMaxBufferLength(V, 'CShakeParams.customization', 512);
357+
if (TypedArrayPrototypeIncludes(getBufferSourceBytes(V), 0))
358+
throw lazyDOMException(
359+
'Unsupported CShakeParams customization',
360+
'NotSupportedError');
358361
}
359362

360363
converters.RsaPssParams = createAlgorithmDictionaryConverter(
@@ -814,7 +817,7 @@ converters.KmacParams = createAlgorithmDictionaryConverter(
814817
converter: (V, opts) =>
815818
converters['unsigned long'](V, enforceRangeOptions(opts)),
816819
validator: (V) => {
817-
if ((V === 0 || V % 8) && isFips())
820+
if (V % 8 !== 0 || (V === 0 && isFips()))
818821
throw lazyDOMException(
819822
'Invalid KmacParams outputLength',
820823
'NotSupportedError');

0 commit comments

Comments
 (0)