diff --git a/lib/internal/crypto/hash.js b/lib/internal/crypto/hash.js index 16834f169a5b..b879d467b0c6 100644 --- a/lib/internal/crypto/hash.js +++ b/lib/internal/crypto/hash.js @@ -187,7 +187,11 @@ Hmac.prototype.digest = function digest(outputEncoding) { return ret; }; -Hmac.prototype._flush = Hash.prototype._flush; +Hmac.prototype._flush = function _flush(callback) { + this.push(this[kHandle].digest()); + this[kState][kFinalized] = true; + callback(); +}; Hmac.prototype._transform = Hash.prototype._transform; // Implementation for WebCrypto subtle.digest() diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc index 42f3b53da0ea..c8328f8ec4fd 100644 --- a/src/crypto/crypto_hmac.cc +++ b/src/crypto/crypto_hmac.cc @@ -141,6 +141,9 @@ void Hmac::HmacDigest(const FunctionCallbackInfo& args) { return ThrowCryptoError(env, ERR_get_error(), "Failed to finalize HMAC"); } hmac->ctx_.reset(); + } else { + // The context has already been finalized; never emit unwritten bytes. + buf.len = 0; } Local ret; diff --git a/test/parallel/test-crypto-hmac.js b/test/parallel/test-crypto-hmac.js index 9ddc4a4b880f..d5c16d9aa834 100644 --- a/test/parallel/test-crypto-hmac.js +++ b/test/parallel/test-crypto-hmac.js @@ -296,6 +296,26 @@ for (let i = 0, l = rfc4231.length; i < l; i++) { } } +// Calling digest() after the Hmac has already been used as a stream must +// return an empty buffer (the DEP0206 repeat-digest guard), not uninitialized +// stack memory. The stream itself must still produce the correct digest. +// See: https://github.com/nodejs/node/issues/28245 +{ + const key = 'key'; + const data = 'some data to hash'; + + const streamHmac = crypto.createHmac('sha256', key); + streamHmac.end(data); + const streamDigest = streamHmac.read(); + + // digest() after the stream already finalized must not return garbage. + assert.deepStrictEqual(streamHmac.digest(), Buffer.from('')); + + // Sanity check: the stream itself produced the correct digest. + const expected = crypto.createHmac('sha256', key).update(data).digest(); + assert.deepStrictEqual(streamDigest, expected); +} + // Test HMAC-MD5/SHA1 (rfc 2202 Test Cases) const rfc2202_md5 = [ {