GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: crypto/crypto_aes.cc Lines: 212 307 69.1 %
Date: 2021-10-03 04:15:53 Branches: 132 238 55.5 %

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
801
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
801
  CHECK_NOT_NULL(key_data);
43
801
  CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
44
45
801
  const int mode = EVP_CIPHER_mode(params.cipher);
46
47
1602
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
48
801
  EVP_CIPHER_CTX_init(ctx.get());
49
801
  if (mode == EVP_CIPH_WRAP_MODE)
50
76
    EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
51
52
801
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
53
54
801
  if (!EVP_CipherInit_ex(
55
          ctx.get(),
56
801
          params.cipher,
57
          nullptr,
58
          nullptr,
59
          nullptr,
60
          encrypt)) {
61
    // Cipher init failed
62
    return WebCryptoCipherStatus::FAILED;
63
  }
64
65

1275
  if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
66
        ctx.get(),
67
        EVP_CTRL_AEAD_SET_IVLEN,
68
474
        params.iv.size(),
69
        nullptr)) {
70
    return WebCryptoCipherStatus::FAILED;
71
  }
72
73
801
  if (!EVP_CIPHER_CTX_set_key_length(
74
          ctx.get(),
75

2403
          key_data->GetSymmetricKeySize()) ||
76
1602
      !EVP_CipherInit_ex(
77
          ctx.get(),
78
          nullptr,
79
          nullptr,
80
801
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
81
          params.iv.data<unsigned char>(),
82
          encrypt)) {
83
    return WebCryptoCipherStatus::FAILED;
84
  }
85
86
801
  size_t tag_len = 0;
87
88
801
  if (mode == EVP_CIPH_GCM_MODE) {
89
474
    switch (cipher_mode) {
90
228
      case kWebCryptoCipherDecrypt:
91
        // If in decrypt mode, the auth tag must be set in the params.tag.
92
228
        CHECK(params.tag);
93
228
        if (!EVP_CIPHER_CTX_ctrl(
94
                ctx.get(),
95
                EVP_CTRL_AEAD_SET_TAG,
96
228
                params.tag.size(),
97
228
                const_cast<char*>(params.tag.get()))) {
98
          return WebCryptoCipherStatus::FAILED;
99
        }
100
228
        break;
101
246
      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
246
        tag_len = params.length;
108
246
        break;
109
      default:
110
        UNREACHABLE();
111
    }
112
  }
113
114
801
  size_t total = 0;
115
801
  int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
116
  int out_len;
117
118
474
  if (mode == EVP_CIPH_GCM_MODE &&
119

1275
      params.additional_data.size() &&
120
318
      !EVP_CipherUpdate(
121
            ctx.get(),
122
            nullptr,
123
            &out_len,
124
            params.additional_data.data<unsigned char>(),
125
318
            params.additional_data.size())) {
126
    return WebCryptoCipherStatus::FAILED;
127
  }
128
129
801
  char* data = MallocOpenSSL<char>(buf_len);
130
1602
  ByteSource buf = ByteSource::Allocated(data, buf_len);
131
801
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
132
133
  // In some outdated version of OpenSSL (e.g.
134
  // ubi81_sharedlibs_openssl111fips_x64) may be used in sharedlib mode, the
135
  // logic will be failed when input size is zero. The newly OpenSSL has fixed
136
  // it up. But we still have to regard zero as special in Node.js code to
137
  // prevent old OpenSSL failure.
138
  //
139
  // Refs: https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f
140
  // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
141
801
  if (in.size() == 0) {
142
2
    out_len = 0;
143
799
  } else if (!EVP_CipherUpdate(
144
          ctx.get(),
145
          ptr,
146
          &out_len,
147
          in.data<unsigned char>(),
148
799
          in.size())) {
149
    return WebCryptoCipherStatus::FAILED;
150
  }
151
152
801
  total += out_len;
153
801
  CHECK_LE(out_len, buf_len);
154
801
  ptr += out_len;
