diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 22342d4332c7e5..847b67bce2c799 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -3419,16 +3419,20 @@ void Hash::HashDigest(const FunctionCallbackInfo& args) { encoding = ParseEncoding(env->isolate(), args[0], BUFFER); } - unsigned char md_value[EVP_MAX_MD_SIZE]; - unsigned int md_len; - - EVP_DigestFinal_ex(hash->mdctx_.get(), md_value, &md_len); + if (hash->md_len_ == 0) { + // Some hash algorithms such as SHA3 do not support calling + // EVP_DigestFinal_ex more than once, however, Hash._flush + // and Hash.digest can both be used to retrieve the digest, + // so we need to cache it. + // See https://github.com/nodejs/node/issues/28245. + EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_, &hash->md_len_); + } Local error; MaybeLocal rc = StringBytes::Encode(env->isolate(), - reinterpret_cast(md_value), - md_len, + reinterpret_cast(hash->md_value_), + hash->md_len_, encoding, &error); if (rc.IsEmpty()) { diff --git a/src/node_crypto.h b/src/node_crypto.h index 5cdbe359d4d880..56f30991b4552d 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -475,12 +475,19 @@ class Hash : public BaseObject { Hash(Environment* env, v8::Local wrap) : BaseObject(env, wrap), - mdctx_(nullptr) { + mdctx_(nullptr), + md_len_(0) { MakeWeak(); } + ~Hash() override { + OPENSSL_cleanse(md_value_, md_len_); + } + private: EVPMDPointer mdctx_; + unsigned char md_value_[EVP_MAX_MD_SIZE]; + unsigned int md_len_; }; class SignBase : public BaseObject { diff --git a/test/parallel/test-crypto-hash-stream-pipeline.js b/test/parallel/test-crypto-hash-stream-pipeline.js new file mode 100644 index 00000000000000..4e4cdcfd7fccd5 --- /dev/null +++ b/test/parallel/test-crypto-hash-stream-pipeline.js @@ -0,0 +1,21 @@ +'use strict'; + +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); +const crypto = require('crypto'); +const stream = require('stream'); + +const hash = crypto.createHash('md5'); +const s = new stream.PassThrough(); +const expect = 'e8dc4081b13434b45189a720b77b6818'; + +s.write('abcdefgh'); +stream.pipeline(s, hash, common.mustCall(function(err) { + assert.ifError(err); + assert.strictEqual(hash.digest('hex'), expect); +})); +s.end();