Skip to content

Commit f3afdcc

Browse files
committed
crypto: move DEP0198 to End-of-Life
Signed-off-by: Filip Skokan <panva.ip@gmail.com>
1 parent 8c8c438 commit f3afdcc

10 files changed

Lines changed: 193 additions & 105 deletions

doc/api/crypto.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3771,6 +3771,10 @@ and description of each available elliptic curve.
37713771
<!-- YAML
37723772
added: v0.1.92
37733773
changes:
3774+
- version: REPLACEME
3775+
pr-url: https://github.com/nodejs/node/pull/64000
3776+
description: The `outputLength` option is now required for XOF
3777+
hash functions without default output lengths.
37743778
- version: v12.8.0
37753779
pr-url: https://github.com/nodejs/node/pull/28805
37763780
description: The `outputLength` option was added for XOF hash functions.
@@ -3783,7 +3787,8 @@ changes:
37833787
Creates and returns a `Hash` object that can be used to generate hash digests
37843788
using the given `algorithm`. Optional `options` argument controls stream
37853789
behavior. For XOF hash functions such as `'shake256'`, the `outputLength` option
3786-
can be used to specify the desired output length in bytes.
3790+
specifies the desired output length in bytes. It is required for XOF hash
3791+
functions without a default output length.
37873792

37883793
The `algorithm` is dependent on the available algorithms supported by the
37893794
version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
@@ -4889,6 +4894,10 @@ added:
48894894
- v21.7.0
48904895
- v20.12.0
48914896
changes:
4897+
- version: REPLACEME
4898+
pr-url: https://github.com/nodejs/node/pull/64000
4899+
description: The `outputLength` option is now required for XOF
4900+
hash functions without default output lengths.
48924901
- version:
48934902
- v25.5.0
48944903
- v24.13.1
@@ -4909,7 +4918,8 @@ changes:
49094918
* `outputEncoding` {string} [Encoding][encoding] used to encode the
49104919
returned digest. **Default:** `'hex'`.
49114920
* `outputLength` {number} For XOF hash functions such as 'shake256',
4912-
the outputLength option can be used to specify the desired output length in bytes.
4921+
specifies the desired output length in bytes. This option is required for
4922+
XOF hash functions without a default output length.
49134923
* Returns: {string|Buffer}
49144924

49154925
A utility for creating one-shot hash digests of data. It can be faster than

doc/api/deprecations.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4354,6 +4354,9 @@ npx codemod@latest @nodejs/types-is-native-error
43544354

43554355
<!-- YAML
43564356
changes:
4357+
- version: REPLACEME
4358+
pr-url: https://github.com/nodejs/node/pull/64000
4359+
description: End-of-Life.
43574360
- version: v25.0.0
43584361
pr-url: https://github.com/nodejs/node/pull/59008
43594362
description: Runtime deprecation.
@@ -4365,9 +4368,10 @@ changes:
43654368
description: Documentation-only deprecation with support for `--pending-deprecation`.
43664369
-->
43674370

4368-
Type: Runtime
4371+
Type: End-of-Life
43694372

