GCC Code Coverage Report
Directory: ../ Exec Total Coverage
File: /home/iojs/build/workspace/node-test-commit-linux-coverage-daily/nodes/benchmark/out/../src/crypto/crypto_aes.cc Lines: 33 317 10.4 %
Date: 2021-02-19 04:08:54 Branches: 2 206 1.0 %

Line Branch Exec Source
1
#include "crypto/crypto_aes.h"
2
#include "crypto/crypto_cipher.h"
3
#include "crypto/crypto_keys.h"
4
#include "crypto/crypto_util.h"
5
#include "allocated_buffer-inl.h"
6
#include "async_wrap-inl.h"
7
#include "base_object-inl.h"
8
#include "env-inl.h"
9
#include "memory_tracker-inl.h"
10
#include "threadpoolwork-inl.h"
11
#include "v8.h"
12
13
#include <openssl/bn.h>
14
#include <openssl/aes.h>
15
16
#include <vector>
17
18
namespace node {
19
20
using v8::FunctionCallbackInfo;
21
using v8::Just;
22
using v8::Local;
23
using v8::Maybe;
24
using v8::Nothing;
25
using v8::Object;
26
using v8::Uint32;
27
using v8::Value;
28
29
namespace crypto {
30
namespace {
31
// Implements general AES encryption and decryption for CBC
32
// The key_data must be a secret key.
33
// On success, this function sets out to a new AllocatedBuffer
34
// instance containing the results and returns WebCryptoCipherStatus::OK.
35
WebCryptoCipherStatus AES_Cipher(
36
    Environment* env,
37
    KeyObjectData* key_data,
38
    WebCryptoCipherMode cipher_mode,
39
    const AESCipherConfig& params,
40
    const ByteSource& in,
41
    ByteSource* out) {
42
  CHECK_NOT_NULL(key_data);
43
  CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
44
45
  const int mode = EVP_CIPHER_mode(params.cipher);
46
47
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
48
  EVP_CIPHER_CTX_init(ctx.get());
49
  if (mode == EVP_CIPH_WRAP_MODE)
50
    EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
51
52
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
53
54
  if (!EVP_CipherInit_ex(
55
          ctx.get(),
56
          params.cipher,
57
          nullptr,
58
          nullptr,
59
          nullptr,
60
          encrypt)) {
61
    // Cipher init failed
62
    return WebCryptoCipherStatus::FAILED;
63
  }
64
65
  if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
66
        ctx.get(),
67
        EVP_CTRL_AEAD_SET_IVLEN,
68
        params.iv.size(),
69
        nullptr)) {
70
    return WebCryptoCipherStatus::FAILED;
71
  }
72
73
  if (!EVP_CIPHER_CTX_set_key_length(
74
          ctx.get(),
75
          key_data->GetSymmetricKeySize()) ||
76
      !EVP_CipherInit_ex(
77
          ctx.get(),
78
          nullptr,
79
          nullptr,
80
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
81
          params.iv.data<unsigned char>(),
82
          encrypt)) {
83
    return WebCryptoCipherStatus::FAILED;
84
  }
85
86
  size_t tag_len = 0;
87
88
  if (mode == EVP_CIPH_GCM_MODE) {
89
    switch (cipher_mode) {
90
      case kWebCryptoCipherDecrypt:
91
        // If in decrypt mode, the auth tag must be set in the params.tag.
92
        CHECK(params.tag);
93
        if (!EVP_CIPHER_CTX_ctrl(
94
                ctx.get(),
95
                EVP_CTRL_AEAD_SET_TAG,
96
                params.tag.size(),
97
                const_cast<char*>(params.tag.get()))) {
98
          return WebCryptoCipherStatus::FAILED;
99
        }
100
        break;
101
      case kWebCryptoCipherEncrypt:
102
        // In decrypt mode, we grab the tag length here. We'll use it to
103
        // ensure that that allocated buffer has enough room for both the
104
        // final block and the auth tag. Unlike our other AES-GCM implementation
105
        // in CipherBase, in WebCrypto, the auth tag is concatenated to the end
106
        // of the generated ciphertext and returned in the same ArrayBuffer.
107
        tag_len = params.length;
108
        break;
109
      default:
110
        UNREACHABLE();
111
    }
112
  }
113
114
  size_t total = 0;
115
  int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
116
  int out_len;
117
118
  if (mode == EVP_CIPH_GCM_MODE &&
119
      params.additional_data.size() &&
120
      !EVP_CipherUpdate(
121
            ctx.get(),
122
            nullptr,
123
            &out_len,
124
            params.additional_data.data<unsigned char>(),
125
            params.additional_data.size())) {
126
    return WebCryptoCipherStatus::FAILED;
127
  }
128
129
  char* data = MallocOpenSSL<char>(buf_len);
130
  ByteSource buf = ByteSource::Allocated(data, buf_len);
131
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
132
133
  if (!EVP_CipherUpdate(
134
          ctx.get(),
135
          ptr,
136
          &out_len,
137
          in.data<unsigned char>(),
138
          in.size())) {
139
    return WebCryptoCipherStatus::FAILED;
140
  }
141
142
  total += out_len;
143
  CHECK_LE(out_len, buf_len);
144
  ptr += out_len;
145
  out_len = EVP_CIPHER_CTX_block_size(ctx.get());
146
  if (!EVP_CipherFinal_ex(ctx.get(), ptr, &out_len)) {
147
    return WebCryptoCipherStatus::FAILED;
148
  }
149
  total += out_len;
150
151
  // If using AES_GCM, grab the generated auth tag and append
152
  // it to the end of the ciphertext.
153
  if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
154
    data += out_len;
155
    if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, tag_len, ptr))
