crypto: fix SHAKE128/256 breaking change introduced with OpenSSL 3.4
Some checks failed
Test and upload documentation to artifacts / build-docs (push) Failing after 7m39s
Linters / lint-addon-docs (push) Successful in 2m13s
Linters / format-cpp (push) Has been skipped
Linters / lint-cpp (push) Successful in 5m48s
Linters / lint-py (push) Successful in 2m38s
Linters / lint-yaml (push) Successful in 2m24s
Linters / lint-js-and-md (push) Successful in 9m18s
Linters / lint-codeowners (push) Failing after 56s
Linters / lint-pr-url (push) Has been skipped
Linters / lint-sh (push) Failing after 1m17s
Linters / lint-readme (push) Successful in 1m18s

Reverts: https://github.com/nodejs/node/pull/56160
Fixes: https://github.com/nodejs/node/issues/56159
Fixes: https://github.com/nodejs/node/issues/58913
Refs: https://github.com/nodejs/node/pull/58121
PR-URL: https://github.com/nodejs/node/pull/58942
Backport-PR-URL: https://github.com/nodejs/node/pull/58961
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
This commit is contained in:
Filip Skokan 2025-07-05 11:34:24 +02:00 committed by Marco Ippolito
parent 7f8d1b2681
commit 89366abd4d
No known key found for this signature in database
GPG key ID: 27F5E38D5B0A215F
7 changed files with 112 additions and 22 deletions

View file

@ -3589,6 +3589,39 @@ Type: Documentation-only
Passing non-supported argument types is deprecated and, instead of returning `false`,
will throw an error in a future version.
<!-- md-lint skip-deprecation DEP0188 -->
<!-- md-lint skip-deprecation DEP0189 -->
<!-- md-lint skip-deprecation DEP0190 -->
<!-- md-lint skip-deprecation DEP0191 -->
<!-- md-lint skip-deprecation DEP0192 -->
<!-- md-lint skip-deprecation DEP0193 -->
<!-- md-lint skip-deprecation DEP0194 -->
<!-- md-lint skip-deprecation DEP0195 -->
<!-- md-lint skip-deprecation DEP0196 -->
<!-- md-lint skip-deprecation DEP0197 -->
### DEP0198: Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength`
<!-- YAML
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/58942
description: Documentation-only deprecation with support for `--pending-deprecation`.
-->
Type: Documentation-only (supports [`--pending-deprecation`][])
Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength` is deprecated.
[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
[RFC 6066]: https://tools.ietf.org/html/rfc6066#section-3
[RFC 8247 Section 2.4]: https://www.rfc-editor.org/rfc/rfc8247#section-2.4

View file

@ -3,6 +3,7 @@
const {
ObjectSetPrototypeOf,
ReflectApply,
StringPrototypeReplace,
StringPrototypeToLowerCase,
Symbol,
} = primordials;
@ -33,6 +34,8 @@ const {
lazyDOMException,
normalizeEncoding,
encodingsMap,
isPendingDeprecation,
getDeprecationWarningEmitter,
} = require('internal/util');
const {
@ -63,6 +66,25 @@ const LazyTransform = require('internal/streams/lazy_transform');
const kState = Symbol('kState');
const kFinalized = Symbol('kFinalized');
/**
* @param {string} name
*/
function normalizeAlgorithm(name) {
return StringPrototypeReplace(StringPrototypeToLowerCase(name), '-', '');
}
const maybeEmitDeprecationWarning = getDeprecationWarningEmitter(
'DEP0198',
'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
undefined,
false,
(algorithm) => {
if (!isPendingDeprecation()) return false;
const normalized = normalizeAlgorithm(algorithm);
return normalized === 'shake128' || normalized === 'shake256';
},
);
function Hash(algorithm, options) {
if (!new.target)
return new Hash(algorithm, options);
@ -80,6 +102,9 @@ function Hash(algorithm, options) {
this[kState] = {
[kFinalized]: false,
};
if (!isCopy && xofLen === undefined) {
maybeEmitDeprecationWarning(algorithm);
}
ReflectApply(LazyTransform, this, [options]);
}
@ -213,6 +238,12 @@ function hash(algorithm, input, outputEncoding = 'hex') {
}
}
}
// TODO: ideally we have to ship https://github.com/nodejs/node/pull/58121 so
// that a proper DEP0198 deprecation can be done here as well.
const normalizedAlgorithm = normalizeAlgorithm(algorithm);
if (normalizedAlgorithm === 'shake128' || normalizedAlgorithm === 'shake256') {
return new Hash(algorithm).update(input).digest(normalized);
}
return oneShotDigest(algorithm, getCachedHashId(algorithm), getHashCache(),
input, normalized, encodingsMap[normalized]);
}

View file

@ -104,8 +104,8 @@ function getDeprecationWarningEmitter(
shouldEmitWarning = () => true,
) {
let warned = false;
return function() {
if (!warned && shouldEmitWarning()) {
return function(arg) {
if (!warned && shouldEmitWarning(arg)) {
warned = true;
if (code !== undefined) {
if (!codesWarned.has(code)) {
@ -994,4 +994,6 @@ module.exports = {
setOwnProperty,
pendingDeprecate,
WeakReference,
isPendingDeprecation,
getDeprecationWarningEmitter,
};

View file

@ -341,10 +341,22 @@ bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
}
md_len_ = EVP_MD_size(md);
bool is_xof = (EVP_MD_flags(md) & EVP_MD_FLAG_XOF) != 0;
if (is_xof && !xof_md_len.IsJust() && md_len_ == 0) {
const char* name = OBJ_nid2sn(EVP_MD_type(md));
if (name != nullptr) {
if (strcmp(name, "SHAKE128") == 0) {
md_len_ = 16;
} else if (strcmp(name, "SHAKE256") == 0) {
md_len_ = 32;
}
}
}
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
// This is a little hack to cause createHash to fail when an incorrect
// hashSize option was passed for a non-XOF hash function.
if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
if (!is_xof) {
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
return false;
}

View file

@ -0,0 +1,18 @@
// Flags: --pending-deprecation
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { createHash } = require('crypto');
common.expectWarning({
DeprecationWarning: {
DEP0198: 'Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.',
}
});
{
createHash('shake128').update('test').digest();
}

View file

@ -7,7 +7,6 @@ const assert = require('assert');
const crypto = require('crypto');
const fs = require('fs');
const { hasOpenSSL } = common;
const fixtures = require('../common/fixtures');
let cryptoType;
@ -183,21 +182,19 @@ assert.throws(
// Test XOF hash functions and the outputLength option.
{
// Default outputLengths. Since OpenSSL 3.4 an outputLength is mandatory
if (!hasOpenSSL(3, 4)) {
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
.copy() // Default outputLength.
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
}
// Default outputLengths.
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
'7f9c2ba4e88f827d616045507605853e');
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
.copy() // Default outputLength.
.digest('hex'),
'46b9dd2b0ba88d13233b3feb743eeb24' +
'3fcd52ea62b81b82b50c27646ed5762f');
// Short outputLengths.
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })

View file

@ -31,9 +31,6 @@ const methods = crypto.getHashes();
const input = fs.readFileSync(fixtures.path('utf8_test_text.txt'));
for (const method of methods) {
// Skip failing tests on OpenSSL 3.4.0
if (method.startsWith('shake') && common.hasOpenSSL(3, 4))
continue;
for (const outputEncoding of ['buffer', 'hex', 'base64', undefined]) {
const oldDigest = crypto.createHash(method).update(input).digest(outputEncoding || 'hex');
const digestFromBuffer = crypto.hash(method, input, outputEncoding);