| #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<Value>& args) { |
| Environment* env = Environment::GetCurrent(args); |
| CHECK(args[0]->IsObject()); |
| Local<Object> info = args[0].As<Object>(); |
| |
| 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<Int32>()->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<Int32>()->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<Int32>()->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<Value>& 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<Local<Value>> 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<Value>& 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<EVP_CIPHER, |
| EVP_CIPHER_fetch, |
| EVP_CIPHER_free, |
| EVP_get_cipherbyname, |
| EVP_CIPHER_get0_name>, |
| #else |
| array_push_back<EVP_CIPHER>, |
| #endif |
| &ctx); |
| args.GetReturnValue().Set(ctx.ToJSArray()); |
| } |
| |
| CipherBase::CipherBase(Environment* env, |
| Local<Object> 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<Object> target) { |
| Isolate* isolate = env->isolate(); |
| Local<Context> context = env->context(); |
| |
| Local<FunctionTemplate> t = NewFunctionTemplate(isolate, New); |
| |
| t->InstanceTemplate()->SetInternalFieldCount( |
| CipherBase::kInternalFieldCount); |
| t->Inherit(BaseObject::GetConstructorTemplate(env)); |
| |
| 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<PublicKeyCipher::kPublic, |
| EVP_PKEY_encrypt_init, |
| EVP_PKEY_encrypt>); |
| SetMethod(context, |
| target, |
| "privateDecrypt", |
| PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate, |
| EVP_PKEY_decrypt_init, |
| EVP_PKEY_decrypt>); |
| SetMethod(context, |
| target, |
| "privateEncrypt", |
| PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate, |
| EVP_PKEY_sign_init, |
| EVP_PKEY_sign>); |
| SetMethod(context, |
| target, |
| "publicDecrypt", |
| PublicKeyCipher::Cipher<PublicKeyCipher::kPublic, |
| EVP_PKEY_verify_recover_init, |
| EVP_PKEY_verify_recover>); |
| |
| 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<PublicKeyCipher::kPublic, |
| EVP_PKEY_encrypt_init, |
| EVP_PKEY_encrypt>); |
| registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate, |
| EVP_PKEY_decrypt_init, |
| EVP_PKEY_decrypt>); |
| registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate, |
| EVP_PKEY_sign_init, |
| EVP_PKEY_sign>); |
| registry->Register(PublicKeyCipher::Cipher<PublicKeyCipher::kPublic, |
| EVP_PKEY_verify_recover_init, |
| EVP_PKEY_verify_recover>); |
| |
| registry->Register(GetCipherInfo); |
| } |
| |
| void CipherBase::New(const FunctionCallbackInfo<Value>& 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<unsigned char>& 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<Value>& 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<unsigned char> 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<Uint32>()->Value(); |
| } else { |
| CHECK(args[2]->IsInt32() && args[2].As<Int32>()->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<unsigned char>& 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<int> for the iv_buf.size() is safe because we've verified |
| // prior that the value is not larger than MAX_INT. |
| if (!is_authenticated_mode && |
| has_iv && |
| static_cast<int>(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<unsigned char>(), |
| key_buf.size(), |
| iv_buf.data(), |
| iv_buf.size(), |
| auth_tag_len); |
| } |
| |
| void CipherBase::InitIv(const FunctionCallbackInfo<Value>& 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<unsigned char> iv_buf( |
| !args[2]->IsNull() ? args[2] : Local<Value>()); |
| |
| 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<Uint32>()->Value(); |
| } else { |
| CHECK(args[3]->IsInt32() && args[3].As<Int32>()->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<Value>& 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<Value>())); |
| } |
| |
| void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& 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<char> 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<unsigned char*>(auth_tag_))) { |
| return false; |
| } |
| auth_tag_state_ = kAuthTagPassedToOpenSSL; |
| } |
| return true; |
| } |
| |
| bool CipherBase::SetAAD( |
| const ArrayBufferOrViewContents<unsigned char>& 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<Value>& 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<Int32>()->Value(); |
| ArrayBufferOrViewContents<unsigned char> 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<BackingStore>* 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()); |
| |
| int buf_len = len + EVP_CIPHER_CTX_block_size(ctx_.get()); |
| // 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<const unsigned char*>(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<unsigned char*>((*out)->Data()), |
| &buf_len, |
| reinterpret_cast<const unsigned char*>(data), |
| len); |
| |
| CHECK_LE(static_cast<size_t>(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<Value>& args) { |
| Decode<CipherBase>(args, [](CipherBase* cipher, |
| const FunctionCallbackInfo<Value>& args, |
| const char* data, size_t size) { |
| std::unique_ptr<BackingStore> 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<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(out)); |
| args.GetReturnValue().Set( |
| Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>())); |
| }); |
| } |
| |
| 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<Value>& 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<BackingStore>* 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<size_t>(EVP_CIPHER_CTX_block_size(ctx_.get()))); |
| } |
| |
| if (kind_ == kDecipher && IsSupportedAuthenticatedMode(ctx_.get())) |
| MaybePassAuthTagToOpenSSL(); |
| |
| // 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<unsigned char*>((*out)->Data()), |
| &out_len) == 1; |
| |
| CHECK_LE(static_cast<size_t>(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<unsigned char*>(auth_tag_))); |
| } |
| } |
| |
| ctx_.reset(); |
| |
| return ok; |
| } |
| |
| void CipherBase::Final(const FunctionCallbackInfo<Value>& 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<BackingStore> 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<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(out)); |
| args.GetReturnValue().Set( |
| Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>())); |
| } |
| |
| template <PublicKeyCipher::Operation operation, |
| PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init, |
| PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher> |
| bool PublicKeyCipher::Cipher( |
| Environment* env, |
| const ManagedEVPPKey& pkey, |
| int padding, |
| const EVP_MD* digest, |
| const ArrayBufferOrViewContents<unsigned char>& oaep_label, |
| const ArrayBufferOrViewContents<unsigned char>& data, |
| std::unique_ptr<BackingStore>* 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 (oaep_label.size() != 0) { |
| // OpenSSL takes ownership of the label, so we need to create a copy. |
| void* label = OPENSSL_memdup(oaep_label.data(), oaep_label.size()); |
| CHECK_NOT_NULL(label); |
| if (0 >= EVP_PKEY_CTX_set0_rsa_oaep_label(ctx.get(), |
| static_cast<unsigned char*>(label), |
| oaep_label.size())) { |
| OPENSSL_free(label); |
| 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<unsigned char*>((*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 <PublicKeyCipher::Operation operation, |
| PublicKeyCipher::EVP_PKEY_cipher_init_t EVP_PKEY_cipher_init, |
| PublicKeyCipher::EVP_PKEY_cipher_t EVP_PKEY_cipher> |
| void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& 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<unsigned char> 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<unsigned char> oaep_label( |
| !args[offset + 3]->IsUndefined() ? args[offset + 3] : Local<Value>()); |
| if (UNLIKELY(!oaep_label.CheckSizeInt32())) |
| return THROW_ERR_OUT_OF_RANGE(env, "oaep_label is too big"); |
| |
| std::unique_ptr<BackingStore> out; |
| if (!Cipher<operation, EVP_PKEY_cipher_init, EVP_PKEY_cipher>( |
| env, pkey, padding, digest, oaep_label, buf, &out)) { |
| return ThrowCryptoError(env, ERR_get_error()); |
| } |
| |
| Local<ArrayBuffer> ab = ArrayBuffer::New(env->isolate(), std::move(out)); |
| args.GetReturnValue().Set( |
| Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>())); |
| } |
| |
| } // namespace crypto |
| } // namespace node |