#include "crypto/crypto_cipher.h" #include "base_object-inl.h" #include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "node_buffer.h" #include "node_internals.h" #include "node_process-inl.h" #include "v8.h" namespace node { using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::Context; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Int32; using v8::Isolate; using v8::Local; using v8::Object; using v8::Uint32; using v8::Value; namespace crypto { namespace { bool IsSupportedAuthenticatedMode(const EVP_CIPHER* cipher) { switch (EVP_CIPHER_mode(cipher)) { case EVP_CIPH_CCM_MODE: case EVP_CIPH_GCM_MODE: #ifndef OPENSSL_NO_OCB case EVP_CIPH_OCB_MODE: #endif return true; case EVP_CIPH_STREAM_CIPHER: return EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305; default: return false; } } bool IsSupportedAuthenticatedMode(const EVP_CIPHER_CTX* ctx) { const EVP_CIPHER* cipher = EVP_CIPHER_CTX_cipher(ctx); return IsSupportedAuthenticatedMode(cipher); } bool IsValidGCMTagLength(unsigned int tag_len) { return tag_len == 4 || tag_len == 8 || (tag_len >= 12 && tag_len <= 16); } // Collects and returns information on the given cipher void GetCipherInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsObject()); Local info = args[0].As(); CHECK(args[1]->IsString() || args[1]->IsInt32()); const EVP_CIPHER* cipher; if (args[1]->IsString()) { Utf8Value name(env->isolate(), args[1]); cipher = EVP_get_cipherbyname(*name); } else { int nid = args[1].As()->Value(); cipher = EVP_get_cipherbynid(nid); } if (cipher == nullptr) return; int mode = EVP_CIPHER_mode(cipher); int iv_length = EVP_CIPHER_iv_length(cipher); int key_length = EVP_CIPHER_key_length(cipher); int block_length = EVP_CIPHER_block_size(cipher); const char* mode_label = nullptr; switch (mode) { case EVP_CIPH_CBC_MODE: mode_label = "cbc"; break; case EVP_CIPH_CCM_MODE: mode_label = "ccm"; break; case EVP_CIPH_CFB_MODE: mode_label = "cfb"; break; case EVP_CIPH_CTR_MODE: mode_label = "ctr"; break; case EVP_CIPH_ECB_MODE: mode_label = "ecb"; break; case EVP_CIPH_GCM_MODE: mode_label = "gcm"; break; case EVP_CIPH_OCB_MODE: mode_label = "ocb"; break; case EVP_CIPH_OFB_MODE: mode_label = "ofb"; break; case EVP_CIPH_WRAP_MODE: mode_label = "wrap"; break; case EVP_CIPH_XTS_MODE: mode_label = "xts"; break; case EVP_CIPH_STREAM_CIPHER: mode_label = "stream"; break; } // If the testKeyLen and testIvLen arguments are specified, // then we will make an attempt to see if they are usable for // the cipher in question, returning undefined if they are not. // If they are, the info object will be returned with the values // given. if (args[2]->IsInt32() || args[3]->IsInt32()) { // Test and input IV or key length to determine if it's acceptable. // If it is, then the getCipherInfo will succeed with the given // values. CipherCtxPointer ctx(EVP_CIPHER_CTX_new()); if (!EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, 1)) return; if (args[2]->IsInt32()) { int check_len = args[2].As()->Value(); if (!EVP_CIPHER_CTX_set_key_length(ctx.get(), check_len)) return; key_length = check_len; } if (args[3]->IsInt32()) { int check_len = args[3].As()->Value(); // For CCM modes, the IV may be between 7 and 13 bytes. // For GCM and OCB modes, we'll check by attempting to // set the value. For everything else, just check that // check_len == iv_length. switch (mode) { case EVP_CIPH_CCM_MODE: if (check_len < 7 || check_len > 13) return; break; case EVP_CIPH_GCM_MODE: // Fall through case EVP_CIPH_OCB_MODE: if (!EVP_CIPHER_CTX_ctrl( ctx.get(), EVP_CTRL_AEAD_SET_IVLEN, check_len, nullptr)) { return; } break; default: if (check_len != iv_length) return; } iv_length = check_len; } } if (mode_label != nullptr && info->Set( env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "mode"), OneByteString(env->isolate(), mode_label)).IsNothing()) { return; } // OBJ_nid2sn(EVP_CIPHER_nid(cipher)) is used here instead of // EVP_CIPHER_name(cipher) for compatibility with BoringSSL. if (info->Set( env->context(), env->name_string(), OneByteString( env->isolate(), OBJ_nid2sn(EVP_CIPHER_nid(cipher)))).IsNothing()) { return; } if (info->Set( env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "nid"), Int32::New(env->isolate(), EVP_CIPHER_nid(cipher))).IsNothing()) { return; } // Stream ciphers do not have a meaningful block size if (mode != EVP_CIPH_STREAM_CIPHER && info->Set( env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "blockSize"), Int32::New(env->isolate(), block_length)).IsNothing()) { return; } // Ciphers that do not use an IV shouldn't report a length if (iv_length != 0 && info->Set( env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "ivLength"), Int32::New(env->isolate(), iv_length)).IsNothing()) { return; } if (info->Set( env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "keyLength"), Int32::New(env->isolate(), key_length)).IsNothing()) { return; } args.GetReturnValue().Set(info); } } // namespace void CipherBase::GetSSLCiphers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); SSLCtxPointer ctx(SSL_CTX_new(TLS_method())); if (!ctx) { return ThrowCryptoError(env, ERR_get_error(), "SSL_CTX_new"); } SSLPointer ssl(SSL_new(ctx.get())); if (!ssl) { return ThrowCryptoError(env, ERR_get_error(), "SSL_new"); } STACK_OF(SSL_CIPHER)* ciphers = SSL_get_ciphers(ssl.get()); // TLSv1.3 ciphers aren't listed by EVP. There are only 5, we could just // document them, but since there are only 5, easier to just add them manually // and not have to explain their absence in the API docs. They are lower-cased // because the docs say they will be. static const char* TLS13_CIPHERS[] = { "tls_aes_256_gcm_sha384", "tls_chacha20_poly1305_sha256", "tls_aes_128_gcm_sha256", "tls_aes_128_ccm_8_sha256", "tls_aes_128_ccm_sha256" }; const int n = sk_SSL_CIPHER_num(ciphers); std::vector> arr(n + arraysize(TLS13_CIPHERS)); for (int i = 0; i < n; ++i) { const SSL_CIPHER* cipher = sk_SSL_CIPHER_value(ciphers, i); arr[i] = OneByteString(env->isolate(), SSL_CIPHER_get_name(cipher)); } for (unsigned i = 0; i < arraysize(TLS13_CIPHERS); ++i) { const char* name = TLS13_CIPHERS[i]; arr[n + i] = OneByteString(env->isolate(), name); } args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size())); } void CipherBase::GetCiphers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); MarkPopErrorOnReturn mark_pop_error_on_return; CipherPushContext ctx(env); EVP_CIPHER_do_all_sorted( #if OPENSSL_VERSION_MAJOR >= 3 array_push_back, #else array_push_back, #endif &ctx); args.GetReturnValue().Set(ctx.ToJSArray()); } CipherBase::CipherBase(Environment* env, Local wrap, CipherKind kind) : BaseObject(env, wrap), ctx_(nullptr), kind_(kind), auth_tag_state_(kAuthTagUnknown), auth_tag_len_(kNoAuthTagLength), pending_auth_failed_(false) { MakeWeak(); } void CipherBase::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackFieldWithSize("context", ctx_ ? kSizeOf_EVP_CIPHER_CTX : 0); } void CipherBase::Initialize(Environment* env, Local target) { Isolate* isolate = env->isolate(); Local context = env->context(); Local t = NewFunctionTemplate(isolate, New); t->InstanceTemplate()->SetInternalFieldCount(CipherBase::kInternalFieldCount); SetProtoMethod(isolate, t, "init", Init); SetProtoMethod(isolate, t, "initiv", InitIv); SetProtoMethod(isolate, t, "update", Update); SetProtoMethod(isolate, t, "final", Final); SetProtoMethod(isolate, t, "setAutoPadding", SetAutoPadding); SetProtoMethodNoSideEffect(isolate, t, "getAuthTag", GetAuthTag); SetProtoMethod(isolate, t, "setAuthTag", SetAuthTag); SetProtoMethod(isolate, t, "setAAD", SetAAD); SetConstructorFunction(context, target, "CipherBase", t); SetMethodNoSideEffect(context, target, "getSSLCiphers", GetSSLCiphers); SetMethodNoSideEffect(context, target, "getCiphers", GetCiphers); SetMethod(context, target, "publicEncrypt", PublicKeyCipher::Cipher); SetMethod(context, target, "privateDecrypt", PublicKeyCipher::Cipher); SetMethod(context, target, "privateEncrypt", PublicKeyCipher::Cipher); SetMethod(context, target, "publicDecrypt", PublicKeyCipher::Cipher); SetMethodNoSideEffect(context, target, "getCipherInfo", GetCipherInfo); NODE_DEFINE_CONSTANT(target, kWebCryptoCipherEncrypt); NODE_DEFINE_CONSTANT(target, kWebCryptoCipherDecrypt); } void CipherBase::RegisterExternalReferences( ExternalReferenceRegistry* registry) { registry->Register(New); registry->Register(Init); registry->Register(InitIv); registry->Register(Update); registry->Register(Final); registry->Register(SetAutoPadding); registry->Register(GetAuthTag); registry->Register(SetAuthTag); registry->Register(SetAAD); registry->Register(GetSSLCiphers); registry->Register(GetCiphers); registry->Register(PublicKeyCipher::Cipher); registry->Register(PublicKeyCipher::Cipher); registry->Register(PublicKeyCipher::Cipher); registry->Register(PublicKeyCipher::Cipher); registry->Register(GetCipherInfo); } void CipherBase::New(const FunctionCallbackInfo& args) { CHECK(args.IsConstructCall()); Environment* env = Environment::GetCurrent(args); new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher); } void CipherBase::CommonInit(const char* cipher_type, const EVP_CIPHER* cipher, const unsigned char* key, int key_len, const unsigned char* iv, int iv_len, unsigned int auth_tag_len) { CHECK(!ctx_); ctx_.reset(EVP_CIPHER_CTX_new()); const int mode = EVP_CIPHER_mode(cipher); if (mode == EVP_CIPH_WRAP_MODE) EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); const bool encrypt = (kind_ == kCipher); if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt)) { return ThrowCryptoError(env(), ERR_get_error(), "Failed to initialize cipher"); } if (IsSupportedAuthenticatedMode(cipher)) { CHECK_GE(iv_len, 0); if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) return; } if (!EVP_CIPHER_CTX_set_key_length(ctx_.get(), key_len)) { ctx_.reset(); return THROW_ERR_CRYPTO_INVALID_KEYLEN(env()); } if (1 != EVP_CipherInit_ex(ctx_.get(), nullptr, nullptr, key, iv, encrypt)) { return ThrowCryptoError(env(), ERR_get_error(), "Failed to initialize cipher"); } } void CipherBase::Init(const char* cipher_type, const ArrayBufferOrViewContents& key_buf, unsigned int auth_tag_len) { HandleScope scope(env()->isolate()); MarkPopErrorOnReturn mark_pop_error_on_return; #if OPENSSL_VERSION_MAJOR >= 3 if (EVP_default_properties_is_fips_enabled(nullptr)) { #else if (FIPS_mode()) { #endif return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(), "crypto.createCipher() is not supported in FIPS mode."); } const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type); if (cipher == nullptr) return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); unsigned char key[EVP_MAX_KEY_LENGTH]; unsigned char iv[EVP_MAX_IV_LENGTH]; int key_len = EVP_BytesToKey(cipher, EVP_md5(), nullptr, key_buf.data(), key_buf.size(), 1, key, iv); CHECK_NE(key_len, 0); const int mode = EVP_CIPHER_mode(cipher); if (kind_ == kCipher && (mode == EVP_CIPH_CTR_MODE || mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE)) { // Ignore the return value (i.e. possible exception) because we are // not calling back into JS anyway. ProcessEmitWarning(env(), "Use Cipheriv for counter mode of %s", cipher_type); } CommonInit(cipher_type, cipher, key, key_len, iv, EVP_CIPHER_iv_length(cipher), auth_tag_len); } void CipherBase::Init(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); Environment* env = Environment::GetCurrent(args); CHECK_GE(args.Length(), 3); const Utf8Value cipher_type(args.GetIsolate(), args[0]); ArrayBufferOrViewContents key_buf(args[1]); if (!key_buf.CheckSizeInt32()) return THROW_ERR_OUT_OF_RANGE(env, "password is too large"); // Don't assign to cipher->auth_tag_len_ directly; the value might not // represent a valid length at this point. unsigned int auth_tag_len; if (args[2]->IsUint32()) { auth_tag_len = args[2].As()->Value(); } else { CHECK(args[2]->IsInt32() && args[2].As()->Value() == -1); auth_tag_len = kNoAuthTagLength; } cipher->Init(*cipher_type, key_buf, auth_tag_len); } void CipherBase::InitIv(const char* cipher_type, const ByteSource& key_buf, const ArrayBufferOrViewContents& iv_buf, unsigned int auth_tag_len) { HandleScope scope(env()->isolate()); MarkPopErrorOnReturn mark_pop_error_on_return; const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type); if (cipher == nullptr) return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); const int expected_iv_len = EVP_CIPHER_iv_length(cipher); const bool is_authenticated_mode = IsSupportedAuthenticatedMode(cipher); const bool has_iv = iv_buf.size() > 0; // Throw if no IV was passed and the cipher requires an IV if (!has_iv && expected_iv_len != 0) return THROW_ERR_CRYPTO_INVALID_IV(env()); // Throw if an IV was passed which does not match the cipher's fixed IV length // static_cast for the iv_buf.size() is safe because we've verified // prior that the value is not larger than INT_MAX. if (!is_authenticated_mode && has_iv && static_cast(iv_buf.size()) != expected_iv_len) { return THROW_ERR_CRYPTO_INVALID_IV(env()); } if (EVP_CIPHER_nid(cipher) == NID_chacha20_poly1305) { CHECK(has_iv); // Check for invalid IV lengths, since OpenSSL does not under some // conditions: // https://www.openssl.org/news/secadv/20190306.txt. if (iv_buf.size() > 12) return THROW_ERR_CRYPTO_INVALID_IV(env()); } CommonInit( cipher_type, cipher, key_buf.data(), key_buf.size(), iv_buf.data(), iv_buf.size(), auth_tag_len); } void CipherBase::InitIv(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); Environment* env = cipher->env(); CHECK_GE(args.Length(), 4); const Utf8Value cipher_type(env->isolate(), args[0]); // The argument can either be a KeyObjectHandle or a byte source // (e.g. ArrayBuffer, TypedArray, etc). Whichever it is, grab the // raw bytes and proceed... const ByteSource key_buf = ByteSource::FromSecretKeyBytes(env, args[1]); if (UNLIKELY(key_buf.size() > INT_MAX)) return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); ArrayBufferOrViewContents iv_buf( !args[2]->IsNull() ? args[2] : Local()); if (UNLIKELY(!iv_buf.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "iv is too big"); // Don't assign to cipher->auth_tag_len_ directly; the value might not // represent a valid length at this point. unsigned int auth_tag_len; if (args[3]->IsUint32()) { auth_tag_len = args[3].As()->Value(); } else { CHECK(args[3]->IsInt32() && args[3].As()->Value() == -1); auth_tag_len = kNoAuthTagLength; } cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len); } bool CipherBase::InitAuthenticated( const char* cipher_type, int iv_len, unsigned int auth_tag_len) { CHECK(IsAuthenticatedMode()); MarkPopErrorOnReturn mark_pop_error_on_return; if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, iv_len, nullptr)) { THROW_ERR_CRYPTO_INVALID_IV(env()); return false; } const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); if (mode == EVP_CIPH_GCM_MODE) { if (auth_tag_len != kNoAuthTagLength) { if (!IsValidGCMTagLength(auth_tag_len)) { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env(), "Invalid authentication tag length: %u", auth_tag_len); return false; } // Remember the given authentication tag length for later. auth_tag_len_ = auth_tag_len; } } else { if (auth_tag_len == kNoAuthTagLength) { // We treat ChaCha20-Poly1305 specially. Like GCM, the authentication tag // length defaults to 16 bytes when encrypting. Unlike GCM, the // authentication tag length also defaults to 16 bytes when decrypting, // whereas GCM would accept any valid authentication tag length. if (EVP_CIPHER_CTX_nid(ctx_.get()) == NID_chacha20_poly1305) { auth_tag_len = 16; } else { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env(), "authTagLength required for %s", cipher_type); return false; } } // TODO(tniessen) Support CCM decryption in FIPS mode #if OPENSSL_VERSION_MAJOR >= 3 if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && EVP_default_properties_is_fips_enabled(nullptr)) { #else if (mode == EVP_CIPH_CCM_MODE && kind_ == kDecipher && FIPS_mode()) { #endif THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(env(), "CCM encryption not supported in FIPS mode"); return false; } // Tell OpenSSL about the desired length. if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len, nullptr)) { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env(), "Invalid authentication tag length: %u", auth_tag_len); return false; } // Remember the given authentication tag length for later. auth_tag_len_ = auth_tag_len; if (mode == EVP_CIPH_CCM_MODE) { // Restrict the message length to min(INT_MAX, 2^(8*(15-iv_len))-1) bytes. CHECK(iv_len >= 7 && iv_len <= 13); max_message_size_ = INT_MAX; if (iv_len == 12) max_message_size_ = 16777215; if (iv_len == 13) max_message_size_ = 65535; } } return true; } bool CipherBase::CheckCCMMessageLength(int message_len) { CHECK(ctx_); CHECK(EVP_CIPHER_CTX_mode(ctx_.get()) == EVP_CIPH_CCM_MODE); if (message_len > max_message_size_) { THROW_ERR_CRYPTO_INVALID_MESSAGELEN(env()); return false; } return true; } bool CipherBase::IsAuthenticatedMode() const { // Check if this cipher operates in an AEAD mode that we support. CHECK(ctx_); return IsSupportedAuthenticatedMode(ctx_.get()); } void CipherBase::GetAuthTag(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); // Only callable after Final and if encrypting. if (cipher->ctx_ || cipher->kind_ != kCipher || cipher->auth_tag_len_ == kNoAuthTagLength) { return; } args.GetReturnValue().Set( Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_) .FromMaybe(Local())); } void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); Environment* env = Environment::GetCurrent(args); if (!cipher->ctx_ || !cipher->IsAuthenticatedMode() || cipher->kind_ != kDecipher || cipher->auth_tag_state_ != kAuthTagUnknown) { return args.GetReturnValue().Set(false); } ArrayBufferOrViewContents auth_tag(args[0]); if (UNLIKELY(!auth_tag.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); unsigned int tag_len = auth_tag.size(); const int mode = EVP_CIPHER_CTX_mode(cipher->ctx_.get()); bool is_valid; if (mode == EVP_CIPH_GCM_MODE) { // Restrict GCM tag lengths according to NIST 800-38d, page 9. is_valid = (cipher->auth_tag_len_ == kNoAuthTagLength || cipher->auth_tag_len_ == tag_len) && IsValidGCMTagLength(tag_len); } else { // At this point, the tag length is already known and must match the // length of the given authentication tag. CHECK(IsSupportedAuthenticatedMode(cipher->ctx_.get())); CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength); is_valid = cipher->auth_tag_len_ == tag_len; } if (!is_valid) { return THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env, "Invalid authentication tag length: %u", tag_len); } cipher->auth_tag_len_ = tag_len; cipher->auth_tag_state_ = kAuthTagKnown; CHECK_LE(cipher->auth_tag_len_, sizeof(cipher->auth_tag_)); memset(cipher->auth_tag_, 0, sizeof(cipher->auth_tag_)); auth_tag.CopyTo(cipher->auth_tag_, cipher->auth_tag_len_); args.GetReturnValue().Set(true); } bool CipherBase::MaybePassAuthTagToOpenSSL() { if (auth_tag_state_ == kAuthTagKnown) { if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_TAG, auth_tag_len_, reinterpret_cast(auth_tag_))) { return false; } auth_tag_state_ = kAuthTagPassedToOpenSSL; } return true; } bool CipherBase::SetAAD( const ArrayBufferOrViewContents& data, int plaintext_len) { if (!ctx_ || !IsAuthenticatedMode()) return false; MarkPopErrorOnReturn mark_pop_error_on_return; int outlen; const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); // When in CCM mode, we need to set the authentication tag and the plaintext // length in advance. if (mode == EVP_CIPH_CCM_MODE) { if (plaintext_len < 0) { THROW_ERR_MISSING_ARGS(env(), "options.plaintextLength required for CCM mode with AAD"); return false; } if (!CheckCCMMessageLength(plaintext_len)) return false; if (kind_ == kDecipher) { if (!MaybePassAuthTagToOpenSSL()) return false; } // Specify the plaintext length. if (!EVP_CipherUpdate(ctx_.get(), nullptr, &outlen, nullptr, plaintext_len)) return false; } return 1 == EVP_CipherUpdate(ctx_.get(), nullptr, &outlen, data.data(), data.size()); } void CipherBase::SetAAD(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); Environment* env = Environment::GetCurrent(args); CHECK_EQ(args.Length(), 2); CHECK(args[1]->IsInt32()); int plaintext_len = args[1].As()->Value(); ArrayBufferOrViewContents buf(args[0]); if (UNLIKELY(!buf.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); args.GetReturnValue().Set(cipher->SetAAD(buf, plaintext_len)); } CipherBase::UpdateResult CipherBase::Update( const char* data, size_t len, std::unique_ptr* out) { if (!ctx_ || len > INT_MAX) return kErrorState; MarkPopErrorOnReturn mark_pop_error_on_return; const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); if (mode == EVP_CIPH_CCM_MODE && !CheckCCMMessageLength(len)) return kErrorMessageSize; // Pass the authentication tag to OpenSSL if possible. This will only happen // once, usually on the first update. if (kind_ == kDecipher && IsAuthenticatedMode()) CHECK(MaybePassAuthTagToOpenSSL()); const int block_size = EVP_CIPHER_CTX_block_size(ctx_.get()); CHECK_GT(block_size, 0); if (len + block_size > INT_MAX) return kErrorState; int buf_len = len + block_size; // For key wrapping algorithms, get output size by calling // EVP_CipherUpdate() with null output. if (kind_ == kCipher && mode == EVP_CIPH_WRAP_MODE && EVP_CipherUpdate(ctx_.get(), nullptr, &buf_len, reinterpret_cast(data), len) != 1) { return kErrorState; } { NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data()); *out = ArrayBuffer::NewBackingStore(env()->isolate(), buf_len); } int r = EVP_CipherUpdate(ctx_.get(), static_cast((*out)->Data()), &buf_len, reinterpret_cast(data), len); CHECK_LE(static_cast(buf_len), (*out)->ByteLength()); if (buf_len == 0) *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); else *out = BackingStore::Reallocate(env()->isolate(), std::move(*out), buf_len); // When in CCM mode, EVP_CipherUpdate will fail if the authentication tag is // invalid. In that case, remember the error and throw in final(). if (!r && kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) { pending_auth_failed_ = true; return kSuccess; } return r == 1 ? kSuccess : kErrorState; } void CipherBase::Update(const FunctionCallbackInfo& args) { Decode(args, [](CipherBase* cipher, const FunctionCallbackInfo& args, const char* data, size_t size) { std::unique_ptr out; Environment* env = Environment::GetCurrent(args); if (UNLIKELY(size > INT_MAX)) return THROW_ERR_OUT_OF_RANGE(env, "data is too long"); UpdateResult r = cipher->Update(data, size, &out); if (r != kSuccess) { if (r == kErrorState) { ThrowCryptoError(env, ERR_get_error(), "Trying to add data in unsupported state"); } return; } Local ab = ArrayBuffer::New(env->isolate(), std::move(out)); args.GetReturnValue().Set( Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local())); }); } bool CipherBase::SetAutoPadding(bool auto_padding) { if (!ctx_) return false; MarkPopErrorOnReturn mark_pop_error_on_return; return EVP_CIPHER_CTX_set_padding(ctx_.get(), auto_padding); } void CipherBase::SetAutoPadding(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); bool b = cipher->SetAutoPadding(args.Length() < 1 || args[0]->IsTrue()); args.GetReturnValue().Set(b); // Possibly report invalid state failure } bool CipherBase::Final(std::unique_ptr* out) { if (!ctx_) return false; const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); { NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data()); *out = ArrayBuffer::NewBackingStore(env()->isolate(), static_cast(EVP_CIPHER_CTX_block_size(ctx_.get()))); } if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) MaybePassAuthTagToOpenSSL(); // OpenSSL v1.x doesn't verify the presence of the auth tag so do // it ourselves, see https://github.com/nodejs/node/issues/45874. if (OPENSSL_VERSION_NUMBER < 0x30000000L && kind_ == kDecipher && NID_chacha20_poly1305 == EVP_CIPHER_CTX_nid(ctx_.get()) && auth_tag_state_ != kAuthTagPassedToOpenSSL) { return false; } // In CCM mode, final() only checks whether authentication failed in update(). // EVP_CipherFinal_ex must not be called and will fail. bool ok; if (kind_ == kDecipher && mode == EVP_CIPH_CCM_MODE) { ok = !pending_auth_failed_; *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } else { int out_len = (*out)->ByteLength(); ok = EVP_CipherFinal_ex(ctx_.get(), static_cast((*out)->Data()), &out_len) == 1; CHECK_LE(static_cast(out_len), (*out)->ByteLength()); if (out_len > 0) { *out = BackingStore::Reallocate(env()->isolate(), std::move(*out), out_len); } else { *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } if (ok && kind_ == kCipher && IsAuthenticatedMode()) { // In GCM mode, the authentication tag length can be specified in advance, // but defaults to 16 bytes when encrypting. In CCM and OCB mode, it must // always be given by the user. if (auth_tag_len_ == kNoAuthTagLength) { CHECK(mode == EVP_CIPH_GCM_MODE); auth_tag_len_ = sizeof(auth_tag_); } ok = (1 == EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_GET_TAG, auth_tag_len_, reinterpret_cast(auth_tag_))); } } ctx_.reset(); return ok; } void CipherBase::Final(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.Holder()); if (cipher->ctx_ == nullptr) return THROW_ERR_CRYPTO_INVALID_STATE(env); std::unique_ptr out; // Check IsAuthenticatedMode() first, Final() destroys the EVP_CIPHER_CTX. const bool is_auth_mode = cipher->IsAuthenticatedMode(); bool r = cipher->Final(&out); if (!r) { const char* msg = is_auth_mode ? "Unsupported state or unable to authenticate data" : "Unsupported state"; return ThrowCryptoError(env, ERR_get_error(), msg); } Local ab = ArrayBuffer::New(env->isolate(), std::move(out)); args.GetReturnValue().Set( Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local())); } template bool PublicKeyCipher::Cipher( Environment* env, const ManagedEVPPKey& pkey, int padding, const EVP_MD* digest, const ArrayBufferOrViewContents& oaep_label, const ArrayBufferOrViewContents& data, std::unique_ptr* out) { EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); if (!ctx) return false; if (EVP_PKEY_cipher_init(ctx.get()) <= 0) return false; if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) <= 0) return false; if (digest != nullptr) { if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx.get(), digest) <= 0) return false; } if (!SetRsaOaepLabel(ctx, oaep_label.ToByteSource())) return false; size_t out_len = 0; if (EVP_PKEY_cipher( ctx.get(), nullptr, &out_len, data.data(), data.size()) <= 0) { return false; } { NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); *out = ArrayBuffer::NewBackingStore(env->isolate(), out_len); } if (EVP_PKEY_cipher( ctx.get(), static_cast((*out)->Data()), &out_len, data.data(), data.size()) <= 0) { return false; } CHECK_LE(out_len, (*out)->ByteLength()); if (out_len > 0) *out = BackingStore::Reallocate(env->isolate(), std::move(*out), out_len); else *out = ArrayBuffer::NewBackingStore(env->isolate(), 0); return true; } template void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; Environment* env = Environment::GetCurrent(args); unsigned int offset = 0; ManagedEVPPKey pkey = ManagedEVPPKey::GetPublicOrPrivateKeyFromJs(args, &offset); if (!pkey) return; ArrayBufferOrViewContents buf(args[offset]); if (UNLIKELY(!buf.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "buffer is too long"); uint32_t padding; if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return; const EVP_MD* digest = nullptr; if (args[offset + 2]->IsString()) { const Utf8Value oaep_str(env->isolate(), args[offset + 2]); digest = EVP_get_digestbyname(*oaep_str); if (digest == nullptr) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env); } ArrayBufferOrViewContents oaep_label( !args[offset + 3]->IsUndefined() ? args[offset + 3] : Local()); if (UNLIKELY(!oaep_label.CheckSizeInt32())) return THROW_ERR_OUT_OF_RANGE(env, "oaepLabel is too big"); std::unique_ptr out; if (!Cipher( env, pkey, padding, digest, oaep_label, buf, &out)) { return ThrowCryptoError(env, ERR_get_error()); } Local ab = ArrayBuffer::New(env->isolate(), std::move(out)); args.GetReturnValue().Set( Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local())); } } // namespace crypto } // namespace node