diff --git a/.github/workflows/coverage-linux-without-intl.yml b/.github/workflows/coverage-linux-without-intl.yml index a1f8f060ddab13..5520c7ac608b82 100644 --- a/.github/workflows/coverage-linux-without-intl.yml +++ b/.github/workflows/coverage-linux-without-intl.yml @@ -45,7 +45,8 @@ permissions: jobs: coverage-linux-without-intl: - if: github.event.pull_request.draft == false + # Disabled because "Report JS" step was crashing. + if: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index 9305d5f02cad6f..993207996b9692 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -45,7 +45,8 @@ permissions: jobs: coverage-linux: - if: github.event.pull_request.draft == false + # Disabled because "Report JS" step was crashing. + if: false runs-on: ubuntu-24.04 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 21f2ba7d5e1a32..6b98b87858649a 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -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. + + + + + + + + + + + + + + + + + + + + +### DEP0198: Creating SHAKE-128 and SHAKE-256 digests without an explicit `options.outputLength` + + + +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 diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index fda2017fa0cc95..f5b89cd9ac3040 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -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]); } diff --git a/lib/internal/util.js b/lib/internal/util.js index 0c34a5ea4e564b..bcc8bdd58da3e4 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -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, }; diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc index da301e433159f8..aa02ff763261ce 100644 --- a/src/crypto/crypto_hash.cc +++ b/src/crypto/crypto_hash.cc @@ -341,10 +341,22 @@ bool Hash::HashInit(const EVP_MD* md, Maybe 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; } diff --git a/test/parallel/test-crypto-default-shake-lengths.js b/test/parallel/test-crypto-default-shake-lengths.js new file mode 100644 index 00000000000000..1e558814ca0e4a --- /dev/null +++ b/test/parallel/test-crypto-default-shake-lengths.js @@ -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(); +} diff --git a/test/parallel/test-crypto-hash.js b/test/parallel/test-crypto-hash.js index 8a946ac55adb00..af2146982c7a3b 100644 --- a/test/parallel/test-crypto-hash.js +++ b/test/parallel/test-crypto-hash.js @@ -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 }) diff --git a/test/parallel/test-crypto-oneshot-hash.js b/test/parallel/test-crypto-oneshot-hash.js index 69051c43d9e882..56b4c04a65a1c1 100644 --- a/test/parallel/test-crypto-oneshot-hash.js +++ b/test/parallel/test-crypto-oneshot-hash.js @@ -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);