diff --git a/addon/mongocrypt.cc b/addon/mongocrypt.cc index 3de6d5b..421bb93 100644 --- a/addon/mongocrypt.cc +++ b/addon/mongocrypt.cc @@ -399,20 +399,18 @@ std::unique_ptr MongoCrypt::createJSCryptoHooks() { return true; }; - return std::make_unique(CryptoHooks { - "js", - aes_256_cbc_encrypt, - aes_256_cbc_decrypt, - random, - hmac_sha_512, - hmac_sha_256, - sha_256, - aes_256_ctr_encrypt, - aes_256_ctr_decrypt, - nullptr, - sign_rsa_sha256, - this - }); + return std::make_unique(CryptoHooks{"js", + aes_256_cbc_encrypt, + aes_256_cbc_decrypt, + random, + hmac_sha_512, + hmac_sha_256, + sha_256, + aes_256_ctr_encrypt, + aes_256_ctr_decrypt, + nullptr, + sign_rsa_sha256, + this}); } bool MongoCrypt::installCryptoHooks() { @@ -441,9 +439,7 @@ bool MongoCrypt::installCryptoHooks() { } if (hooks.aes_256_ecb_encrypt && - !mongocrypt_setopt_aes_256_ecb( - _mongo_crypt.get(), hooks.aes_256_ecb_encrypt, hooks.ctx) - ) { + !mongocrypt_setopt_aes_256_ecb(_mongo_crypt.get(), hooks.aes_256_ecb_encrypt, hooks.ctx)) { return false; } @@ -565,7 +561,8 @@ Value MongoCrypt::CryptSharedLibVersionInfo(const CallbackInfo& info) { } Value MongoCrypt::CryptoHooksProvider(const CallbackInfo& info) { - if (!_crypto_hooks) return Env().Null(); + if (!_crypto_hooks) + return Env().Null(); return String::New(Env(), _crypto_hooks->id); } diff --git a/addon/openssl-crypto.cc b/addon/openssl-crypto.cc index ac714de..91eb2e2 100644 --- a/addon/openssl-crypto.cc +++ b/addon/openssl-crypto.cc @@ -1,15 +1,18 @@ -// Adapted from https://github.com/mongodb/libmongocrypt/blob/18cb9e4e900c45c0b9b71fd34e159f8cb29fe1de/src/crypto/libcrypto.c -// and https://github.com/mongodb/libmongocrypt/blob/18cb9e4e900c45c0b9b71fd34e159f8cb29fe1de/kms-message/src/kms_crypto_libcrypto.c +// Adapted from +// https://github.com/mongodb/libmongocrypt/blob/18cb9e4e900c45c0b9b71fd34e159f8cb29fe1de/src/crypto/libcrypto.c +// and +// https://github.com/mongodb/libmongocrypt/blob/18cb9e4e900c45c0b9b71fd34e159f8cb29fe1de/kms-message/src/kms_crypto_libcrypto.c // This file provides native crypto hooks for OpenSSL 3 (the default since Node.js 18), // allowing us to skip expensive round-trips between JS and C++. -#include "mongocrypt.h" #include #include #include #include #include +#include "mongocrypt.h" + #ifdef _WIN32 #include #else @@ -19,26 +22,30 @@ #undef ASSERT #undef STRINGIFY #define STRINGIFY(x) #x -#define ASSERT(x) do { \ - if (!(x)) { \ - throw std::runtime_error("Assertion failed: " #x " (at " __FILE__ ":" STRINGIFY(__LINE__) ")"); \ - } \ - } while(0) +#define ASSERT(x) \ + do { \ + if (!(x)) { \ + throw std::runtime_error("Assertion failed: " #x " (at " __FILE__ \ + ":" STRINGIFY(__LINE__) ")"); \ + } \ + } while (0) // Helpers to easily access OpenSSL symbols in a type-safe way. #ifdef MONGO_CLIENT_ENCRYPTION_STATIC_OPENSSL #define S_Unchecked(x) (x) #define S(x) S_Unchecked(x) #else -#define S_Unchecked(x) \ - ([&]() { \ +#define S_Unchecked(x) \ + ([&]() { \ static void* const sym = node_mongocrypt::opensslcrypto::opensslsym(#x); \ - return reinterpret_cast(sym); \ + return reinterpret_cast(sym); \ })() -#define S(x) ([&]() { \ - static auto* sym = S_Unchecked(x); \ - if (!sym) throw new std::runtime_error("Unable to look up OpenSSL symbol: " #x ); \ - return sym; \ +#define S(x) \ + ([&]() { \ + static auto* sym = S_Unchecked(x); \ + if (!sym) \ + throw new std::runtime_error("Unable to look up OpenSSL symbol: " #x); \ + return sym; \ })() #endif @@ -61,31 +68,37 @@ namespace opensslcrypto { void* opensslsym(const char* symname); // Helper to use RAII together with C cleanup functions. -template +template struct CleanupImpl { T fn; bool active; - CleanupImpl(T&& fn): fn(std::move(fn)), active(true) {} - ~CleanupImpl() { if (active) fn(); } + CleanupImpl(T&& fn) : fn(std::move(fn)), active(true) {} + ~CleanupImpl() { + if (active) + fn(); + } CleanupImpl(const CleanupImpl&) = delete; CleanupImpl& operator=(const CleanupImpl&) = delete; - CleanupImpl(CleanupImpl&& other): fn(std::move(other.fn)), active(true) { + CleanupImpl(CleanupImpl&& other) : fn(std::move(other.fn)), active(true) { other.active = false; } }; -template +template CleanupImpl Cleanup(T&& fn) { - return CleanupImpl{ std::move(fn) }; + return CleanupImpl{std::move(fn)}; } static bool set_status_from_openssl(mongocrypt_status_t* status, const char* base_error) { std::string error_message = base_error; error_message += ": "; error_message += S(ERR_error_string)(S(ERR_get_error)(), nullptr); - mongocrypt_status_set( - status, MONGOCRYPT_STATUS_ERROR_CLIENT, 1, error_message.c_str(), error_message.length() + 1); + mongocrypt_status_set(status, + MONGOCRYPT_STATUS_ERROR_CLIENT, + 1, + error_message.c_str(), + error_message.length() + 1); return false; } @@ -96,15 +109,14 @@ static bool set_status_from_openssl(mongocrypt_status_t* status, const char* bas * enough room for the ciphertext. * @bytes_written is the number of bytes that were written to @out. * Returns false and sets @status on error. @status is required. */ -template -bool encrypt_with_cipher( - void* unused_ctx, - mongocrypt_binary_t* key, - mongocrypt_binary_t* iv, - mongocrypt_binary_t* in, - mongocrypt_binary_t* out, - uint32_t* bytes_written, - mongocrypt_status_t* status) { +template +bool encrypt_with_cipher(void* unused_ctx, + mongocrypt_binary_t* key, + mongocrypt_binary_t* iv, + mongocrypt_binary_t* in, + mongocrypt_binary_t* out, + uint32_t* bytes_written, + mongocrypt_status_t* status) { int intermediate_bytes_written = 0; const EVP_CIPHER* cipher = Cipher::Get(); @@ -120,12 +132,11 @@ bool encrypt_with_cipher( ASSERT(static_cast(S(EVP_CIPHER_get_key_length)(cipher)) == key->len); ASSERT(in->len <= INT_MAX); - if (!S(EVP_EncryptInit_ex)( - ctx, - cipher, - nullptr /* engine */, - static_cast(key->data), - nullptr == iv ? nullptr : static_cast(iv->data))) { + if (!S(EVP_EncryptInit_ex)(ctx, + cipher, + nullptr /* engine */, + static_cast(key->data), + nullptr == iv ? nullptr : static_cast(iv->data))) { return set_status_from_openssl(status, "error in EVP_EncryptInit_ex"); } @@ -133,20 +144,21 @@ bool encrypt_with_cipher( S(EVP_CIPHER_CTX_set_padding)(ctx, 0); *bytes_written = 0; - if (!S(EVP_EncryptUpdate)( - ctx, - static_cast(out->data), - &intermediate_bytes_written, - static_cast(in->data), - static_cast(in->len))) { + if (!S(EVP_EncryptUpdate)(ctx, + static_cast(out->data), + &intermediate_bytes_written, + static_cast(in->data), + static_cast(in->len))) { return set_status_from_openssl(status, "error in EVP_EncryptUpdate"); } - ASSERT(intermediate_bytes_written >= 0 && static_cast(intermediate_bytes_written) <= UINT32_MAX); + ASSERT(intermediate_bytes_written >= 0 && + static_cast(intermediate_bytes_written) <= UINT32_MAX); /* intermediate_bytes_written cannot be negative, so int -> uint32_t is OK */ *bytes_written = static_cast(intermediate_bytes_written); - if (!S(EVP_EncryptFinal_ex)(ctx, static_cast(out->data), &intermediate_bytes_written)) { + if (!S(EVP_EncryptFinal_ex)( + ctx, static_cast(out->data), &intermediate_bytes_written)) { return set_status_from_openssl(status, "error in EVP_EncryptFinal_ex"); } @@ -163,15 +175,14 @@ bool encrypt_with_cipher( * enough room for the plaintext. * @bytes_written is the number of bytes that were written to @out. * Returns false and sets @status on error. @status is required. */ -template -bool decrypt_with_cipher( - void* unused_ctx, - mongocrypt_binary_t* key, - mongocrypt_binary_t* iv, - mongocrypt_binary_t* in, - mongocrypt_binary_t* out, - uint32_t* bytes_written, - mongocrypt_status_t* status) { +template +bool decrypt_with_cipher(void* unused_ctx, + mongocrypt_binary_t* key, + mongocrypt_binary_t* iv, + mongocrypt_binary_t* in, + mongocrypt_binary_t* out, + uint32_t* bytes_written, + mongocrypt_status_t* status) { int intermediate_bytes_written = 0; const EVP_CIPHER* cipher = Cipher::Get(); @@ -188,12 +199,11 @@ bool decrypt_with_cipher( ASSERT(static_cast(S(EVP_CIPHER_get_key_length)(cipher)) == key->len); ASSERT(in->len <= INT_MAX); - if (!S(EVP_DecryptInit_ex)( - ctx, - cipher, - nullptr /* engine */, - static_cast(key->data), - static_cast(iv->data))) { + if (!S(EVP_DecryptInit_ex)(ctx, + cipher, + nullptr /* engine */, + static_cast(key->data), + static_cast(iv->data))) { return set_status_from_openssl(status, "error in EVP_DecryptInit_ex"); } @@ -202,20 +212,21 @@ bool decrypt_with_cipher( *bytes_written = 0; - if (!S(EVP_DecryptUpdate)( - ctx, - static_cast(out->data), - &intermediate_bytes_written, - static_cast(in->data), - static_cast(in->len))) { + if (!S(EVP_DecryptUpdate)(ctx, + static_cast(out->data), + &intermediate_bytes_written, + static_cast(in->data), + static_cast(in->len))) { return set_status_from_openssl(status, "error in EVP_DecryptUpdate"); } - ASSERT(intermediate_bytes_written >= 0 && static_cast(intermediate_bytes_written) <= UINT32_MAX); + ASSERT(intermediate_bytes_written >= 0 && + static_cast(intermediate_bytes_written) <= UINT32_MAX); /* intermediate_bytes_written cannot be negative, so int -> uint32_t is OK */ *bytes_written = static_cast(intermediate_bytes_written); - if (!S(EVP_DecryptFinal_ex)(ctx, static_cast(out->data), &intermediate_bytes_written)) { + if (!S(EVP_DecryptFinal_ex)( + ctx, static_cast(out->data), &intermediate_bytes_written)) { return set_status_from_openssl(status, "error in EVP_DecryptFinal_ex"); } @@ -230,37 +241,34 @@ bool decrypt_with_cipher( * @out is the output. @out must be allocated by the caller with * the exact length for the output. E.g. for HMAC 256, @out->len must be 32. * Returns false and sets @status on error. @status is required. */ -template -bool hmac_with_hash( - void* unused_ctx, - mongocrypt_binary_t *key, - mongocrypt_binary_t *in, - mongocrypt_binary_t *out, - mongocrypt_status_t *status) { +template +bool hmac_with_hash(void* unused_ctx, + mongocrypt_binary_t* key, + mongocrypt_binary_t* in, + mongocrypt_binary_t* out, + mongocrypt_status_t* status) { const EVP_MD* hash = Hash::Get(); ASSERT(hash); ASSERT(key); ASSERT(in); ASSERT(out); - if (!S(HMAC)( - hash, - key->data, - static_cast(key->len), - static_cast(in->data), - in->len, - static_cast(out->data), - nullptr /* unused out len */)) { + if (!S(HMAC)(hash, + key->data, + static_cast(key->len), + static_cast(in->data), + in->len, + static_cast(out->data), + nullptr /* unused out len */)) { return set_status_from_openssl(status, "error initializing HMAC"); } return true; } -bool random_fn( - void* unused_ctx, - mongocrypt_binary_t* out, - uint32_t count, - mongocrypt_status_t* status) { +bool random_fn(void* unused_ctx, + mongocrypt_binary_t* out, + uint32_t count, + mongocrypt_status_t* status) { ASSERT(out); ASSERT(count <= INT_MAX); @@ -276,12 +284,11 @@ bool random_fn( return true; } -template -bool compute_hash ( - void* unused_ctx, - mongocrypt_binary_t* in, - mongocrypt_binary_t* out, - mongocrypt_status_t* status) { +template +bool compute_hash(void* unused_ctx, + mongocrypt_binary_t* in, + mongocrypt_binary_t* out, + mongocrypt_status_t* status) { const EVP_MD* hash = Hash::Get(); ASSERT(hash); @@ -303,13 +310,12 @@ bool compute_hash ( return true; } -template -bool sign_rsa ( - void* unused_ctx, - mongocrypt_binary_t* key, - mongocrypt_binary_t* in, - mongocrypt_binary_t* out, - mongocrypt_status_t* status) { +template +bool sign_rsa(void* unused_ctx, + mongocrypt_binary_t* key, + mongocrypt_binary_t* in, + mongocrypt_binary_t* out, + mongocrypt_status_t* status) { const EVP_MD* hash = Hash::Get(); ASSERT(hash); @@ -325,11 +331,7 @@ bool sign_rsa ( auto cleanup_ctx = Cleanup([&]() { S(EVP_MD_CTX_free)(ctx); }); ASSERT(key->len <= LONG_MAX); const unsigned char* key_data = static_cast(key->data); - pkey = S(d2i_PrivateKey)( - EVP_PKEY_RSA, - nullptr, - &key_data, - static_cast(key->len)); + pkey = S(d2i_PrivateKey)(EVP_PKEY_RSA, nullptr, &key_data, static_cast(key->len)); auto cleanup_pkey = Cleanup([&]() { S(EVP_PKEY_free)(pkey); }); if (!pkey) { return set_status_from_openssl(status, "error in d2i_PrivateKey"); @@ -343,7 +345,7 @@ bool sign_rsa ( return set_status_from_openssl(status, "error in EVP_DigestSignUpdate"); } - if (!S(EVP_DigestSignFinal) (ctx, static_cast(out->data), &signature_out_len)) { + if (!S(EVP_DigestSignFinal)(ctx, static_cast(out->data), &signature_out_len)) { return set_status_from_openssl(status, "error in EVP_DigestSignFinal"); } @@ -388,32 +390,52 @@ void* opensslsym(const char* name) { std::unique_ptr createOpenSSLCryptoHooks() { auto version_num_fn = S_Unchecked(OpenSSL_version_num); - if (!version_num_fn) return {}; - unsigned long openssl_version = version_num_fn(); // 0xMNN00PP0L + if (!version_num_fn) + return {}; + unsigned long openssl_version = version_num_fn(); // 0xMNN00PP0L // Check that OpenSSL version is in [3.0.0, 4.0.0) - if (openssl_version < 0x30000000L || openssl_version >= 0x40000000L) return {}; - - struct AES256CBC { static const EVP_CIPHER* Get() { return S(EVP_aes_256_cbc)(); } }; - struct AES256ECB { static const EVP_CIPHER* Get() { return S(EVP_aes_256_ecb)(); } }; - struct AES256CTR { static const EVP_CIPHER* Get() { return S(EVP_aes_256_ctr)(); } }; - struct SHA512 { static const EVP_MD* Get() { return S(EVP_sha512)(); } }; - struct SHA256 { static const EVP_MD* Get() { return S(EVP_sha256)(); } }; - - return std::make_unique(CryptoHooks { - "native_openssl", - encrypt_with_cipher, - decrypt_with_cipher, - random_fn, - hmac_with_hash, - hmac_with_hash, - compute_hash, - encrypt_with_cipher, - decrypt_with_cipher, - encrypt_with_cipher, - sign_rsa, - nullptr - }); -} + if (openssl_version < 0x30000000L || openssl_version >= 0x40000000L) + return {}; + struct AES256CBC { + static const EVP_CIPHER* Get() { + return S(EVP_aes_256_cbc)(); + } + }; + struct AES256ECB { + static const EVP_CIPHER* Get() { + return S(EVP_aes_256_ecb)(); + } + }; + struct AES256CTR { + static const EVP_CIPHER* Get() { + return S(EVP_aes_256_ctr)(); + } + }; + struct SHA512 { + static const EVP_MD* Get() { + return S(EVP_sha512)(); + } + }; + struct SHA256 { + static const EVP_MD* Get() { + return S(EVP_sha256)(); + } + }; + + return std::make_unique(CryptoHooks{"native_openssl", + encrypt_with_cipher, + decrypt_with_cipher, + random_fn, + hmac_with_hash, + hmac_with_hash, + compute_hash, + encrypt_with_cipher, + decrypt_with_cipher, + encrypt_with_cipher, + sign_rsa, + nullptr}); } -} + +} // namespace opensslcrypto +} // namespace node_mongocrypt