155
801
  out_len = EVP_CIPHER_CTX_block_size(ctx.get());
156
801
  if (!EVP_CipherFinal_ex(ctx.get(), ptr, &out_len)) {
157
15
    return WebCryptoCipherStatus::FAILED;
158
  }
159
786
  total += out_len;
160
161
  // If using AES_GCM, grab the generated auth tag and append
162
  // it to the end of the ciphertext.
163

786
  if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
164
246
    data += out_len;
165
246
    if (!EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, tag_len, ptr))
166
      return WebCryptoCipherStatus::FAILED;
167
246
    total += tag_len;
168
  }
169
170
  // It's possible that we haven't used the full allocated space. Size down.
171
786
  buf.Resize(total);
172
786
  *out = std::move(buf);
173
174
786
  return WebCryptoCipherStatus::OK;
175
}
176
177
// The AES_CTR implementation here takes it's inspiration from the chromium
178
// implementation here:
179
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
180
181
template <typename T>
182
236
T CeilDiv(T a, T b) {
183
236
  return a == 0 ? 0 : 1 + (a - 1) / b;
184
}
185
186
236
BignumPointer GetCounter(const AESCipherConfig& params) {
187
236
  unsigned int remainder = (params.length % CHAR_BIT);
188
236
  const unsigned char* data = params.iv.data<unsigned char>();
189
190
236
  if (remainder == 0) {
191
236
    unsigned int byte_length = params.length / CHAR_BIT;
192
    return BignumPointer(BN_bin2bn(
193
472
        data + params.iv.size() - byte_length,
194
        byte_length,
195
236
        nullptr));
196
  }
197
198
  unsigned int byte_length =
199
      CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
200
201
  std::vector<unsigned char> counter(
202
      data + params.iv.size() - byte_length,
203
      data + params.iv.size());
204
  counter[0] &= ~(0xFF << remainder);
205
206
  return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
207
}
208
209
std::vector<unsigned char> BlockWithZeroedCounter(
210
    const AESCipherConfig& params) {
211
  unsigned int length_bytes = params.length / CHAR_BIT;
212
  unsigned int remainder = params.length % CHAR_BIT;
213
214
  const unsigned char* data = params.iv.data<unsigned char>();
215
216
  std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
217
218
  size_t index = new_counter_block.size() - length_bytes;
219
  memset(&new_counter_block.front() + index, 0, length_bytes);
220
221
  if (remainder)
222
    new_counter_block[index - 1] &= 0xFF << remainder;
223
224
  return new_counter_block;
225
}
226
227
236
WebCryptoCipherStatus AES_CTR_Cipher2(
228
    KeyObjectData* key_data,
229
    WebCryptoCipherMode cipher_mode,
230
    const AESCipherConfig& params,
231
    const ByteSource& in,
232
    unsigned const char* counter,
233
    unsigned char* out) {
234
472
  CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
235
236
  const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
236
237
236
  if (!EVP_CipherInit_ex(
238
          ctx.get(),
239
236
          params.cipher,
240
          nullptr,
241
236
          reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
242
          counter,
243
          encrypt)) {
244
    // Cipher init failed
245
    return WebCryptoCipherStatus::FAILED;
246
  }
247
248
236
  int out_len = 0;
249
236
  int final_len = 0;
250
236
  if (!EVP_CipherUpdate(
251
          ctx.get(),
252
          out,
253
          &out_len,
254
          in.data<unsigned char>(),
255
236
          in.size())) {
256
    return WebCryptoCipherStatus::FAILED;
257
  }
258
259
236
  if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
260
    return WebCryptoCipherStatus::FAILED;
261
262
236
  out_len += final_len;
263
236
  if (static_cast<unsigned>(out_len) != in.size())
264
    return WebCryptoCipherStatus::FAILED;
265
266
236
  return WebCryptoCipherStatus::OK;
267
}
268
269
236
WebCryptoCipherStatus AES_CTR_Cipher(
270
    Environment* env,
271
    KeyObjectData* key_data,
272
    WebCryptoCipherMode cipher_mode,
273
    const AESCipherConfig& params,
274
    const ByteSource& in,
275
    ByteSource* out) {
276
472
  BignumPointer num_counters(BN_new());
277
236
  if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
278
    return WebCryptoCipherStatus::FAILED;
279
280
472
  BignumPointer current_counter = GetCounter(params);
281
282
472
  BignumPointer num_output(BN_new());
283
284
236
  if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
285
    return WebCryptoCipherStatus::FAILED;
286
287
  // Just like in chromium's implementation, if the counter will
288
  // be incremented more than there are counter values, we fail.
289
236
  if (BN_cmp(num_output.get(), num_counters.get()) > 0)
290
    return WebCryptoCipherStatus::FAILED;
291
292
472
  BignumPointer remaining_until_reset(BN_new());
293
236
  if (!BN_sub(remaining_until_reset.get(),
294
236
              num_counters.get(),
295
236
              current_counter.get())) {
296
    return WebCryptoCipherStatus::FAILED;
297
  }
298
299
  // Output size is identical to the input size
300
236
  char* data = MallocOpenSSL<char>(in.size());
301
472
  ByteSource buf = ByteSource::Allocated(data, in.size());
302
236
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
303
304
  // Also just like in chromium's implementation, if we can process
305
  // the input without wrapping the counter, we'll do it as a single
306
  // call here. If we can't, we'll fallback to the a two-step approach
307
236
  if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
308
236
    auto status = AES_CTR_Cipher2(
309
        key_data,
310
        cipher_mode,
311
        params,
312
        in,
313
        params.iv.data<unsigned char>(),
314
        ptr);
315
236
    if (status == WebCryptoCipherStatus::OK)
316
236
      *out = std::move(buf);
317
236
    return status;
318
  }
319
320
  BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
321
  BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
322
323
  // Encrypt the first part...
324
  auto status = AES_CTR_Cipher2(
325
      key_data,
326
      cipher_mode,
327
      params,
328
      ByteSource::Foreign(in.get(), input_size_part1),
329
      params.iv.data<unsigned char>(),
330
      ptr);
331
332
  if (status != WebCryptoCipherStatus::OK)
333
    return status;
334
335
  // Wrap the counter around to zero
336
  std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
337
338
  // Encrypt the second part...
339
  status = AES_CTR_Cipher2(
340
      key_data,
341
      cipher_mode,
342
      params,
343
      ByteSource::Foreign(
344
          in.get() + input_size_part1,
345
          in.size() - input_size_part1),
346
      new_counter_block.data(),
347
      ptr + input_size_part1);
348
349
  if (status == WebCryptoCipherStatus::OK)
350
    *out = std::move(buf);
351
352
  return status;
353
}
354
355
961
bool ValidateIV(
356
    Environment* env,
357
    CryptoJobMode mode,
358
    Local<Value> value,
359
    AESCipherConfig* params) {
360
1922
  ArrayBufferOrViewContents<char> iv(value);
361
961
  if (UNLIKELY(!iv.CheckSizeInt32())) {
362
    THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
363
    return false;
364
  }
365
  params->iv = (mode == kCryptoJobAsync)
366
1922
      ? iv.ToCopy()
367
961
      : iv.ToByteSource();
368
961
  return true;
369
}
370
371
236
bool ValidateCounter(
372
  Environment* env,
373
  Local<Value> value,
374
  AESCipherConfig* params) {
375
236
  CHECK(value->IsUint32());  // Length
376
236
  params->length = value.As<Uint32>()->Value();
377
236
  if (params->iv.size() != 16 ||
378

472
      params->length == 0 ||
379
236
      params->length > 128) {
380
    THROW_ERR_CRYPTO_INVALID_COUNTER(env);
381
    return false;
382
  }
383
236
  return true;
384
}
385
386
474
bool ValidateAuthTag(
387
    Environment* env,
388
    CryptoJobMode mode,
389
    WebCryptoCipherMode cipher_mode,
390
    Local<Value> value,
391
    AESCipherConfig* params) {
392
474
  switch (cipher_mode) {
393
228
    case kWebCryptoCipherDecrypt: {
394
228
      if (!IsAnyByteSource(value)) {
395
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
396
        return false;
397
      }
398
228
      ArrayBufferOrViewContents<char> tag_contents(value);
399
228
      if (UNLIKELY(!tag_contents.CheckSizeInt32())) {
400
        THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
401
        return false;
402
      }
403
      params->tag = mode == kCryptoJobAsync
404
456
          ? tag_contents.ToCopy()
405
228
          : tag_contents.ToByteSource();
406
228
      break;
407
    }
408
246
    case kWebCryptoCipherEncrypt: {
409
246
      if (!value->IsUint32()) {
410
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
411
        return false;
412
      }
413
246
      params->length = value.As<Uint32>()->Value();
414
246
      if (params->length > 128) {
415
        THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
416
        return false;
417
      }
418
246
      break;
419
    }
420
    default:
421
      UNREACHABLE();
422
  }
423
474
  return true;
424
}
425
426
474
bool ValidateAdditionalData(
427
    Environment* env,
428
    CryptoJobMode mode,
429
    Local<Value> value,
430
    AESCipherConfig* params) {
431
  // Additional Data
432
474
  if (IsAnyByteSource(value)) {
433
318
    ArrayBufferOrViewContents<char> additional(value);
434
318
    if (UNLIKELY(!additional.CheckSizeInt32())) {
435
      THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
436
      return false;
437
    }
438
    params->additional_data = mode == kCryptoJobAsync
439
636
        ? additional.ToCopy()
440
318
        : additional.ToByteSource();
441
  }
442
474
  return true;
443
}
444
445
76
void UseDefaultIV(AESCipherConfig* params) {
446
76
  params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
447
76
}
448
}  // namespace
449
450
1037
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
451
1037
    : mode(other.mode),
452
1037
      variant(other.variant),
453
1037
      cipher(other.cipher),
454
1037
      length(other.length),
455
1037
      iv(std::move(other.iv)),
456
1037
      additional_data(std::move(other.additional_data)),
457
1037
      tag(std::move(other.tag)) {}
458
459
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
460
  if (&other == this) return *this;
461
  this->~AESCipherConfig();
462
  return *new (this) AESCipherConfig(std::move(other));
463
}
464
465
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
466
  // If mode is sync, then the data in each of these properties
467
  // is not owned by the AESCipherConfig, so we ignore it.
468
  if (mode == kCryptoJobAsync) {
469
    tracker->TrackFieldWithSize("iv", iv.size());
470
    tracker->TrackFieldWithSize("additional_data", additional_data.size());
471
    tracker->TrackFieldWithSize("tag", tag.size());
472
  }
473
}
474
475
1037
Maybe<bool> AESCipherTraits::AdditionalConfig(
476
    CryptoJobMode mode,
477
    const FunctionCallbackInfo<Value>& args,
478
    unsigned int offset,
479
    WebCryptoCipherMode cipher_mode,
480
    AESCipherConfig* params) {
481
1037
  Environment* env = Environment::GetCurrent(args);
482
483
1037
  params->mode = mode;
484
485

2074
  CHECK(args[offset]->IsUint32());  // Key Variant
486
1037
  params->variant =
487
3111
      static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
488
489
  int cipher_nid;
490
491



1037
  switch (params->variant) {
492
223
    case kKeyVariantAES_CTR_128:
493

669
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
494

446
          !ValidateCounter(env, args[offset + 2], params)) {
495
        return Nothing<bool>();
496
      }
497
223
      cipher_nid = NID_aes_128_ctr;
498
223
      break;
499
4
    case kKeyVariantAES_CTR_192:
500

12
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
501

8
          !ValidateCounter(env, args[offset + 2], params)) {
502
        return Nothing<bool>();
503
      }