156
      return WebCryptoCipherStatus::FAILED;
157
    total += tag_len;
158
  }
159
160
  // It's possible that we haven't used the full allocated space. Size down.
161
  buf.Resize(total);
162
  *out = std::move(buf);
163
164
  return WebCryptoCipherStatus::OK;
165
}
166
167
// The AES_CTR implementation here takes it's inspiration from the chromium
168
// implementation here:
169
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
170
171
template <typename T>
172
T CeilDiv(T a, T b) {
173
  return a == 0 ? 0 : 1 + (a - 1) / b;
174
}
175
176
BignumPointer GetCounter(const AESCipherConfig& params) {
177
  unsigned int remainder = (params.length % CHAR_BIT);
178
  const unsigned char* data = params.iv.data<unsigned char>();
179
180
  if (remainder == 0) {
181
    unsigned int byte_length = params.length / CHAR_BIT;
182
    return BignumPointer(BN_bin2bn(
183
        data + params.iv.size() - byte_length,
184
        byte_length,
185
        nullptr));
186
  }
187
188
  unsigned int byte_length =
189
      CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
190
191
  std::vector<unsigned char> counter(
192
      data + params.iv.size() - byte_length,
193
      data + params.iv.size());
194
  counter[0] &= ~(0xFF << remainder);
195
196
  return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
197
}
198
199
std::vector<unsigned char> BlockWithZeroedCounter(
200
    const AESCipherConfig& params) {
201
  unsigned int length_bytes = params.length / CHAR_BIT;
202
  unsigned int remainder = params.length % CHAR_BIT;
203
204
  const unsigned char* data = params.iv.data<unsigned char>();
205
206
  std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
207
208
  size_t index = new_counter_block.size() - length_bytes;
209
  memset(&new_counter_block.front() + index, 0, length_bytes);
210
211
  if (remainder)
212
    new_counter_block[index - 1] &= 0xFF << remainder;
213
214
  return new_counter_block;
215
}
216
217
WebCryptoCipherStatus AES_CTR_Cipher2(
218
    KeyObjectData* key_data,
219
    WebCryptoCipherMode cipher_mode,
220
    const AESCipherConfig& params,
221
    const ByteSource& in,
222
    unsigned const char* counter,
223
    unsigned char* out) {
224
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
225
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
226
227
  if (!EVP_CipherInit_ex(
228
          ctx.get(),
229
          params.cipher,
230
          nullptr,
231
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
232
          counter,
233
          encrypt)) {
234
    // Cipher init failed
235
    return WebCryptoCipherStatus::FAILED;
236
  }
237
238
  int out_len = 0;
239
  int final_len = 0;
240
  if (!EVP_CipherUpdate(
241
          ctx.get(),
242
          out,
243
          &out_len,
244
          in.data<unsigned char>(),
245
          in.size())) {
246
    return WebCryptoCipherStatus::FAILED;
247
  }
248
249
  if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
250
    return WebCryptoCipherStatus::FAILED;
251
252
  out_len += final_len;
253
  if (static_cast<unsigned>(out_len) != in.size())
254
    return WebCryptoCipherStatus::FAILED;
255
256
  return WebCryptoCipherStatus::OK;
257
}
258
259
WebCryptoCipherStatus AES_CTR_Cipher(
260
    Environment* env,
261
    KeyObjectData* key_data,
262
    WebCryptoCipherMode cipher_mode,
263
    const AESCipherConfig& params,
264
    const ByteSource& in,
265
    ByteSource* out) {
266
  BignumPointer num_counters(BN_new());
267
  if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
268
    return WebCryptoCipherStatus::FAILED;
269
270
  BignumPointer current_counter = GetCounter(params);
271
272
  BignumPointer num_output(BN_new());
273
274
  if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
275
    return WebCryptoCipherStatus::FAILED;
276
277
  // Just like in chromium's implementation, if the counter will
278
  // be incremented more than there are counter values, we fail.
279
  if (BN_cmp(num_output.get(), num_counters.get()) > 0)
280
    return WebCryptoCipherStatus::FAILED;
281
282
  BignumPointer remaining_until_reset(BN_new());
283
  if (!BN_sub(remaining_until_reset.get(),
284
              num_counters.get(),
285
              current_counter.get())) {
286
    return WebCryptoCipherStatus::FAILED;
287
  }
288
289
  // Output size is identical to the input size
290
  char* data = MallocOpenSSL<char>(in.size());
291
  ByteSource buf = ByteSource::Allocated(data, in.size());
292
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
293
294
  // Also just like in chromium's implementation, if we can process
295
  // the input without wrapping the counter, we'll do it as a single
296
  // call here. If we can't, we'll fallback to the a two-step approach
297
  if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
298
    auto status = AES_CTR_Cipher2(
299
        key_data,
300
        cipher_mode,
301
        params,
302
        in,
303
        params.iv.data<unsigned char>(),
304
        ptr);
305
    if (status == WebCryptoCipherStatus::OK)
306
      *out = std::move(buf);
307
    return status;
308
  }
309
310
  BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
311
  BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
312
313
  // Encrypt the first part...
314
  auto status = AES_CTR_Cipher2(
315
      key_data,
316
      cipher_mode,
317
      params,
318
      ByteSource::Foreign(in.get(), input_size_part1),
319
      params.iv.data<unsigned char>(),
320
      ptr);
321
322
  if (status != WebCryptoCipherStatus::OK)
323
    return status;
324
325
  // Wrap the counter around to zero
326
  std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
327
328
  // Encrypt the second part...
329
  status = AES_CTR_Cipher2(
330
      key_data,
331
      cipher_mode,
332
      params,
333
      ByteSource::Foreign(
334
          in.get() + input_size_part1,
335
          in.size() - input_size_part1),
336
      new_counter_block.data(),
337
      ptr + input_size_part1);
338
339
  if (status == WebCryptoCipherStatus::OK)
340
    *out = std::move(buf);
341
342
  return status;
343
}
344
345
bool ValidateIV(
346
    Environment* env,
347
    CryptoJobMode mode,
348
    Local<Value> value,
349
    AESCipherConfig* params) {
350
  ArrayBufferOrViewContents<char> iv(value);
351
  if (UNLIKELY(!iv.CheckSizeInt32())) {
352
    THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
353
    return false;
354
  }
355
  params->iv = (mode == kCryptoJobAsync)
356
      ? iv.ToCopy()
357
      : iv.ToByteSource();
358
  return true;
359
}
360
361
bool ValidateCounter(
362
  Environment* env,
363
  Local<Value> value,
364
  AESCipherConfig* params) {
365
  CHECK(value->IsUint32());  // Length
366
  params->length = value.As<Uint32>()->Value();
367
  if (params->iv.size() != 16 ||
368
      params->length == 0 ||
369
      params->length > 128) {
370
    THROW_ERR_CRYPTO_INVALID_COUNTER(env);
371
    return false;
372
  }
373
  return true;
374
}
375
376
bool ValidateAuthTag(
377
    Environment* env,
378
    CryptoJobMode mode,
379
    WebCryptoCipherMode cipher_mode,
380
    Local<Value> value,
381
    AESCipherConfig* params) {
382
  switch (cipher_mode) {
383
    case kWebCryptoCipherDecrypt: {
384
      if (!IsAnyByteSource(value)) {
385
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
386
        return false;
387
      }
388
      ArrayBufferOrViewContents<char> tag_contents(value);
389
      if (UNLIKELY(!tag_contents.CheckSizeInt32())) {
390
        THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
391
        return false;
392
      }
393
      params->tag = mode == kCryptoJobAsync
394
          ? tag_contents.ToCopy()
395
          : tag_contents.ToByteSource();
396
      break;
397
    }
398
    case kWebCryptoCipherEncrypt: {
399
      if (!value->IsUint32()) {
400
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
401
        return false;
402
      }
403
      params->length = value.As<Uint32>()->Value();
404
      if (params->length > 128) {
405
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
406
        return false;
407
      }
408
      break;
409
    }
410
    default:
411
      UNREACHABLE();
412
  }
413
  return true;
414
}
415
416
bool ValidateAdditionalData(
417
    Environment* env,
418
    CryptoJobMode mode,
419
    Local<Value> value,
420
    AESCipherConfig* params) {
421
  // Additional Data
422
  if (IsAnyByteSource(value)) {
423
    ArrayBufferOrViewContents<char> additional(value);
424
    if (UNLIKELY(!additional.CheckSizeInt32())) {
425
      THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
426
      return false;
427
    }
428
    params->additional_data = mode == kCryptoJobAsync
429
        ? additional.ToCopy()
430
        : additional.ToByteSource();
431
  }
432
  return true;
433
}
434
435
void UseDefaultIV(AESCipherConfig* params) {
436
  params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
437
}
438
}  // namespace
439
440
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
441
    : mode(other.mode),
