• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 #include "crypto/crypto_aes.h"
2 #include "async_wrap-inl.h"
3 #include "base_object-inl.h"
4 #include "crypto/crypto_cipher.h"
5 #include "crypto/crypto_keys.h"
6 #include "crypto/crypto_util.h"
7 #include "env-inl.h"
8 #include "memory_tracker-inl.h"
9 #include "threadpoolwork-inl.h"
10 #include "v8.h"
11 
12 #include <openssl/bn.h>
13 #include <openssl/aes.h>
14 
15 #include <vector>
16 
17 namespace node {
18 
19 using v8::FunctionCallbackInfo;
20 using v8::Just;
21 using v8::Local;
22 using v8::Maybe;
23 using v8::Nothing;
24 using v8::Object;
25 using v8::Uint32;
26 using v8::Value;
27 
28 namespace crypto {
29 namespace {
30 // Implements general AES encryption and decryption for CBC
31 // The key_data must be a secret key.
32 // On success, this function sets out to a new ByteSource
33 // instance containing the results and returns WebCryptoCipherStatus::OK.
AES_Cipher(Environment * env,KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)34 WebCryptoCipherStatus AES_Cipher(
35     Environment* env,
36     KeyObjectData* key_data,
37     WebCryptoCipherMode cipher_mode,
38     const AESCipherConfig& params,
39     const ByteSource& in,
40     ByteSource* out) {
41   CHECK_NOT_NULL(key_data);
42   CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
43 
44   const int mode = EVP_CIPHER_mode(params.cipher);
45 
46   CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
47   EVP_CIPHER_CTX_init(ctx.get());
48   if (mode == EVP_CIPH_WRAP_MODE)
49     EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
50 
51   const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
52 
53   if (!EVP_CipherInit_ex(
54           ctx.get(),
55           params.cipher,
56           nullptr,
57           nullptr,
58           nullptr,
59           encrypt)) {
60     // Cipher init failed
61     return WebCryptoCipherStatus::FAILED;
62   }
63 
64   if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
65         ctx.get(),
66         EVP_CTRL_AEAD_SET_IVLEN,
67         params.iv.size(),
68         nullptr)) {
69     return WebCryptoCipherStatus::FAILED;
70   }
71 
72   if (!EVP_CIPHER_CTX_set_key_length(
73           ctx.get(),
74           key_data->GetSymmetricKeySize()) ||
75       !EVP_CipherInit_ex(
76           ctx.get(),
77           nullptr,
78           nullptr,
79           reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
80           params.iv.data<unsigned char>(),
81           encrypt)) {
82     return WebCryptoCipherStatus::FAILED;
83   }
84 
85   size_t tag_len = 0;
86 
87   if (mode == EVP_CIPH_GCM_MODE) {
88     switch (cipher_mode) {
89       case kWebCryptoCipherDecrypt:
90         // If in decrypt mode, the auth tag must be set in the params.tag.
91         CHECK(params.tag);
92         if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
93                                  EVP_CTRL_AEAD_SET_TAG,
94                                  params.tag.size(),
95                                  const_cast<char*>(params.tag.data<char>()))) {
96           return WebCryptoCipherStatus::FAILED;
97         }
98         break;
99       case kWebCryptoCipherEncrypt:
100         // In decrypt mode, we grab the tag length here. We'll use it to
101         // ensure that that allocated buffer has enough room for both the
102         // final block and the auth tag. Unlike our other AES-GCM implementation
103         // in CipherBase, in WebCrypto, the auth tag is concatenated to the end
104         // of the generated ciphertext and returned in the same ArrayBuffer.
105         tag_len = params.length;
106         break;
107       default:
108         UNREACHABLE();
109     }
110   }
111 
112   size_t total = 0;
113   int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
114   int out_len;
115 
116   if (mode == EVP_CIPH_GCM_MODE &&
117       params.additional_data.size() &&
118       !EVP_CipherUpdate(
119             ctx.get(),
120             nullptr,
121             &out_len,
122             params.additional_data.data<unsigned char>(),
123             params.additional_data.size())) {
124     return WebCryptoCipherStatus::FAILED;
125   }
126 
127   ByteSource::Builder buf(buf_len);
128 
129   // In some outdated version of OpenSSL (e.g.
130   // ubi81_sharedlibs_openssl111fips_x64) may be used in sharedlib mode, the
131   // logic will be failed when input size is zero. The newly OpenSSL has fixed
132   // it up. But we still have to regard zero as special in Node.js code to
133   // prevent old OpenSSL failure.
134   //
135   // Refs: https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f
136   // Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
137   if (in.size() == 0) {
138     out_len = 0;
139   } else if (!EVP_CipherUpdate(ctx.get(),
140                                buf.data<unsigned char>(),
141                                &out_len,
142                                in.data<unsigned char>(),
143                                in.size())) {
144     return WebCryptoCipherStatus::FAILED;
145   }
146 
147   total += out_len;
148   CHECK_LE(out_len, buf_len);
149   out_len = EVP_CIPHER_CTX_block_size(ctx.get());
150   if (!EVP_CipherFinal_ex(
151           ctx.get(), buf.data<unsigned char>() + total, &out_len)) {
152     return WebCryptoCipherStatus::FAILED;
153   }
154   total += out_len;
155 
156   // If using AES_GCM, grab the generated auth tag and append
157   // it to the end of the ciphertext.
158   if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
159     if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
160                              EVP_CTRL_AEAD_GET_TAG,
161                              tag_len,
162                              buf.data<unsigned char>() + total))
163       return WebCryptoCipherStatus::FAILED;
164     total += tag_len;
165   }
166 
167   // It's possible that we haven't used the full allocated space. Size down.
168   *out = std::move(buf).release(total);
169 
170   return WebCryptoCipherStatus::OK;
171 }
172 
173 // The AES_CTR implementation here takes it's inspiration from the chromium
174 // implementation here:
175 // https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
176 
177 template <typename T>
CeilDiv(T a,T b)178 T CeilDiv(T a, T b) {
179   return a == 0 ? 0 : 1 + (a - 1) / b;
180 }
181 
GetCounter(const AESCipherConfig & params)182 BignumPointer GetCounter(const AESCipherConfig& params) {
183   unsigned int remainder = (params.length % CHAR_BIT);
184   const unsigned char* data = params.iv.data<unsigned char>();
185 
186   if (remainder == 0) {
187     unsigned int byte_length = params.length / CHAR_BIT;
188     return BignumPointer(BN_bin2bn(
189         data + params.iv.size() - byte_length,
190         byte_length,
191         nullptr));
192   }
193 
194   unsigned int byte_length =
195       CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
196 
197   std::vector<unsigned char> counter(
198       data + params.iv.size() - byte_length,
199       data + params.iv.size());
200   counter[0] &= ~(0xFF << remainder);
201 
202   return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
203 }
204 
BlockWithZeroedCounter(const AESCipherConfig & params)205 std::vector<unsigned char> BlockWithZeroedCounter(
206     const AESCipherConfig& params) {
207   unsigned int length_bytes = params.length / CHAR_BIT;
208   unsigned int remainder = params.length % CHAR_BIT;
209 
210   const unsigned char* data = params.iv.data<unsigned char>();
211 
212   std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
213 
214   size_t index = new_counter_block.size() - length_bytes;
215   memset(&new_counter_block.front() + index, 0, length_bytes);
216 
217   if (remainder)
218     new_counter_block[index - 1] &= 0xFF << remainder;
219 
220   return new_counter_block;
221 }
222 
AES_CTR_Cipher2(KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,unsigned const char * counter,unsigned char * out)223 WebCryptoCipherStatus AES_CTR_Cipher2(
224     KeyObjectData* key_data,
225     WebCryptoCipherMode cipher_mode,
226     const AESCipherConfig& params,
227     const ByteSource& in,
228     unsigned const char* counter,
229     unsigned char* out) {
230   CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
231   const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
232 
233   if (!EVP_CipherInit_ex(
234           ctx.get(),
235           params.cipher,
236           nullptr,
237           reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
238           counter,
239           encrypt)) {
240     // Cipher init failed
241     return WebCryptoCipherStatus::FAILED;
242   }
243 
244   int out_len = 0;
245   int final_len = 0;
246   if (!EVP_CipherUpdate(
247           ctx.get(),
248           out,
249           &out_len,
250           in.data<unsigned char>(),
251           in.size())) {
252     return WebCryptoCipherStatus::FAILED;
253   }
254 
255   if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
256     return WebCryptoCipherStatus::FAILED;
257 
258   out_len += final_len;
259   if (static_cast<unsigned>(out_len) != in.size())
260     return WebCryptoCipherStatus::FAILED;
261 
262   return WebCryptoCipherStatus::OK;
263 }
264 
AES_CTR_Cipher(Environment * env,KeyObjectData * key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)265 WebCryptoCipherStatus AES_CTR_Cipher(
266     Environment* env,
267     KeyObjectData* key_data,
268     WebCryptoCipherMode cipher_mode,
269     const AESCipherConfig& params,
270     const ByteSource& in,
271     ByteSource* out) {
272   BignumPointer num_counters(BN_new());
273   if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
274     return WebCryptoCipherStatus::FAILED;
275 
276   BignumPointer current_counter = GetCounter(params);
277 
278   BignumPointer num_output(BN_new());
279 
280   if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
281     return WebCryptoCipherStatus::FAILED;
282 
283   // Just like in chromium's implementation, if the counter will
284   // be incremented more than there are counter values, we fail.
285   if (BN_cmp(num_output.get(), num_counters.get()) > 0)
286     return WebCryptoCipherStatus::FAILED;
287 
288   BignumPointer remaining_until_reset(BN_new());
289   if (!BN_sub(remaining_until_reset.get(),
290               num_counters.get(),
291               current_counter.get())) {
292     return WebCryptoCipherStatus::FAILED;
293   }
294 
295   // Output size is identical to the input size.
296   ByteSource::Builder buf(in.size());
297 
298   // Also just like in chromium's implementation, if we can process
299   // the input without wrapping the counter, we'll do it as a single
300   // call here. If we can't, we'll fallback to the a two-step approach
301   if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
302     auto status = AES_CTR_Cipher2(key_data,
303                                   cipher_mode,
304                                   params,
305                                   in,
306                                   params.iv.data<unsigned char>(),
307                                   buf.data<unsigned char>());
308     if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
309     return status;
310   }
311 
312   BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
313   BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
314 
315   // Encrypt the first part...
316   auto status =
317       AES_CTR_Cipher2(key_data,
318                       cipher_mode,
319                       params,
320                       ByteSource::Foreign(in.data<char>(), input_size_part1),
321                       params.iv.data<unsigned char>(),
322                       buf.data<unsigned char>());
323 
324   if (status != WebCryptoCipherStatus::OK)
325     return status;
326 
327   // Wrap the counter around to zero
328   std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
329 
330   // Encrypt the second part...
331   status =
332       AES_CTR_Cipher2(key_data,
333                       cipher_mode,
334                       params,
335                       ByteSource::Foreign(in.data<char>() + input_size_part1,
336                                           in.size() - input_size_part1),
337                       new_counter_block.data(),
338                       buf.data<unsigned char>() + input_size_part1);
339 
340   if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
341 
342   return status;
343 }
344 
ValidateIV(Environment * env,CryptoJobMode mode,Local<Value> value,AESCipherConfig * params)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 
ValidateCounter(Environment * env,Local<Value> value,AESCipherConfig * params)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 
ValidateAuthTag(Environment * env,CryptoJobMode mode,WebCryptoCipherMode cipher_mode,Local<Value> value,AESCipherConfig * params)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 
ValidateAdditionalData(Environment * env,CryptoJobMode mode,Local<Value> value,AESCipherConfig * params)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 
UseDefaultIV(AESCipherConfig * params)435 void UseDefaultIV(AESCipherConfig* params) {
436   params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
437 }
438 }  // namespace
439 
AESCipherConfig(AESCipherConfig && other)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 
operator =(AESCipherConfig && other)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 
MemoryInfo(MemoryTracker * tracker) const455 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 
AdditionalConfig(CryptoJobMode mode,const FunctionCallbackInfo<Value> & args,unsigned int offset,WebCryptoCipherMode cipher_mode,AESCipherConfig * params)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   if (params->cipher == nullptr) {
560     THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
561     return Nothing<bool>();
562   }
563 
564   if (params->iv.size() <
565       static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
566     THROW_ERR_CRYPTO_INVALID_IV(env);
567     return Nothing<bool>();
568   }
569 
570   return Just(true);
571 }
572 
DoCipher(Environment * env,std::shared_ptr<KeyObjectData> key_data,WebCryptoCipherMode cipher_mode,const AESCipherConfig & params,const ByteSource & in,ByteSource * out)573 WebCryptoCipherStatus AESCipherTraits::DoCipher(
574     Environment* env,
575     std::shared_ptr<KeyObjectData> key_data,
576     WebCryptoCipherMode cipher_mode,
577     const AESCipherConfig& params,
578     const ByteSource& in,
579     ByteSource* out) {
580 #define V(name, fn)                                                           \
581   case kKeyVariantAES_ ## name:                                               \
582     return fn(env, key_data.get(), cipher_mode, params, in, out);
583   switch (params.variant) {
584     VARIANTS(V)
585     default:
586       UNREACHABLE();
587   }
588 #undef V
589 }
590 
Initialize(Environment * env,Local<Object> target)591 void AES::Initialize(Environment* env, Local<Object> target) {
592   AESCryptoJob::Initialize(env, target);
593 
594 #define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
595   VARIANTS(V)
596 #undef V
597 }
598 
RegisterExternalReferences(ExternalReferenceRegistry * registry)599 void AES::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
600   AESCryptoJob::RegisterExternalReferences(registry);
601 }
602 
603 }  // namespace crypto
604 }  // namespace node
605