#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 ncrypto::Cipher; using ncrypto::CipherCtxPointer; using ncrypto::ClearErrorOnReturn; using ncrypto::Digest; using ncrypto::EVPKeyCtxPointer; using ncrypto::EVPKeyPointer; using ncrypto::MarkPopErrorOnReturn; using ncrypto::SSLCtxPointer; using ncrypto::SSLPointer; using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; using v8::BackingStoreInitializationMode; using v8::Context; using v8::DictionaryTemplate; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; using v8::Int32; using v8::Isolate; using v8::Local; using v8::LocalVector; using v8::MaybeLocal; using v8::Object; using v8::Uint32; using v8::Undefined; using v8::Value; namespace crypto { namespace { // Collects and returns information on the given cipher void GetCipherInfo(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); auto tmpl = env->cipherinfo_detail_template(); if (tmpl.IsEmpty()) { static constexpr std::string_view names[] = { "mode", "name", "nid", "keyLength", "blockSize", "ivLength", }; tmpl = DictionaryTemplate::New(env->isolate(), names); env->set_cipherinfo_detail_template(tmpl); } MaybeLocal values[] = { Undefined(env->isolate()), // mode Undefined(env->isolate()), // name Undefined(env->isolate()), // nid Undefined(env->isolate()), // keyLength Undefined(env->isolate()), // blockSize Undefined(env->isolate()), // ivLength }; CHECK(args[0]->IsString() || args[0]->IsInt32()); const auto cipher = ([&] { if (args[0]->IsString()) { Utf8Value name(env->isolate(), args[0]); return Cipher::FromName(*name); } else { int nid = args[0].As()->Value(); return Cipher::FromNid(nid); } })(); if (!cipher) return; size_t iv_length = cipher.getIvLength(); size_t key_length = cipher.getKeyLength(); // 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[1]->IsUint32() || args[2]->IsUint32()) { // 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. auto ctx = CipherCtxPointer::New(); if (!ctx.init(cipher, true)) { return; } if (args[1]->IsUint32()) { size_t check_len = args[1].As()->Value(); if (!ctx.setKeyLength(check_len)) { return; } key_length = check_len; } if (args[2]->IsUint32()) { size_t check_len = args[2].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. if (cipher.isCcmMode()) { if (check_len < 7 || check_len > 13) return; } else if (cipher.isGcmMode()) { // Nothing to do. } else if (cipher.isOcbMode()) { if (!ctx.setIvLength(check_len)) return; } else { if (check_len != iv_length) return; } iv_length = check_len; } } // Lowercase the name in place before we create the JS string from it. std::string name_str(cipher.getName()); name_str = ToLower(name_str); values[0] = ToV8Value(env->context(), cipher.getModeLabel(), env->isolate()); values[1] = ToV8Value(env->context(), name_str, env->isolate()); values[2] = Uint32::NewFromUnsigned(env->isolate(), cipher.getNid()); values[3] = Uint32::NewFromUnsigned(env->isolate(), key_length); // Stream ciphers do not have a meaningful block size if (!cipher.isStreamMode()) { values[4] = Uint32::NewFromUnsigned(env->isolate(), cipher.getBlockSize()); } // Ciphers that do not use an IV shouldn't report a length if (iv_length != 0) { values[5] = Uint32::NewFromUnsigned(env->isolate(), iv_length); } Local info; if (NewDictionaryInstanceNullProto(env->context(), tmpl, values) .ToLocal(&info)) { args.GetReturnValue().Set(info); } } } // namespace void CipherBase::GetSSLCiphers(const FunctionCallbackInfo& args) { ClearErrorOnReturn clear_error_on_return; Environment* env = Environment::GetCurrent(args); auto ctx = SSLCtxPointer::New(); if (!ctx) { return ThrowCryptoError( env, clear_error_on_return.peekError(), "SSL_CTX_new"); } auto ssl = SSLPointer::New(ctx); if (!ssl) { return ThrowCryptoError(env, clear_error_on_return.peekError(), "SSL_new"); } LocalVector arr(env->isolate()); ssl.getCiphers([&](const std::string_view name) { arr.push_back(OneByteString(env->isolate(), name.data(), name.length())); }); args.GetReturnValue().Set(Array::New(env->isolate(), arr.data(), arr.size())); } void CipherBase::GetCiphers(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); LocalVector ciphers(env->isolate()); bool errored = false; Cipher::ForEach([&](std::string_view name) { // If a prior iteration errored, do nothing further. We apparently // can't actually stop openssl from stopping its iteration here. // But why does it matter? Good question. if (errored) return; Local val; if (!ToV8Value(env->context(), name, env->isolate()).ToLocal(&val)) { errored = true; return; } ciphers.push_back(val); }); // If errored is true here, then we encountered a JavaScript error // while trying to create the V8 String from the std::string_view // in the iteration callback. That means we need to throw. if (!errored) { args.GetReturnValue().Set( Array::New(env->isolate(), ciphers.data(), ciphers.size())); } } 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, "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(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); CHECK_EQ(args.Length(), 5); CipherBase* cipher = new CipherBase(env, args.This(), args[0]->IsTrue() ? kCipher : kDecipher); const Utf8Value cipher_type(env->isolate(), args[1]); // 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[2]); if (key_buf.size() > INT_MAX) [[unlikely]] { return THROW_ERR_OUT_OF_RANGE(env, "key is too big"); } ArrayBufferOrViewContents iv_buf( !args[3]->IsNull() ? args[3] : Local()); if (!iv_buf.CheckSizeInt32()) [[unlikely]] { 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[4]->IsUint32()) { auth_tag_len = args[4].As()->Value(); } else { CHECK(args[4]->IsInt32() && args[4].As()->Value() == -1); auth_tag_len = kNoAuthTagLength; } cipher->InitIv(*cipher_type, key_buf, iv_buf, auth_tag_len); } void CipherBase::CommonInit(const char* cipher_type, const ncrypto::Cipher& cipher, const unsigned char* key, int key_len, const unsigned char* iv, int iv_len, unsigned int auth_tag_len) { MarkPopErrorOnReturn mark_pop_error_on_return; CHECK(!ctx_); ctx_ = CipherCtxPointer::New(); CHECK(ctx_); if (cipher.isWrapMode()) { ctx_.setAllowWrap(); } const bool encrypt = (kind_ == kCipher); if (!ctx_.init(cipher, encrypt)) { return ThrowCryptoError(env(), mark_pop_error_on_return.peekError(), "Failed to initialize cipher"); } if (cipher.isSupportedAuthenticatedMode()) { CHECK_GE(iv_len, 0); if (!InitAuthenticated(cipher_type, iv_len, auth_tag_len)) { return; } } if (!ctx_.setKeyLength(key_len)) { ctx_.reset(); return THROW_ERR_CRYPTO_INVALID_KEYLEN(env()); } if (!ctx_.init(Cipher(), encrypt, key, iv)) { return ThrowCryptoError(env(), mark_pop_error_on_return.peekError(), "Failed to initialize cipher"); } } 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; auto cipher = Cipher::FromName(cipher_type); if (!cipher) return THROW_ERR_CRYPTO_UNKNOWN_CIPHER(env()); const int expected_iv_len = cipher.getIvLength(); 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 (!cipher.isSupportedAuthenticatedMode() && has_iv && static_cast(iv_buf.size()) != expected_iv_len) { return THROW_ERR_CRYPTO_INVALID_IV(env()); } if (cipher.isChaCha20Poly1305()) { 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); } bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, unsigned int auth_tag_len) { CHECK(IsAuthenticatedMode()); MarkPopErrorOnReturn mark_pop_error_on_return; if (!ctx_.setIvLength(iv_len)) { THROW_ERR_CRYPTO_INVALID_IV(env()); return false; } if (ctx_.isCcmMode()) { // TODO(tniessen) Support CCM decryption in FIPS mode if (kind_ == kDecipher && ncrypto::isFipsEnabled()) { THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION( env(), "CCM encryption not supported in FIPS mode"); return false; } // 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; } if (auth_tag_len == kNoAuthTagLength) { // Both GCM and ChaCha20-Poly1305 have a default tag length of 16 bytes. // Other modes (CCM, OCB) require an explicit tag length. if (ctx_.isGcmMode()) { auth_tag_len = EVP_GCM_TLS_TAG_LEN; } else if (ctx_.isChaCha20Poly1305()) { auth_tag_len = EVP_CHACHAPOLY_TLS_TAG_LEN; } else { THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env(), "authTagLength required for %s", cipher_type); return false; } } else if ((ctx_.isGcmMode() && !Cipher::IsValidGCMTagLength(auth_tag_len)) || (!ctx_.isGcmMode() && !ctx_.setAeadTagLength(auth_tag_len))) { // GCM authentication tag lengths are restricted according to NIST 800-38d, // page 9. For other modes, we rely on OpenSSL to validate the length. 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; return true; } bool CipherBase::CheckCCMMessageLength(int message_len) { CHECK(ctx_); CHECK(ctx_.isCcmMode()); 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 ncrypto::Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode(); } void CipherBase::GetAuthTag(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This()); // Only callable after Final and if encrypting. if (cipher->ctx_ || cipher->kind_ != kCipher || cipher->auth_tag_state_ != kAuthTagComputed) { return; } CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength); Local ret; if (Buffer::Copy(env, cipher->auth_tag_, cipher->auth_tag_len_) .ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } void CipherBase::SetAuthTag(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This()); 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 (!auth_tag.CheckSizeInt32()) [[unlikely]] { return THROW_ERR_OUT_OF_RANGE(env, "buffer is too big"); } unsigned int tag_len = auth_tag.size(); // Older versions of Node.js did allow setting the auth tag length implicitly // for GCM mode (see DEP0182). We now require knowledge of the expected tag // length up front, so it must have been set during initialization. // The configured tag length must match the length of the given auth tag. CHECK_NE(cipher->auth_tag_len_, kNoAuthTagLength); if (cipher->auth_tag_len_ != tag_len) { return THROW_ERR_CRYPTO_INVALID_AUTH_TAG( env, "Invalid authentication tag length: %u", tag_len); } CHECK_LE(cipher->auth_tag_len_, ncrypto::Cipher::MAX_AUTH_TAG_LENGTH); if (!cipher->ctx_.setAeadTag({auth_tag.data(), cipher->auth_tag_len_})) { return args.GetReturnValue().Set(false); } cipher->auth_tag_state_ = kAuthTagSetByUser; args.GetReturnValue().Set(true); } bool CipherBase::SetAAD( const ArrayBufferOrViewContents& data, int plaintext_len) { if (!ctx_ || !IsAuthenticatedMode()) return false; MarkPopErrorOnReturn mark_pop_error_on_return; int outlen; // When in CCM mode, we need to set the authentication tag and the plaintext // length in advance. if (ctx_.isCcmMode()) { 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; } ncrypto::Buffer buffer{ .data = nullptr, .len = static_cast(plaintext_len), }; // Specify the plaintext length. if (!ctx_.update(buffer, nullptr, &outlen)) { return false; } } ncrypto::Buffer buffer{ .data = data.data(), .len = data.size(), }; return ctx_.update(buffer, nullptr, &outlen); } void CipherBase::SetAAD(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This()); 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 (!buf.CheckSizeInt32()) [[unlikely]] { 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; if (ctx_.isCcmMode() && !CheckCCMMessageLength(len)) { return kErrorMessageSize; } const int block_size = ctx_.getBlockSize(); CHECK_GT(block_size, 0); if (len + block_size > INT_MAX) return kErrorState; int buf_len = len + block_size; ncrypto::Buffer buffer = { .data = reinterpret_cast(data), .len = len, }; if (kind_ == kCipher && ctx_.isWrapMode() && !ctx_.update(buffer, nullptr, &buf_len)) { return kErrorState; } *out = ArrayBuffer::NewBackingStore( env()->isolate(), buf_len, BackingStoreInitializationMode::kUninitialized); buffer = { .data = reinterpret_cast(data), .len = len, }; bool r = ctx_.update( buffer, static_cast((*out)->Data()), &buf_len); CHECK_LE(static_cast(buf_len), (*out)->ByteLength()); if (buf_len == 0) { *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } else if (static_cast(buf_len) != (*out)->ByteLength()) { std::unique_ptr old_out = std::move(*out); *out = ArrayBuffer::NewBackingStore( env()->isolate(), buf_len, BackingStoreInitializationMode::kUninitialized); memcpy((*out)->Data(), old_out->Data(), 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 && ctx_.isCcmMode()) { 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) { MarkPopErrorOnReturn mark_pop_error_on_return; std::unique_ptr out; Environment* env = Environment::GetCurrent(args); if (size > INT_MAX) [[unlikely]] { 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, mark_pop_error_on_return.peekError(), "Trying to add data in unsupported state"); } return; } auto 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 ctx_.setPadding(auto_padding); } void CipherBase::SetAutoPadding(const FunctionCallbackInfo& args) { CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This()); 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; *out = ArrayBuffer::NewBackingStore( env()->isolate(), static_cast(ctx_.getBlockSize()), BackingStoreInitializationMode::kUninitialized); #if (OPENSSL_VERSION_NUMBER < 0x30000000L) // 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 (kind_ == kDecipher && ctx_.isChaCha20Poly1305() && auth_tag_state_ != kAuthTagSetByUser) { return false; } #endif // 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 && ctx_.isCcmMode()) { ok = !pending_auth_failed_; *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } else { int out_len = (*out)->ByteLength(); ok = ctx_.update( {}, static_cast((*out)->Data()), &out_len, true); CHECK_LE(static_cast(out_len), (*out)->ByteLength()); if (out_len == 0) { *out = ArrayBuffer::NewBackingStore(env()->isolate(), 0); } else if (static_cast(out_len) != (*out)->ByteLength()) { std::unique_ptr old_out = std::move(*out); *out = ArrayBuffer::NewBackingStore( env()->isolate(), out_len, BackingStoreInitializationMode::kUninitialized); memcpy((*out)->Data(), old_out->Data(), out_len); } if (ok && kind_ == kCipher && IsAuthenticatedMode()) { CHECK_NE(auth_tag_len_, kNoAuthTagLength); ok = ctx_.getAeadTag(auth_tag_len_, reinterpret_cast(auth_tag_)); if (ok) { auth_tag_state_ = kAuthTagComputed; } } } ctx_.reset(); return ok; } void CipherBase::Final(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); MarkPopErrorOnReturn mark_pop_error_on_return; CipherBase* cipher; ASSIGN_OR_RETURN_UNWRAP(&cipher, args.This()); 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, mark_pop_error_on_return.peekError(), msg); } auto 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 EVPKeyPointer& pkey, int padding, const Digest& digest, const ArrayBufferOrViewContents& oaep_label, const ArrayBufferOrViewContents& data, std::unique_ptr* out) { auto label = oaep_label.ToByteSource(); auto in = data.ToByteSource(); const ncrypto::Cipher::CipherParams params{ .padding = padding, .digest = digest, .label = label, }; auto buf = cipher(pkey, params, in); if (!buf) return false; if (buf.size() == 0) { *out = ArrayBuffer::NewBackingStore(env->isolate(), 0); } else { *out = ArrayBuffer::NewBackingStore( env->isolate(), buf.size(), BackingStoreInitializationMode::kUninitialized); memcpy((*out)->Data(), buf.get(), buf.size()); } return true; } template void PublicKeyCipher::Cipher(const FunctionCallbackInfo& args) { MarkPopErrorOnReturn mark_pop_error_on_return; Environment* env = Environment::GetCurrent(args); unsigned int offset = 0; auto data = KeyObjectData::GetPublicOrPrivateKeyFromJs(args, &offset); if (!data) return; const auto& pkey = data.GetAsymmetricKey(); if (!pkey) return; ArrayBufferOrViewContents buf(args[offset]); if (!buf.CheckSizeInt32()) [[unlikely]] { return THROW_ERR_OUT_OF_RANGE(env, "buffer is too long"); } uint32_t padding; if (!args[offset + 1]->Uint32Value(env->context()).To(&padding)) return; if (cipher == ncrypto::Cipher::decrypt && operation == PublicKeyCipher::kPrivate && padding == RSA_PKCS1_PADDING) { EVPKeyCtxPointer ctx = pkey.newCtx(); CHECK(ctx); if (!ctx.initForDecrypt()) { return ThrowCryptoError(env, ERR_get_error()); } // RSA implicit rejection here is not supported by BoringSSL. if (!ctx.setRsaImplicitRejection()) [[unlikely]] { return THROW_ERR_INVALID_ARG_VALUE( env, "RSA_PKCS1_PADDING is no longer supported for private decryption"); } } Digest digest; if (args[offset + 2]->IsString()) { Utf8Value oaep_str(env->isolate(), args[offset + 2]); digest = Digest::FromName(*oaep_str); if (!digest) return THROW_ERR_OSSL_EVP_INVALID_DIGEST(env); } ArrayBufferOrViewContents oaep_label( !args[offset + 3]->IsUndefined() ? args[offset + 3] : Local()); if (!oaep_label.CheckSizeInt32()) [[unlikely]] { 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