442
      variant(other.variant),
443
      cipher(other.cipher),
444
      length(other.length),
445
      iv(std::move(other.iv)),
446
      additional_data(std::move(other.additional_data)),
447
      tag(std::move(other.tag)) {}
448
449
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
450
  if (&other == this) return *this;
451
  this->~AESCipherConfig();
452
  return *new (this) AESCipherConfig(std::move(other));
453
}
454
455
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
456
  // If mode is sync, then the data in each of these properties
457
  // is not owned by the AESCipherConfig, so we ignore it.
458
  if (mode == kCryptoJobAsync) {
459
    tracker->TrackFieldWithSize("iv", iv.size());
460
    tracker->TrackFieldWithSize("additional_data", additional_data.size());
461
    tracker->TrackFieldWithSize("tag", tag.size());
462
  }
463
}
464
465
Maybe<bool> AESCipherTraits::AdditionalConfig(
466
    CryptoJobMode mode,
467
    const FunctionCallbackInfo<Value>& args,
468
    unsigned int offset,
469
    WebCryptoCipherMode cipher_mode,
470
    AESCipherConfig* params) {
471
  Environment* env = Environment::GetCurrent(args);
472
473
  params->mode = mode;
474
475
  CHECK(args[offset]->IsUint32());  // Key Variant
476
  params->variant =
477
      static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
478
479
  int cipher_nid;
480
481
  switch (params->variant) {
482
    case kKeyVariantAES_CTR_128:
483
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
484
          !ValidateCounter(env, args[offset + 2], params)) {
485
        return Nothing<bool>();
486
      }
487
      cipher_nid = NID_aes_128_ctr;
488
      break;
489
    case kKeyVariantAES_CTR_192:
490
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
491
          !ValidateCounter(env, args[offset + 2], params)) {
492
        return Nothing<bool>();
493
      }
