| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d3426ee commit b7c6ad5
5 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1785,14 +1785,19 @@ and description of each available elliptic curve. | |||
| 1785 | 1785 | ### crypto.createHash(algorithm[, options]) | |
| 1786 | 1786 | <!-- YAML | |
| 1787 | 1787 | added: v0.1.92 | |
| 1788 | + changes: | ||
| 1789 | + - version: REPLACEME | ||
| 1790 | + pr-url: https://github.com/nodejs/node/pull/28805 | ||
| 1791 | + description: The `outputLength` option was added for XOF hash functions. | ||
| 1788 | 1792 | --> | |
| 1789 | 1793 | * `algorithm` {string} | |
| 1790 | 1794 | * `options` {Object} [`stream.transform` options][] | |
| 1791 | 1795 | * Returns: {Hash} | |
| 1792 | 1796 | ||
| 1793 | 1797 | Creates and returns a `Hash` object that can be used to generate hash digests | |
| 1794 | 1798 | using the given `algorithm`. Optional `options` argument controls stream | |
| 1795 | - behavior. | ||
| 1799 | + behavior. For XOF hash functions such as `'shake256'`, the `outputLength` option | ||
| 1800 | + can be used to specify the desired output length in bytes. | ||
| 1796 | 1801 | ||
| 1797 | 1802 | The `algorithm` is dependent on the available algorithms supported by the | |
| 1798 | 1803 | version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -25,7 +25,7 @@ const { | |||
| 25 | 25 | ERR_CRYPTO_HASH_UPDATE_FAILED, | |
| 26 | 26 | ERR_INVALID_ARG_TYPE | |
| 27 | 27 | } = require('internal/errors').codes; | |
| 28 | - const { validateString } = require('internal/validators'); | ||
| 28 | + const { validateString, validateUint32 } = require('internal/validators'); | ||
| 29 | 29 | const { normalizeEncoding } = require('internal/util'); | |
| 30 | 30 | const { isArrayBufferView } = require('internal/util/types'); | |
| 31 | 31 | const LazyTransform = require('internal/streams/lazy_transform'); | |
@@ -36,7 +36,10 @@ function Hash(algorithm, options) { | |||
| 36 | 36 | if (!(this instanceof Hash)) | |
| 37 | 37 | return new Hash(algorithm, options); | |
| 38 | 38 | validateString(algorithm, 'algorithm'); | |
| 39 | - this[kHandle] = new _Hash(algorithm); | ||
| 39 | + const xofLen = typeof options === 'object' ? options.outputLength : undefined; | ||
| 40 | + if (xofLen !== undefined) | ||
| 41 | + validateUint32(xofLen, 'options.outputLength'); | ||
| 42 | + this[kHandle] = new _Hash(algorithm, xofLen); | ||
| 40 | 43 | this[kState] = { | |
| 41 | 44 | [kFinalized]: false | |
| 42 | 45 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4569,15 +4569,21 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) { | |||
| 4569 | 4569 | ||
| 4570 | 4570 | const node::Utf8Value hash_type(env->isolate(), args[0]); | |
| 4571 | 4571 | ||
| 4572 | + Maybe<unsigned int> xof_md_len = Nothing<unsigned int>(); | ||
| 4573 | + if (!args[1]->IsUndefined()) { | ||
| 4574 | + CHECK(args[1]->IsUint32()); | ||
| 4575 | + xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value()); | ||
| 4576 | + } | ||
| 4577 | + | ||
| 4572 | 4578 | Hash* hash = new Hash(env, args.This()); | |
| 4573 | - if (!hash->HashInit(*hash_type)) { | ||
| 4579 | + if (!hash->HashInit(*hash_type, xof_md_len)) { | ||
| 4574 | 4580 | return ThrowCryptoError(env, ERR_get_error(), | |
| 4575 | 4581 | "Digest method not supported"); | |
| 4576 | 4582 | } | |
| 4577 | 4583 | } | |
| 4578 | 4584 | ||
| 4579 | 4585 | ||
| 4580 | - bool Hash::HashInit(const char* hash_type) { | ||
| 4586 | + bool Hash::HashInit(const char* hash_type, Maybe<unsigned int> xof_md_len) { | ||
| 4581 | 4587 | const EVP_MD* md = EVP_get_digestbyname(hash_type); | |
| 4582 | 4588 | if (md == nullptr) | |
| 4583 | 4589 | return false; | |
@@ -4586,6 +4592,18 @@ bool Hash::HashInit(const char* hash_type) { | |||
| 4586 | 4592 | mdctx_.reset(); | |
| 4587 | 4593 | return false; | |
| 4588 | 4594 | } | |
| 4595 | + | ||
| 4596 | + md_len_ = EVP_MD_size(md); | ||
| 4597 | + if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) { | ||
| 4598 | + // This is a little hack to cause createHash to fail when an incorrect | ||
| 4599 | + // hashSize option was passed for a non-XOF hash function. | ||
| 4600 | + if ((EVP_MD_meth_get_flags(md) & EVP_MD_FLAG_XOF) == 0) { | ||
| 4601 | + EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH); | ||
| 4602 | + return false; | ||
| 4603 | + } | ||
| 4604 | + md_len_ = xof_md_len.FromJust(); | ||
| 4605 | + } | ||
| 4606 | + | ||
| 4589 | 4607 | return true; | |
| 4590 | 4608 | } | |
| 4591 | 4609 | ||
@@ -4634,13 +4652,40 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) { | |||
| 4634 | 4652 | encoding = ParseEncoding(env->isolate(), args[0], BUFFER); | |
| 4635 | 4653 | } | |
| 4636 | 4654 | ||
| 4637 | - if (hash->md_len_ == 0) { | ||
| 4655 | + // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all | ||
| 4656 | + // platforms and will cause a segmentation fault if called. This workaround | ||
| 4657 | + // causes hash.digest() to correctly return an empty buffer / string. | ||
| 4658 | + // See https://github.com/openssl/openssl/issues/9431. | ||
| 4659 | + if (!hash->has_md_ && hash->md_len_ == 0) { | ||
| 4660 | + hash->has_md_ = true; | ||
| 4661 | + } | ||
| 4662 | + | ||
| 4663 | + if (!hash->has_md_) { | ||
| 4638 | 4664 | // Some hash algorithms such as SHA3 do not support calling | |
| 4639 | 4665 | // EVP_DigestFinal_ex more than once, however, Hash._flush | |
| 4640 | 4666 | // and Hash.digest can both be used to retrieve the digest, | |
| 4641 | 4667 | // so we need to cache it. | |
| 4642 | 4668 | // See https://github.com/nodejs/node/issues/28245. | |
| 4643 | - EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_, &hash->md_len_); | ||
| 4669 | + | ||
| 4670 | + hash->md_value_ = MallocOpenSSL<unsigned char>(hash->md_len_); | ||
| 4671 | + | ||
| 4672 | + size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get()); | ||
| 4673 | + int ret; | ||
| 4674 | + if (hash->md_len_ == default_len) { | ||
| 4675 | + ret = EVP_DigestFinal_ex(hash->mdctx_.get(), hash->md_value_, | ||
| 4676 | + &hash->md_len_); | ||
| 4677 | + } else { | ||
| 4678 | + ret = EVP_DigestFinalXOF(hash->mdctx_.get(), hash->md_value_, | ||
| 4679 | + hash->md_len_); | ||
| 4680 | + } | ||
| 4681 | + | ||
| 4682 | + if (ret != 1) { | ||
| 4683 | + OPENSSL_free(hash->md_value_); | ||
| 4684 | + hash->md_value_ = nullptr; | ||
| 4685 | + return ThrowCryptoError(env, ERR_get_error()); | ||
| 4686 | + } | ||
| 4687 | + | ||
| 4688 | + hash->has_md_ = true; | ||
| 4644 | 4689 | } | |
| 4645 | 4690 | ||
| 4646 | 4691 | Local<Value> error; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -585,7 +585,7 @@ class Hash : public BaseObject { | |||
| 585 | 585 | SET_MEMORY_INFO_NAME(Hash) | |
| 586 | 586 | SET_SELF_SIZE(Hash) | |
| 587 | 587 | ||
| 588 | - bool HashInit(const char* hash_type); | ||
| 588 | + bool HashInit(const char* hash_type, v8::Maybe<unsigned int> xof_md_len); | ||
| 589 | 589 | bool HashUpdate(const char* data, int len); | |
| 590 | 590 | ||
| 591 | 591 | protected: | |
@@ -596,18 +596,21 @@ class Hash : public BaseObject { | |||
| 596 | 596 | Hash(Environment* env, v8::Local<v8::Object> wrap) | |
| 597 | 597 | : BaseObject(env, wrap), | |
| 598 | 598 | mdctx_(nullptr), | |
| 599 | - md_len_(0) { | ||
| 599 | + has_md_(false), | ||
| 600 | + md_value_(nullptr) { | ||
| 600 | 601 | MakeWeak(); | |
| 601 | 602 | } | |
| 602 | 603 | ||
| 603 | 604 | ~Hash() override { | |
| 604 | - OPENSSL_cleanse(md_value_, md_len_); | ||
| 605 | + if (md_value_ != nullptr) | ||
| 606 | + OPENSSL_clear_free(md_value_, md_len_); | ||
| 605 | 607 | } | |
| 606 | 608 | ||
| 607 | 609 | private: | |
| 608 | 610 | EVPMDPointer mdctx_; | |
| 609 | - unsigned char md_value_[EVP_MAX_MD_SIZE]; | ||
| 611 | + bool has_md_; | ||
| 610 | 612 | unsigned int md_len_; | |
| 613 | + unsigned char* md_value_; | ||
| 611 | 614 | }; | |
| 612 | 615 | ||
| 613 | 616 | class SignBase : public BaseObject { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -185,3 +185,69 @@ common.expectsError( | |||
| 185 | 185 | assert(instance instanceof Hash, 'Hash is expected to return a new instance' + | |
| 186 | 186 | ' when called without `new`'); | |
| 187 | 187 | } | |
| 188 | + | ||
| 189 | + // Test XOF hash functions and the outputLength option. | ||
| 190 | + { | ||
| 191 | + // Default outputLengths. | ||
| 192 | + assert.strictEqual(crypto.createHash('shake128').digest('hex'), | ||
| 193 | + '7f9c2ba4e88f827d616045507605853e'); | ||
| 194 | + assert.strictEqual(crypto.createHash('shake256').digest('hex'), | ||
| 195 | + '46b9dd2b0ba88d13233b3feb743eeb24' + | ||
| 196 | + '3fcd52ea62b81b82b50c27646ed5762f'); | ||
| 197 | + | ||
| 198 | + // Short outputLengths. | ||
| 199 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 }) | ||
| 200 | + .digest('hex'), | ||
| 201 | + ''); | ||
| 202 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 }) | ||
| 203 | + .digest('hex'), | ||
| 204 | + '7f9c2ba4e8'); | ||
| 205 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 }) | ||
| 206 | + .digest('hex'), | ||
| 207 | + '7f9c2ba4e88f827d61604550760585'); | ||
| 208 | + assert.strictEqual(crypto.createHash('shake256', { outputLength: 16 }) | ||
| 209 | + .digest('hex'), | ||
| 210 | + '46b9dd2b0ba88d13233b3feb743eeb24'); | ||
| 211 | + | ||
| 212 | + // Large outputLengths. | ||
| 213 | + assert.strictEqual(crypto.createHash('shake128', { outputLength: 128 }) | ||
| 214 | + .digest('hex'), | ||
| 215 | + '7f9c2ba4e88f827d616045507605853e' + | ||
| 216 | + 'd73b8093f6efbc88eb1a6eacfa66ef26' + | ||
| 217 | + '3cb1eea988004b93103cfb0aeefd2a68' + | ||
| 218 | + '6e01fa4a58e8a3639ca8a1e3f9ae57e2' + | ||
| 219 | + '35b8cc873c23dc62b8d260169afa2f75' + | ||
| 220 | + 'ab916a58d974918835d25e6a435085b2' + | ||
| 221 | + 'badfd6dfaac359a5efbb7bcc4b59d538' + | ||
| 222 | + 'df9a04302e10c8bc1cbf1a0b3a5120ea'); | ||
| 223 | + const superLongHash = crypto.createHash('shake256', { | ||
| 224 | + outputLength: 1024 * 1024 | ||
| 225 | + }).update('The message is shorter than the hash!') | ||
| 226 | + .digest('hex'); | ||
| 227 | + assert.strictEqual(superLongHash.length, 2 * 1024 * 1024); | ||
| 228 | + assert.ok(superLongHash.endsWith('193414035ddba77bf7bba97981e656ec')); | ||
| 229 | + assert.ok(superLongHash.startsWith('a2a28dbc49cfd6e5d6ceea3d03e77748')); | ||
| 230 | + | ||
| 231 | + // Non-XOF hash functions should accept valid outputLength options as well. | ||
| 232 | + assert.strictEqual(crypto.createHash('sha224', { outputLength: 28 }) | ||
| 233 | + .digest('hex'), | ||
| 234 | + 'd14a028c2a3a2bc9476102bb288234c4' + | ||
| 235 | + '15a2b01f828ea62ac5b3e42f'); | ||
| 236 | + | ||
| 237 | + // Passing invalid sizes should throw during creation. | ||
| 238 | + common.expectsError(() => { | ||
| 239 | + crypto.createHash('sha256', { outputLength: 28 }); | ||
| 240 | + }, { | ||
| 241 | + code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH' | ||
| 242 | + }); | ||
| 243 | + | ||
| 244 | + for (const outputLength of [null, {}, 'foo', false]) { | ||
| 245 | + common.expectsError(() => crypto.createHash('sha256', { outputLength }), | ||
| 246 | + { code: 'ERR_INVALID_ARG_TYPE' }); | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + for (const outputLength of [-1, .5, Infinity, 2 ** 90]) { | ||
| 250 | + common.expectsError(() => crypto.createHash('sha256', { outputLength }), | ||
| 251 | + { code: 'ERR_OUT_OF_RANGE' }); | ||
| 252 | + } | ||
| 253 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments