GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: crypto/crypto_hash.cc Lines: 135 159 84.9 %
Date: 2022-12-31 04:22:30 Branches: 69 110 62.7 %

Line Branch Exec Source
1
#include "crypto/crypto_hash.h"
2
#include "async_wrap-inl.h"
3
#include "base_object-inl.h"
4
#include "env-inl.h"
5
#include "memory_tracker-inl.h"
6
#include "string_bytes.h"
7
#include "threadpoolwork-inl.h"
8
#include "v8.h"
9
10
#include <cstdio>
11
12
namespace node {
13
14
using v8::Context;
15
using v8::FunctionCallbackInfo;
16
using v8::FunctionTemplate;
17
using v8::Isolate;
18
using v8::Just;
19
using v8::Local;
20
using v8::Maybe;
21
using v8::MaybeLocal;
22
using v8::Nothing;
23
using v8::Object;
24
using v8::Uint32;
25
using v8::Value;
26
27
namespace crypto {
28
1332
Hash::Hash(Environment* env, Local<Object> wrap) : BaseObject(env, wrap) {
29
1332
  MakeWeak();
30
1332
}
31
32
void Hash::MemoryInfo(MemoryTracker* tracker) const {
33
  tracker->TrackFieldWithSize("mdctx", mdctx_ ? kSizeOf_EVP_MD_CTX : 0);
34
  tracker->TrackFieldWithSize("md", digest_ ? md_len_ : 0);
35
}
36
37
9
void Hash::GetHashes(const FunctionCallbackInfo<Value>& args) {
38
9
  Environment* env = Environment::GetCurrent(args);
39
18
  MarkPopErrorOnReturn mark_pop_error_on_return;
40
9
  CipherPushContext ctx(env);
41
9
  EVP_MD_do_all_sorted(
42
#if OPENSSL_VERSION_MAJOR >= 3
43
    array_push_back<EVP_MD,
44
                    EVP_MD_fetch,
45
                    EVP_MD_free,
46
                    EVP_get_digestbyname,
47
                    EVP_MD_get0_name>,
48
#else
49
    array_push_back<EVP_MD>,
50
#endif
51
    &ctx);
52
18
  args.GetReturnValue().Set(ctx.ToJSArray());
53
9
}
54
55
5065
void Hash::Initialize(Environment* env, Local<Object> target) {
56
5065
  Isolate* isolate = env->isolate();
57
5065
  Local<Context> context = env->context();
58
5065
  Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New);
59
60
10130
  t->InstanceTemplate()->SetInternalFieldCount(
61
      Hash::kInternalFieldCount);
62
5065
  t->Inherit(BaseObject::GetConstructorTemplate(env));
63
64
5065
  SetProtoMethod(isolate, t, "update", HashUpdate);
65
5065
  SetProtoMethod(isolate, t, "digest", HashDigest);
66
67
5065
  SetConstructorFunction(context, target, "Hash", t);
68
69
5065
  SetMethodNoSideEffect(context, target, "getHashes", GetHashes);
70
71
5065
  HashJob::Initialize(env, target);
72
5065
}
73
74
5718
void Hash::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
75
5718
  registry->Register(New);
76
5718
  registry->Register(HashUpdate);
77
5718
  registry->Register(HashDigest);
78
5718
  registry->Register(GetHashes);
79
80
5718
  HashJob::RegisterExternalReferences(registry);
81
5718
}
82
83
1332
void Hash::New(const FunctionCallbackInfo<Value>& args) {
84
1332
  Environment* env = Environment::GetCurrent(args);
85
86
1332
  const Hash* orig = nullptr;
87
1332
  const EVP_MD* md = nullptr;
88
89
1332
  if (args[0]->IsObject()) {
90
19
    ASSIGN_OR_RETURN_UNWRAP(&orig, args[0].As<Object>());
91
5
    md = EVP_MD_CTX_md(orig->mdctx_.get());
92
  } else {
93
2654
    const Utf8Value hash_type(env->isolate(), args[0]);
94
1327
    md = EVP_get_digestbyname(*hash_type);
95
  }
96
97
1332
  Maybe<unsigned int> xof_md_len = Nothing<unsigned int>();
98
2664
  if (!args[1]->IsUndefined()) {
99
13
    CHECK(args[1]->IsUint32());
100
26
    xof_md_len = Just<unsigned int>(args[1].As<Uint32>()->Value());
101
  }
102
103
1332
  Hash* hash = new Hash(env, args.This());
104

1332
  if (md == nullptr || !hash->HashInit(md, xof_md_len)) {
105
9
    return ThrowCryptoError(env, ERR_get_error(),
106
9
                            "Digest method not supported");
107
  }
108
109

1328
  if (orig != nullptr &&
110
5
      0 >= EVP_MD_CTX_copy(hash->mdctx_.get(), orig->mdctx_.get())) {
111
    return ThrowCryptoError(env, ERR_get_error(), "Digest copy error");
112
  }
113
}
114
115
1330
bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
116
1330
  mdctx_.reset(EVP_MD_CTX_new());
117

1330
  if (!mdctx_ || EVP_DigestInit_ex(mdctx_.get(), md, nullptr) <= 0) {
118
6
    mdctx_.reset();
119
6
    return false;
120
  }
121
122
1324
  md_len_ = EVP_MD_size(md);
123

1337
  if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
124
    // This is a little hack to cause createHash to fail when an incorrect
125
    // hashSize option was passed for a non-XOF hash function.
126
12
    if ((EVP_MD_flags(md) & EVP_MD_FLAG_XOF) == 0) {
127
1
      EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
128
1
      return false;
129
    }
130
11
    md_len_ = xof_md_len.FromJust();
131
  }
132
133
1323
  return true;
134
}
135
136
1564
bool Hash::HashUpdate(const char* data, size_t len) {
137
1564
  if (!mdctx_)
138
    return false;
139
1564
  return EVP_DigestUpdate(mdctx_.get(), data, len) == 1;
140
}
141
142
1564
void Hash::HashUpdate(const FunctionCallbackInfo<Value>& args) {
143
1564
  Decode<Hash>(args, [](Hash* hash, const FunctionCallbackInfo<Value>& args,
144
1564
                        const char* data, size_t size) {
145
1564
    Environment* env = Environment::GetCurrent(args);
146
1564
    if (UNLIKELY(size > INT_MAX))
147
      return THROW_ERR_OUT_OF_RANGE(env, "data is too long");
148
1564
    bool r = hash->HashUpdate(data, size);
149
3128
    args.GetReturnValue().Set(r);
150
  });
151
1564
}
152
153
1248
void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
154
1248
  Environment* env = Environment::GetCurrent(args);
155
156
  Hash* hash;
157
1248
  ASSIGN_OR_RETURN_UNWRAP(&hash, args.Holder());
158
159
1248
  enum encoding encoding = BUFFER;
160
1248
  if (args.Length() >= 1) {
161
1236
    encoding = ParseEncoding(env->isolate(), args[0], BUFFER);
162
  }
163
164
1248
  unsigned int len = hash->md_len_;
165
166
  // TODO(tniessen): SHA3_squeeze does not work for zero-length outputs on all
167
  // platforms and will cause a segmentation fault if called. This workaround
168
  // causes hash.digest() to correctly return an empty buffer / string.
169
  // See https://github.com/openssl/openssl/issues/9431.
170
171

1248
  if (!hash->digest_ && len > 0) {
172
    // Some hash algorithms such as SHA3 do not support calling
173
    // EVP_DigestFinal_ex more than once, however, Hash._flush
174
    // and Hash.digest can both be used to retrieve the digest,
175
    // so we need to cache it.
176
    // See https://github.com/nodejs/node/issues/28245.
177
178
1245
    ByteSource::Builder digest(len);
179
180
1245
    size_t default_len = EVP_MD_CTX_size(hash->mdctx_.get());
181
    int ret;
182
1245
    if (len == default_len) {
183
2478
      ret = EVP_DigestFinal_ex(
184
1239
          hash->mdctx_.get(), digest.data<unsigned char>(), &len);
185
      // The output length should always equal hash->md_len_
186
1239
      CHECK_EQ(len, hash->md_len_);
187
    } else {
188
12
      ret = EVP_DigestFinalXOF(
189
6
          hash->mdctx_.get(), digest.data<unsigned char>(), len);
190
    }
191
192
1245
    if (ret != 1)
193
      return ThrowCryptoError(env, ERR_get_error());
194
195
1245
    hash->digest_ = std::move(digest).release();
196
  }
197
198
  Local<Value> error;
199
  MaybeLocal<Value> rc = StringBytes::Encode(
200
1248
      env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
201
1248
  if (rc.IsEmpty()) {
202
    CHECK(!error.IsEmpty());
203
    env->isolate()->ThrowException(error);
204
    return;
205
  }
206
3744
  args.GetReturnValue().Set(rc.FromMaybe(Local<Value>()));
207
}
208
209
137
HashConfig::HashConfig(HashConfig&& other) noexcept
210
137
    : mode(other.mode),
211
137
      in(std::move(other.in)),
212
137
      digest(other.digest),
213
137
      length(other.length) {}
214
215
HashConfig& HashConfig::operator=(HashConfig&& other) noexcept {
216
  if (&other == this) return *this;
217
  this->~HashConfig();
218
  return *new (this) HashConfig(std::move(other));
219
}
220
221
void HashConfig::MemoryInfo(MemoryTracker* tracker) const {
222
  // If the Job is sync, then the HashConfig does not own the data.
223
  if (mode == kCryptoJobAsync)
224
    tracker->TrackFieldWithSize("in", in.size());
225
}
226
227
137
Maybe<bool> HashTraits::EncodeOutput(
228
    Environment* env,
229
    const HashConfig& params,
230
    ByteSource* out,
231
    v8::Local<v8::Value>* result) {
232
274
  *result = out->ToArrayBuffer(env);
233
137
  return Just(!result->IsEmpty());
234
}
235
236
138
Maybe<bool> HashTraits::AdditionalConfig(
237
    CryptoJobMode mode,
238
    const FunctionCallbackInfo<Value>& args,
239
    unsigned int offset,
240
    HashConfig* params) {
241
138
  Environment* env = Environment::GetCurrent(args);
242
243
138
  params->mode = mode;
244
245

414
  CHECK(args[offset]->IsString());  // Hash algorithm
246
414
  Utf8Value digest(env->isolate(), args[offset]);
247
138
  params->digest = EVP_get_digestbyname(*digest);
248
138
  if (UNLIKELY(params->digest == nullptr)) {
249
    THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *digest);
250
    return Nothing<bool>();
251
  }
252
253
276
  ArrayBufferOrViewContents<char> data(args[offset + 1]);
254
138
  if (UNLIKELY(!data.CheckSizeInt32())) {
255
    THROW_ERR_OUT_OF_RANGE(env, "data is too big");
256
    return Nothing<bool>();
257
  }
258
  params->in = mode == kCryptoJobAsync
259
276
      ? data.ToCopy()
260
138
      : data.ToByteSource();
261
262
138
  unsigned int expected = EVP_MD_size(params->digest);
263
138
  params->length = expected;
264

276
  if (UNLIKELY(args[offset + 2]->IsUint32())) {
265
    // length is expressed in terms of bits
266
5
    params->length =
267
10
        static_cast<uint32_t>(args[offset + 2]
268
5
            .As<Uint32>()->Value()) / CHAR_BIT;
269
5
    if (params->length != expected) {
270
1
      if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
271
1
        THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
272
1
        return Nothing<bool>();
273
      }
274
    }
275
  }
276
277
137
  return Just(true);
278
}
279
280
137
bool HashTraits::DeriveBits(
281
    Environment* env,
282
    const HashConfig& params,
283
    ByteSource* out) {
284
274
  EVPMDPointer ctx(EVP_MD_CTX_new());
285
286
411
  if (UNLIKELY(!ctx ||
287
               EVP_DigestInit_ex(ctx.get(), params.digest, nullptr) <= 0 ||
288
               EVP_DigestUpdate(
289


411
                   ctx.get(), params.in.data<char>(), params.in.size()) <= 0)) {
290
    return false;
291
  }
292
293
137
  if (LIKELY(params.length > 0)) {
294
137
    unsigned int length = params.length;
295
137
    ByteSource::Builder buf(length);
296
297
137
    size_t expected = EVP_MD_CTX_size(ctx.get());
298
299
    int ret =
300
137
        (length == expected)
301
137
            ? EVP_DigestFinal_ex(ctx.get(), buf.data<unsigned char>(), &length)
302
            : EVP_DigestFinalXOF(ctx.get(), buf.data<unsigned char>(), length);
303
304
137
    if (UNLIKELY(ret != 1))
305
      return false;
306
307
137
    *out = std::move(buf).release();
308
  }
309
310
137
  return true;
311
}
312
313
}  // namespace crypto
314
}  // namespace node