%PDF- %PDF-
| Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/ |
| Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/src/crypto/crypto_aes.cc |
#include "crypto/crypto_aes.h"
#include "async_wrap-inl.h"
#include "base_object-inl.h"
#include "crypto/crypto_cipher.h"
#include "crypto/crypto_keys.h"
#include "crypto/crypto_util.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "threadpoolwork-inl.h"
#include "v8.h"
#include <openssl/bn.h>
#include <openssl/aes.h>
#include <vector>
namespace node {
using v8::FunctionCallbackInfo;
using v8::Just;
using v8::Local;
using v8::Maybe;
using v8::Nothing;
using v8::Object;
using v8::Uint32;
using v8::Value;
namespace crypto {
namespace {
// Implements general AES encryption and decryption for CBC
// The key_data must be a secret key.
// On success, this function sets out to a new ByteSource
// instance containing the results and returns WebCryptoCipherStatus::OK.
WebCryptoCipherStatus AES_Cipher(
Environment* env,
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
CHECK_NOT_NULL(key_data);
CHECK_EQ(key_data->GetKeyType(), kKeyTypeSecret);
const int mode = EVP_CIPHER_mode(params.cipher);
CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
EVP_CIPHER_CTX_init(ctx.get());
if (mode == EVP_CIPH_WRAP_MODE)
EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
if (!EVP_CipherInit_ex(
ctx.get(),
params.cipher,
nullptr,
nullptr,
nullptr,
encrypt)) {
// Cipher init failed
return WebCryptoCipherStatus::FAILED;
}
if (mode == EVP_CIPH_GCM_MODE && !EVP_CIPHER_CTX_ctrl(
ctx.get(),
EVP_CTRL_AEAD_SET_IVLEN,
params.iv.size(),
nullptr)) {
return WebCryptoCipherStatus::FAILED;
}
if (!EVP_CIPHER_CTX_set_key_length(
ctx.get(),
key_data->GetSymmetricKeySize()) ||
!EVP_CipherInit_ex(
ctx.get(),
nullptr,
nullptr,
reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
params.iv.data<unsigned char>(),
encrypt)) {
return WebCryptoCipherStatus::FAILED;
}
size_t tag_len = 0;
if (mode == EVP_CIPH_GCM_MODE) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt:
// If in decrypt mode, the auth tag must be set in the params.tag.
CHECK(params.tag);
if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
EVP_CTRL_AEAD_SET_TAG,
params.tag.size(),
const_cast<char*>(params.tag.data<char>()))) {
return WebCryptoCipherStatus::FAILED;
}
break;
case kWebCryptoCipherEncrypt:
// In decrypt mode, we grab the tag length here. We'll use it to
// ensure that that allocated buffer has enough room for both the
// final block and the auth tag. Unlike our other AES-GCM implementation
// in CipherBase, in WebCrypto, the auth tag is concatenated to the end
// of the generated ciphertext and returned in the same ArrayBuffer.
tag_len = params.length;
break;
default:
UNREACHABLE();
}
}
size_t total = 0;
int buf_len = in.size() + EVP_CIPHER_CTX_block_size(ctx.get()) + tag_len;
int out_len;
if (mode == EVP_CIPH_GCM_MODE &&
params.additional_data.size() &&
!EVP_CipherUpdate(
ctx.get(),
nullptr,
&out_len,
params.additional_data.data<unsigned char>(),
params.additional_data.size())) {
return WebCryptoCipherStatus::FAILED;
}
ByteSource::Builder buf(buf_len);
// In some outdated version of OpenSSL (e.g.
// ubi81_sharedlibs_openssl111fips_x64) may be used in sharedlib mode, the
// logic will be failed when input size is zero. The newly OpenSSL has fixed
// it up. But we still have to regard zero as special in Node.js code to
// prevent old OpenSSL failure.
//
// Refs: https://github.com/openssl/openssl/commit/420cb707b880e4fb649094241371701013eeb15f
// Refs: https://github.com/nodejs/node/pull/38913#issuecomment-866505244
if (in.size() == 0) {
out_len = 0;
} else if (!EVP_CipherUpdate(ctx.get(),
buf.data<unsigned char>(),
&out_len,
in.data<unsigned char>(),
in.size())) {
return WebCryptoCipherStatus::FAILED;
}
total += out_len;
CHECK_LE(out_len, buf_len);
out_len = EVP_CIPHER_CTX_block_size(ctx.get());
if (!EVP_CipherFinal_ex(
ctx.get(), buf.data<unsigned char>() + total, &out_len)) {
return WebCryptoCipherStatus::FAILED;
}
total += out_len;
// If using AES_GCM, grab the generated auth tag and append
// it to the end of the ciphertext.
if (cipher_mode == kWebCryptoCipherEncrypt && mode == EVP_CIPH_GCM_MODE) {
if (!EVP_CIPHER_CTX_ctrl(ctx.get(),
EVP_CTRL_AEAD_GET_TAG,
tag_len,
buf.data<unsigned char>() + total))
return WebCryptoCipherStatus::FAILED;
total += tag_len;
}
// It's possible that we haven't used the full allocated space. Size down.
*out = std::move(buf).release(total);
return WebCryptoCipherStatus::OK;
}
// The AES_CTR implementation here takes it's inspiration from the chromium
// implementation here:
// https://github.com/chromium/chromium/blob/7af6cfd/components/webcrypto/algorithms/aes_ctr.cc
template <typename T>
T CeilDiv(T a, T b) {
return a == 0 ? 0 : 1 + (a - 1) / b;
}
BignumPointer GetCounter(const AESCipherConfig& params) {
unsigned int remainder = (params.length % CHAR_BIT);
const unsigned char* data = params.iv.data<unsigned char>();
if (remainder == 0) {
unsigned int byte_length = params.length / CHAR_BIT;
return BignumPointer(BN_bin2bn(
data + params.iv.size() - byte_length,
byte_length,
nullptr));
}
unsigned int byte_length =
CeilDiv(params.length, static_cast<size_t>(CHAR_BIT));
std::vector<unsigned char> counter(
data + params.iv.size() - byte_length,
data + params.iv.size());
counter[0] &= ~(0xFF << remainder);
return BignumPointer(BN_bin2bn(counter.data(), counter.size(), nullptr));
}
std::vector<unsigned char> BlockWithZeroedCounter(
const AESCipherConfig& params) {
unsigned int length_bytes = params.length / CHAR_BIT;
unsigned int remainder = params.length % CHAR_BIT;
const unsigned char* data = params.iv.data<unsigned char>();
std::vector<unsigned char> new_counter_block(data, data + params.iv.size());
size_t index = new_counter_block.size() - length_bytes;
memset(&new_counter_block.front() + index, 0, length_bytes);
if (remainder)
new_counter_block[index - 1] &= 0xFF << remainder;
return new_counter_block;
}
WebCryptoCipherStatus AES_CTR_Cipher2(
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
unsigned const char* counter,
unsigned char* out) {
CipherCtxPointer ctx(EVP_CIPHER_CTX_new());
const bool encrypt = cipher_mode == kWebCryptoCipherEncrypt;
if (!EVP_CipherInit_ex(
ctx.get(),
params.cipher,
nullptr,
reinterpret_cast<const unsigned char*>(key_data->GetSymmetricKey()),
counter,
encrypt)) {
// Cipher init failed
return WebCryptoCipherStatus::FAILED;
}
int out_len = 0;
int final_len = 0;
if (!EVP_CipherUpdate(
ctx.get(),
out,
&out_len,
in.data<unsigned char>(),
in.size())) {
return WebCryptoCipherStatus::FAILED;
}
if (!EVP_CipherFinal_ex(ctx.get(), out + out_len, &final_len))
return WebCryptoCipherStatus::FAILED;
out_len += final_len;
if (static_cast<unsigned>(out_len) != in.size())
return WebCryptoCipherStatus::FAILED;
return WebCryptoCipherStatus::OK;
}
WebCryptoCipherStatus AES_CTR_Cipher(
Environment* env,
KeyObjectData* key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
BignumPointer num_counters(BN_new());
if (!BN_lshift(num_counters.get(), BN_value_one(), params.length))
return WebCryptoCipherStatus::FAILED;
BignumPointer current_counter = GetCounter(params);
BignumPointer num_output(BN_new());
if (!BN_set_word(num_output.get(), CeilDiv(in.size(), kAesBlockSize)))
return WebCryptoCipherStatus::FAILED;
// Just like in chromium's implementation, if the counter will
// be incremented more than there are counter values, we fail.
if (BN_cmp(num_output.get(), num_counters.get()) > 0)
return WebCryptoCipherStatus::FAILED;
BignumPointer remaining_until_reset(BN_new());
if (!BN_sub(remaining_until_reset.get(),
num_counters.get(),
current_counter.get())) {
return WebCryptoCipherStatus::FAILED;
}
// Output size is identical to the input size.
ByteSource::Builder buf(in.size());
// Also just like in chromium's implementation, if we can process
// the input without wrapping the counter, we'll do it as a single
// call here. If we can't, we'll fallback to the a two-step approach
if (BN_cmp(remaining_until_reset.get(), num_output.get()) >= 0) {
auto status = AES_CTR_Cipher2(key_data,
cipher_mode,
params,
in,
params.iv.data<unsigned char>(),
buf.data<unsigned char>());
if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
return status;
}
BN_ULONG blocks_part1 = BN_get_word(remaining_until_reset.get());
BN_ULONG input_size_part1 = blocks_part1 * kAesBlockSize;
// Encrypt the first part...
auto status =
AES_CTR_Cipher2(key_data,
cipher_mode,
params,
ByteSource::Foreign(in.data<char>(), input_size_part1),
params.iv.data<unsigned char>(),
buf.data<unsigned char>());
if (status != WebCryptoCipherStatus::OK)
return status;
// Wrap the counter around to zero
std::vector<unsigned char> new_counter_block = BlockWithZeroedCounter(params);
// Encrypt the second part...
status =
AES_CTR_Cipher2(key_data,
cipher_mode,
params,
ByteSource::Foreign(in.data<char>() + input_size_part1,
in.size() - input_size_part1),
new_counter_block.data(),
buf.data<unsigned char>() + input_size_part1);
if (status == WebCryptoCipherStatus::OK) *out = std::move(buf).release();
return status;
}
bool ValidateIV(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
ArrayBufferOrViewContents<char> iv(value);
if (UNLIKELY(!iv.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "iv is too big");
return false;
}
params->iv = (mode == kCryptoJobAsync)
? iv.ToCopy()
: iv.ToByteSource();
return true;
}
bool ValidateCounter(
Environment* env,
Local<Value> value,
AESCipherConfig* params) {
CHECK(value->IsUint32()); // Length
params->length = value.As<Uint32>()->Value();
if (params->iv.size() != 16 ||
params->length == 0 ||
params->length > 128) {
THROW_ERR_CRYPTO_INVALID_COUNTER(env);
return false;
}
return true;
}
bool ValidateAuthTag(
Environment* env,
CryptoJobMode mode,
WebCryptoCipherMode cipher_mode,
Local<Value> value,
AESCipherConfig* params) {
switch (cipher_mode) {
case kWebCryptoCipherDecrypt: {
if (!IsAnyBufferSource(value)) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
ArrayBufferOrViewContents<char> tag_contents(value);
if (UNLIKELY(!tag_contents.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "tagLength is too big");
return false;
}
params->tag = mode == kCryptoJobAsync
? tag_contents.ToCopy()
: tag_contents.ToByteSource();
break;
}
case kWebCryptoCipherEncrypt: {
if (!value->IsUint32()) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
params->length = value.As<Uint32>()->Value();
if (params->length > 128) {
THROW_ERR_CRYPTO_INVALID_TAG_LENGTH(env);
return false;
}
break;
}
default:
UNREACHABLE();
}
return true;
}
bool ValidateAdditionalData(
Environment* env,
CryptoJobMode mode,
Local<Value> value,
AESCipherConfig* params) {
// Additional Data
if (IsAnyBufferSource(value)) {
ArrayBufferOrViewContents<char> additional(value);
if (UNLIKELY(!additional.CheckSizeInt32())) {
THROW_ERR_OUT_OF_RANGE(env, "additionalData is too big");
return false;
}
params->additional_data = mode == kCryptoJobAsync
? additional.ToCopy()
: additional.ToByteSource();
}
return true;
}
void UseDefaultIV(AESCipherConfig* params) {
params->iv = ByteSource::Foreign(kDefaultWrapIV, strlen(kDefaultWrapIV));
}
} // namespace
AESCipherConfig::AESCipherConfig(AESCipherConfig&& other) noexcept
: mode(other.mode),
variant(other.variant),
cipher(other.cipher),
length(other.length),
iv(std::move(other.iv)),
additional_data(std::move(other.additional_data)),
tag(std::move(other.tag)) {}
AESCipherConfig& AESCipherConfig::operator=(AESCipherConfig&& other) noexcept {
if (&other == this) return *this;
this->~AESCipherConfig();
return *new (this) AESCipherConfig(std::move(other));
}
void AESCipherConfig::MemoryInfo(MemoryTracker* tracker) const {
// If mode is sync, then the data in each of these properties
// is not owned by the AESCipherConfig, so we ignore it.
if (mode == kCryptoJobAsync) {
tracker->TrackFieldWithSize("iv", iv.size());
tracker->TrackFieldWithSize("additional_data", additional_data.size());
tracker->TrackFieldWithSize("tag", tag.size());
}
}
Maybe<bool> AESCipherTraits::AdditionalConfig(
CryptoJobMode mode,
const FunctionCallbackInfo<Value>& args,
unsigned int offset,
WebCryptoCipherMode cipher_mode,
AESCipherConfig* params) {
Environment* env = Environment::GetCurrent(args);
params->mode = mode;
CHECK(args[offset]->IsUint32()); // Key Variant
params->variant =
static_cast<AESKeyVariant>(args[offset].As<Uint32>()->Value());
int cipher_nid;
switch (params->variant) {
case kKeyVariantAES_CTR_128:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_128_ctr;
break;
case kKeyVariantAES_CTR_192:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_192_ctr;
break;
case kKeyVariantAES_CTR_256:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateCounter(env, args[offset + 2], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_256_ctr;
break;
case kKeyVariantAES_CBC_128:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_128_cbc;
break;
case kKeyVariantAES_CBC_192:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_192_cbc;
break;
case kKeyVariantAES_CBC_256:
if (!ValidateIV(env, mode, args[offset + 1], params))
return Nothing<bool>();
cipher_nid = NID_aes_256_cbc;
break;
case kKeyVariantAES_KW_128:
UseDefaultIV(params);
cipher_nid = NID_id_aes128_wrap;
break;
case kKeyVariantAES_KW_192:
UseDefaultIV(params);
cipher_nid = NID_id_aes192_wrap;
break;
case kKeyVariantAES_KW_256:
UseDefaultIV(params);
cipher_nid = NID_id_aes256_wrap;
break;
case kKeyVariantAES_GCM_128:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_128_gcm;
break;
case kKeyVariantAES_GCM_192:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_192_gcm;
break;
case kKeyVariantAES_GCM_256:
if (!ValidateIV(env, mode, args[offset + 1], params) ||
!ValidateAuthTag(env, mode, cipher_mode, args[offset + 2], params) ||
!ValidateAdditionalData(env, mode, args[offset + 3], params)) {
return Nothing<bool>();
}
cipher_nid = NID_aes_256_gcm;
break;
default:
UNREACHABLE();
}
params->cipher = EVP_get_cipherbynid(cipher_nid);
if (params->cipher == nullptr) {
THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env);
return Nothing<bool>();
}
if (params->iv.size() <
static_cast<size_t>(EVP_CIPHER_iv_length(params->cipher))) {
THROW_ERR_CRYPTO_INVALID_IV(env);
return Nothing<bool>();
}
return Just(true);
}
WebCryptoCipherStatus AESCipherTraits::DoCipher(
Environment* env,
std::shared_ptr<KeyObjectData> key_data,
WebCryptoCipherMode cipher_mode,
const AESCipherConfig& params,
const ByteSource& in,
ByteSource* out) {
#define V(name, fn) \
case kKeyVariantAES_ ## name: \
return fn(env, key_data.get(), cipher_mode, params, in, out);
switch (params.variant) {
VARIANTS(V)
default:
UNREACHABLE();
}
#undef V
}
void AES::Initialize(Environment* env, Local<Object> target) {
AESCryptoJob::Initialize(env, target);
#define V(name, _) NODE_DEFINE_CONSTANT(target, kKeyVariantAES_ ## name);
VARIANTS(V)
#undef V
}
void AES::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
AESCryptoJob::RegisterExternalReferences(registry);
}
} // namespace crypto
} // namespace node