494
      cipher_nid = NID_aes_192_ctr;
495
      break;
496
    case kKeyVariantAES_CTR_256:
497
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
498
          !ValidateCounter(env, args[offset + 2], params)) {
499
        return Nothing<bool>();
500
      }
501
      cipher_nid = NID_aes_256_ctr;
502
      break;
503
    case kKeyVariantAES_CBC_128:
504
      if (!ValidateIV(env, mode, args[offset + 1], params))
505
        return Nothing<bool>();
506
      cipher_nid = NID_aes_128_cbc;
507
      break;
508
    case kKeyVariantAES_CBC_192:
509
      if (!ValidateIV(env, mode, args[offset + 1], params))
510
        return Nothing<bool>();
511
      cipher_nid = NID_aes_192_cbc;
512
      break;
513
    case kKeyVariantAES_CBC_256:
514
      if (!ValidateIV(env, mode, args[offset + 1], params))
515
        return Nothing<bool>();
516
      cipher_nid = NID_aes_256_cbc;
517
      break;
518
    case kKeyVariantAES_KW_128:
519
      UseDefaultIV(params);
520
      cipher_nid = NID_id_aes128_wrap;
521
      break;
522
    case kKeyVariantAES_KW_192:
523
      UseDefaultIV(params);
524
      cipher_nid = NID_id_aes192_wrap;
525
      break;
526
    case kKeyVariantAES_KW_256:
527
      UseDefaultIV(params);
528
      cipher_nid = NID_id_aes256_wrap;
529
      break;
530
    case kKeyVariantAES_GCM_128:
531
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
532
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
533
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
534
        return Nothing<bool>();
535
      }
536
      cipher_nid = NID_aes_128_gcm;
537
      break;
538
    case kKeyVariantAES_GCM_192:
539
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
540
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
541
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
542
        return Nothing<bool>();
543
      }
544
      cipher_nid = NID_aes_192_gcm;
545
      break;
546
    case kKeyVariantAES_GCM_256:
547
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
548
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
549
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
550
        return Nothing<bool>();
551
      }
552
      cipher_nid = NID_aes_256_gcm;
553
      break;
554
    default:
555
      UNREACHABLE();
556
  }
557
558
  params->cipher = EVP_get_cipherbynid(cipher_nid);
559
  CHECK_NOT_NULL(params->cipher);
560
561
  if (params->iv.size() <
562
      static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
563
    THROW_ERR_CRYPTO_INVALID_IV(env);
564
    return Nothing<bool>();
565
  }
566
567
  return Just(true);
568
}
569
570
WebCryptoCipherStatus AESCipherTraits::DoCipher(
571
    Environment* env,
572
    std::shared_ptr<KeyObjectData> key_data,
573
    WebCryptoCipherMode cipher_mode,
574
    const AESCipherConfig& params,
575
    const ByteSource& in,
576
    ByteSource* out) {
577
#define V(name, fn)                                                           \
578
  case kKeyVariantAES_ ## name:                                               \
579
    return fn(env, key_data.get(), cipher_mode, params, in, out);
580
  switch (params.variant) {
581
    VARIANTS(V)
582
    default:
583
      UNREACHABLE();
584
  }
585
#undef V
586
}
587
588
2
void AES::Initialize(Environment* env, Local<Object> target) {
589
2
  AESCryptoJob::Initialize(env, target);
590
591
#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
592
48
  VARIANTS(V)
593
#undef V
594
6
}
595
2
596
4
}  // namespace crypto
597

368
}  // namespace node