4370-
Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength` is deprecated.
4373+
Creating SHAKE-128 and SHAKE-256 digests without an explicit
4374+
`options.outputLength` is no longer supported.
43714375

43724376
### DEP0199: `require('node:_http_*')`
43734377

lib/internal/crypto/hash.js

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
const {
44
FunctionPrototypeCall,
55
ObjectSetPrototypeOf,
6-
StringPrototypeReplace,
76
StringPrototypeToLowerCase,
87
Symbol,
98
TypedArrayPrototypeGetBuffer,
@@ -73,25 +72,6 @@ const LazyTransform = require('internal/streams/lazy_transform');
7372
const kState = Symbol('kState');
7473
const kFinalized = Symbol('kFinalized');
7574

76-
/**
77-
* @param {string} name
78-
* @returns {string}
79-
*/
80-
function normalizeAlgorithm(name) {
81-
return StringPrototypeReplace(StringPrototypeToLowerCase(name), '-', '');
82-
}
83-
84-
const maybeEmitDeprecationWarning = getDeprecationWarningEmitter(
85-
'DEP0198',
86-
'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
87-
undefined,
88-
false,
89-
(algorithm) => {
90-
const normalized = normalizeAlgorithm(algorithm);
91-
return normalized === 'shake128' || normalized === 'shake256';
92-
},
93-
);
94-
9575
const emitHmacDigestDeprecation = getDeprecationWarningEmitter(
9676
'DEP0206',
9777
'Calling Hmac.digest() more than once is deprecated.',
@@ -117,9 +97,6 @@ function Hash(algorithm, options) {
11797
this[kState] = {
11898
[kFinalized]: false,
11999
};
120-
if (!isCopy && xofLen === undefined) {
121-
maybeEmitDeprecationWarning(algorithm);
122-
}
123100
FunctionPrototypeCall(LazyTransform, this, options);
124101
}
125102

@@ -334,10 +311,6 @@ function hash(algorithm, input, options) {
334311
outputLength += 0;
335312
}
336313

337-
if (outputLength === undefined) {
338-
maybeEmitDeprecationWarning(algorithm);
339-
}
340-
341314
return oneShotDigest(algorithm, getCachedHashId(algorithm), getHashCache(),
342315
input, normalized, encodingsMap[normalized], outputLength);
343316
}

src/crypto/crypto_hash.cc

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,39 @@ const EVP_MD* GetDigestImplementation(Environment* env,
246246
#endif
247247
}
248248

249+
void MarkInvalidXofLength() {
250+
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
251+
}
252+
253+
// DEP0198 EOL requires XOFs without an OpenSSL-defined default output length
254+
// to fail when outputLength is omitted. OpenSSL 3.4 and later report a digest
255+
// size of 0 for such XOFs, including SHAKE, which had weak historical defaults
256+
// before OpenSSL 3.4. For older OpenSSL versions, identify those resolved
257+
// EVP_MD values explicitly to keep the missing-outputLength error
258+
// version-independent.
259+
#if !OPENSSL_VERSION_PREREQ(3, 4)
260+
bool IsShakeDigest(const EVP_MD* md) {
261+
#if OPENSSL_VERSION_MAJOR >= 3
262+
return EVP_MD_is_a(md, "SHAKE128") || EVP_MD_is_a(md, "SHAKE256");
263+
#else
264+
const char* name = OBJ_nid2sn(EVP_MD_type(md));
265+
return name != nullptr &&
266+
(strcmp(name, "SHAKE128") == 0 || strcmp(name, "SHAKE256") == 0);
267+
#endif
268+
}
269+
#endif
270+
271+
bool ShouldRejectMissingXofLength(const EVP_MD* md, size_t default_length) {
272+
if (default_length == 0) return true;
273+
274+
#if !OPENSSL_VERSION_PREREQ(3, 4)
275+
return IsShakeDigest(md);
276+
#else
277+
static_cast<void>(md);
278+
return false;
279+
#endif
280+
}
281+
249282
// crypto.digest(algorithm, algorithmId, algorithmCache,
250283
// input, outputEncoding, outputEncodingId, outputLength)
251284
void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
@@ -287,18 +320,10 @@ void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
287320
} else if (is_xof) {
288321
if (!args[6]->IsUndefined()) {
289322
output_length = args[6].As<Uint32>()->Value();
290-
} else if (output_length == 0) {
291-
// This is to handle OpenSSL 3.4's breaking change in SHAKE128/256
292-
// default lengths
293-
// TODO(@panva): remove this behaviour when DEP0198 is End-Of-Life
294-
const char* name = OBJ_nid2sn(EVP_MD_type(md));
295-
if (name != nullptr) {
296-
if (strcmp(name, "SHAKE128") == 0) {
297-
output_length = 16;
298-
} else if (strcmp(name, "SHAKE256") == 0) {
299-
output_length = 32;
300-
}
301-
}
323+
} else if (ShouldRejectMissingXofLength(md, output_length)) {
324+
MarkInvalidXofLength();
325+
return ThrowCryptoError(
326+
env, ERR_get_error(), "Digest method not supported");
302327
}
303328
}
304329

@@ -387,6 +412,12 @@ void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
387412
void Hash::New(const FunctionCallbackInfo<Value>& args) {
388413
Environment* env = Environment::GetCurrent(args);
389414

415+
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
416+
if (!args[1]->IsUndefined()) {
417+
CHECK(args[1]->IsUint32());
418+
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
419+
}
420+
390421
const Hash* orig = nullptr;
391422
const EVP_MD* md = nullptr;
392423
if (args[0]->IsObject()) {
@@ -397,12 +428,6 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {
397428
md = GetDigestImplementation(env, args[0], args[2], args[3]);
398429
}
399430

400-
Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
401-
if (!args[1]->IsUndefined()) {
402-
CHECK(args[1]->IsUint32());
403-
xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
404-
}
405-
406431
Hash* hash = new Hash(env, args.This());
407432
if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
408433
return ThrowCryptoError(env, ERR_get_error(),
@@ -423,18 +448,11 @@ bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
423448

424449
md_len_ = mdctx_.getDigestSize();
425450

426-
// This is to handle OpenSSL 3.4's breaking change in SHAKE128/256
427-
// default lengths
428-
// TODO(@panva): remove this behaviour when DEP0198 is End-Of-Life
429-
if (mdctx_.hasXofFlag() && !xof_md_len.IsJust() && md_len_ == 0) {
430-
const char* name = OBJ_nid2sn(EVP_MD_type(md));
431-
if (name != nullptr) {
432-
if (strcmp(name, "SHAKE128") == 0) {
433-
md_len_ = 16;
434-
} else if (strcmp(name, "SHAKE256") == 0) {
435-
md_len_ = 32;
436-
}
437-
}
451+
if (mdctx_.hasXofFlag() && !xof_md_len.IsJust() &&
452+
ShouldRejectMissingXofLength(md, md_len_)) {
453+
MarkInvalidXofLength();
454+
mdctx_.reset();
455+
return false;
438456
}
439457

440458
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {

test/parallel/test-crypto-default-shake-lengths-oneshot.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,34 @@ const common = require('../common');
44
if (!common.hasCrypto)
55
common.skip('missing crypto');
66

7-
if (process.features.openssl_is_boringssl)
8-
common.skip('not supported by BoringSSL');
7+
const {
8+
getHashes,
9+
hash,
10+
} = require('crypto');
911

10-
const { hash } = require('crypto');
12+
if (!getHashes().includes('shake128'))
13+
common.skip('unsupported shake128 test');
1114

12-
common.expectWarning({
13-
DeprecationWarning: {
14-
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
15-
}
16-
});
15+
const assert = require('assert');
1716

18-
{
19-
hash('shake128', 'test');
17+
const invalidXofLength = {
18+
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
19+
name: 'Error',
20+
message: /not XOF or invalid length/,
21+
};
22+
23+
const shakeAlgorithms = [
24+
'shake128',
25+
'SHAKE128',
26+
'shake256',
27+
'SHAKE256',
28+
];
29+
30+
for (const algorithm of shakeAlgorithms) {
31+
assert.throws(() => hash(algorithm, 'test'), invalidXofLength);
32+
assert.throws(() => hash(algorithm, 'test', 'hex'), invalidXofLength);
33+
assert.throws(
34+
() => hash(algorithm, 'test', { outputEncoding: 'buffer' }),
35+
invalidXofLength);
36+
assert.throws(() => hash(algorithm, 'test', {}), invalidXofLength);
2037
}

test/parallel/test-crypto-default-shake-lengths.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,39 @@ const common = require('../common');
44
if (!common.hasCrypto)
55
common.skip('missing crypto');
66

7-
const crypto = require('crypto');
8-
if (!crypto.getHashes().includes('shake128')) {
7+
const {
8+
createHash,
9+
getHashes,
10+
} = require('crypto');
11+
12+
if (!getHashes().includes('shake128'))
913
common.skip('unsupported shake128 test');
10-
}
1114

12-
const { createHash } = require('crypto');
15+
const assert = require('assert');
16+
17+
const invalidXofLength = {
18+
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
19+
name: 'Error',
20+
message: /not XOF or invalid length/,
21+
};
1322

14-
common.expectWarning({
15-
DeprecationWarning: {
16-
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
17-
}
18-
});
23+
const shakeAlgorithms = [
24+
'shake128',
25+
'SHAKE128',
26+
'shake256',
27+
'SHAKE256',
28+
];
1929

20-
{
21-
createHash('shake128').update('test').digest();
30+
for (const algorithm of shakeAlgorithms) {
31+
assert.throws(() => createHash(algorithm), invalidXofLength);
32+
assert.throws(() => createHash(algorithm, null), invalidXofLength);
33+
assert.throws(() => createHash(algorithm, {}), invalidXofLength);
2234
}
35+
36+
const shake128 = createHash('shake128', { outputLength: 5 });
37+
const shake128Copy = shake128.copy({ outputLength: 5 });
38+
39+
assert.throws(() => shake128.copy(), invalidXofLength);
40+
assert.throws(() => shake128.copy(null), invalidXofLength);
41+
assert.throws(() => shake128.copy({}), invalidXofLength);
42+
assert.throws(() => shake128Copy.copy(), invalidXofLength);

test/parallel/test-crypto-hash.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ common.expectWarning({
88
DeprecationWarning: [
99
['crypto.Hash constructor is deprecated.',
1010
'DEP0179'],
11-
...(process.features.openssl_is_boringssl ? [] : [[
12-
'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
13-
'DEP0198',
14-
]]),
1511
]
1612
});
1713

@@ -194,16 +190,29 @@ assert.throws(
194190

195191
// Test XOF hash functions and the outputLength option.
196192
if (!process.features.openssl_is_boringssl) {
197-
// Default outputLengths.
198-
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
199-
'7f9c2ba4e88f827d616045507605853e');
200-
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
193+
const invalidXofLength = {
194+
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH',
195+
name: 'Error',
196+
message: /not XOF or invalid length/,
197+
};
198+
199+
assert.throws(() => crypto.createHash('shake128'), invalidXofLength);
200+
assert.throws(() => crypto.createHash('shake128', null), invalidXofLength);
201+
assert.throws(() => crypto.createHash('shake256'), invalidXofLength);
202+
assert.throws(() => crypto.createHash('shake256', {}), invalidXofLength);
203+
204+
assert.strictEqual(crypto.createHash('shake128', { outputLength: 16 })
205+
.digest('hex'),
201206
'7f9c2ba4e88f827d616045507605853e');
202-
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
207+
assert.strictEqual(crypto.createHash('shake256', { outputLength: 32 })
208+
.digest('hex'),
203209
'46b9dd2b0ba88d13233b3feb743eeb24' +
204210
'3fcd52ea62b81b82b50c27646ed5762f');
205-
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
206-
.copy() // Default outputLength.
211+
const shake256 = crypto.createHash('shake256', { outputLength: 0 });
212+
assert.throws(() => shake256.copy(), invalidXofLength);
213+
assert.throws(() => shake256.copy(null), invalidXofLength);
214+
assert.throws(() => shake256.copy({}), invalidXofLength);
215+
assert.strictEqual(shake256.copy({ outputLength: 32 })
207216
.digest('hex'),
208217
'46b9dd2b0ba88d13233b3feb743eeb24' +
209218
'3fcd52ea62b81b82b50c27646ed5762f');

0 commit comments

Comments
 (0)