504
4
      cipher_nid = NID_aes_192_ctr;
505
4
      break;
506
9
    case kKeyVariantAES_CTR_256:
507

27
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
508

18
          !ValidateCounter(env, args[offset + 2], params)) {
509
        return Nothing<bool>();
510
      }
511
9
      cipher_nid = NID_aes_256_ctr;
512
9
      break;
513
229
    case kKeyVariantAES_CBC_128:
514

458
      if (!ValidateIV(env, mode, args[offset + 1], params))
515
        return Nothing<bool>();
516
229
      cipher_nid = NID_aes_128_cbc;
517
229
      break;
518
7
    case kKeyVariantAES_CBC_192:
519

14
      if (!ValidateIV(env, mode, args[offset + 1], params))
520
        return Nothing<bool>();
521
7
      cipher_nid = NID_aes_192_cbc;
522
7
      break;
523
15
    case kKeyVariantAES_CBC_256:
524

30
      if (!ValidateIV(env, mode, args[offset + 1], params))
525
        return Nothing<bool>();
526
15
      cipher_nid = NID_aes_256_cbc;
527
15
      break;
528
76
    case kKeyVariantAES_KW_128:
529
76
      UseDefaultIV(params);
530
76
      cipher_nid = NID_id_aes128_wrap;
531
76
      break;
532
    case kKeyVariantAES_KW_192:
533
      UseDefaultIV(params);
534
      cipher_nid = NID_id_aes192_wrap;
535
      break;
536
    case kKeyVariantAES_KW_256:
537
      UseDefaultIV(params);
538
      cipher_nid = NID_id_aes256_wrap;
539
      break;
540
314
    case kKeyVariantAES_GCM_128:
541
628
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
542


942
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
543

628
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
544
        return Nothing<bool>();
545
      }
546
314
      cipher_nid = NID_aes_128_gcm;
547
314
      break;
548
56
    case kKeyVariantAES_GCM_192:
549
112
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
550


168
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
551

112
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
552
        return Nothing<bool>();
553
      }
554
56
      cipher_nid = NID_aes_192_gcm;
555
56
      break;
556
104
    case kKeyVariantAES_GCM_256:
557
208
      if (!ValidateIV(env, mode, args[offset + 1], params) ||
558


312
          !ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
559

208
          !ValidateAdditionalData(env, mode, args[offset + 3], params)) {
560
        return Nothing<bool>();
561
      }
562
104
      cipher_nid = NID_aes_256_gcm;
563
104
      break;
564
    default:
565
      UNREACHABLE();
566
  }
567
568
1037
  params->cipher = EVP_get_cipherbynid(cipher_nid);
569
1037
  CHECK_NOT_NULL(params->cipher);
570
571
1037
  if (params->iv.size() <
572
1037
      static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
573
    THROW_ERR_CRYPTO_INVALID_IV(env);
574
    return Nothing<bool>();
575
  }
576
577
1037
  return Just(true);
578
}
579
580
1037
WebCryptoCipherStatus AESCipherTraits::DoCipher(
581
    Environment* env,
582
    std::shared_ptr<KeyObjectData> key_data,
583
    WebCryptoCipherMode cipher_mode,
584
    const AESCipherConfig& params,
585
    const ByteSource& in,
586
    ByteSource* out) {
587
#define V(name, fn)                                                           \
588
  case kKeyVariantAES_ ## name:                                               \
589
    return fn(env, key_data.get(), cipher_mode, params, in, out);
590



1037
  switch (params.variant) {
591
1037
    VARIANTS(V)
592
    default:
593
      UNREACHABLE();
594
  }
595
#undef V
596
}
597
598
4300
void AES::Initialize(Environment* env, Local<Object> target) {
599
4300
  AESCryptoJob::Initialize(env, target);
600
601
#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
602
103200
  VARIANTS(V)
603
#undef V
604
4300
}
605
606
}  // namespace crypto
607
}  // namespace node