diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index fb7446578f57..cda04438fdc4 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -4486,26 +4486,40 @@ const Cipher Cipher::FromCtx(const CipherCtxPointer& ctx) { } const Cipher Cipher::EMPTY = Cipher(); -const Cipher Cipher::AES_128_CBC = Cipher::FromNid(NID_aes_128_cbc); -const Cipher Cipher::AES_192_CBC = Cipher::FromNid(NID_aes_192_cbc); -const Cipher Cipher::AES_256_CBC = Cipher::FromNid(NID_aes_256_cbc); -const Cipher Cipher::AES_128_CTR = Cipher::FromNid(NID_aes_128_ctr); -const Cipher Cipher::AES_192_CTR = Cipher::FromNid(NID_aes_192_ctr); -const Cipher Cipher::AES_256_CTR = Cipher::FromNid(NID_aes_256_ctr); -const Cipher Cipher::AES_128_GCM = Cipher::FromNid(NID_aes_128_gcm); -const Cipher Cipher::AES_192_GCM = Cipher::FromNid(NID_aes_192_gcm); -const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm); -const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap); -const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap); -const Cipher Cipher::AES_256_KW = Cipher::FromNid(NID_id_aes256_wrap); + +// The well-known ciphers are resolved lazily rather than in static +// initializers: EVP_get_cipherbynid() triggers OPENSSL_init_crypto(), and +// doing that while the executable is still being loaded adds the full cost +// of OpenSSL initialization to every process startup, even when crypto is +// never used. +#define NCRYPTO_LAZY_CIPHER(name, nid) \ + const Cipher& Cipher::name() { \ + static const Cipher cipher = Cipher::FromNid(nid); \ + return cipher; \ + } + +NCRYPTO_LAZY_CIPHER(AES_128_CBC, NID_aes_128_cbc) +NCRYPTO_LAZY_CIPHER(AES_192_CBC, NID_aes_192_cbc) +NCRYPTO_LAZY_CIPHER(AES_256_CBC, NID_aes_256_cbc) +NCRYPTO_LAZY_CIPHER(AES_128_CTR, NID_aes_128_ctr) +NCRYPTO_LAZY_CIPHER(AES_192_CTR, NID_aes_192_ctr) +NCRYPTO_LAZY_CIPHER(AES_256_CTR, NID_aes_256_ctr) +NCRYPTO_LAZY_CIPHER(AES_128_GCM, NID_aes_128_gcm) +NCRYPTO_LAZY_CIPHER(AES_192_GCM, NID_aes_192_gcm) +NCRYPTO_LAZY_CIPHER(AES_256_GCM, NID_aes_256_gcm) +NCRYPTO_LAZY_CIPHER(AES_128_KW, NID_id_aes128_wrap) +NCRYPTO_LAZY_CIPHER(AES_192_KW, NID_id_aes192_wrap) +NCRYPTO_LAZY_CIPHER(AES_256_KW, NID_id_aes256_wrap) #ifndef OPENSSL_IS_BORINGSSL -const Cipher Cipher::AES_128_OCB = Cipher::FromNid(NID_aes_128_ocb); -const Cipher Cipher::AES_192_OCB = Cipher::FromNid(NID_aes_192_ocb); -const Cipher Cipher::AES_256_OCB = Cipher::FromNid(NID_aes_256_ocb); +NCRYPTO_LAZY_CIPHER(AES_128_OCB, NID_aes_128_ocb) +NCRYPTO_LAZY_CIPHER(AES_192_OCB, NID_aes_192_ocb) +NCRYPTO_LAZY_CIPHER(AES_256_OCB, NID_aes_256_ocb) #endif -const Cipher Cipher::CHACHA20_POLY1305 = Cipher::FromNid(NID_chacha20_poly1305); +NCRYPTO_LAZY_CIPHER(CHACHA20_POLY1305, NID_chacha20_poly1305) + +#undef NCRYPTO_LAZY_CIPHER bool Cipher::isGcmMode() const { if (!cipher_) return false; diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 58e32cc18fc7..29a70ec9d110 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -502,25 +502,26 @@ class Cipher final { // Utilities to get various ciphers by type. If the underlying // implementation does not support the requested cipher, then // the result will be an empty Cipher object whose bool operator - // will return false. + // will return false. The ciphers are looked up lazily on first use so + // that merely loading the library does not initialize OpenSSL. static const Cipher EMPTY; - static const Cipher AES_128_CBC; - static const Cipher AES_192_CBC; - static const Cipher AES_256_CBC; - static const Cipher AES_128_CTR; - static const Cipher AES_192_CTR; - static const Cipher AES_256_CTR; - static const Cipher AES_128_GCM; - static const Cipher AES_192_GCM; - static const Cipher AES_256_GCM; - static const Cipher AES_128_KW; - static const Cipher AES_192_KW; - static const Cipher AES_256_KW; - static const Cipher AES_128_OCB; - static const Cipher AES_192_OCB; - static const Cipher AES_256_OCB; - static const Cipher CHACHA20_POLY1305; + static const Cipher& AES_128_CBC(); + static const Cipher& AES_192_CBC(); + static const Cipher& AES_256_CBC(); + static const Cipher& AES_128_CTR(); + static const Cipher& AES_192_CTR(); + static const Cipher& AES_256_CTR(); + static const Cipher& AES_128_GCM(); + static const Cipher& AES_192_GCM(); + static const Cipher& AES_256_GCM(); + static const Cipher& AES_128_KW(); + static const Cipher& AES_192_KW(); + static const Cipher& AES_256_KW(); + static const Cipher& AES_128_OCB(); + static const Cipher& AES_192_OCB(); + static const Cipher& AES_256_OCB(); + static const Cipher& CHACHA20_POLY1305(); struct CipherParams { int padding; diff --git a/src/crypto/crypto_aes.h b/src/crypto/crypto_aes.h index 76359f14e7df..6b18b1e29e24 100644 --- a/src/crypto/crypto_aes.h +++ b/src/crypto/crypto_aes.h @@ -13,15 +13,15 @@ namespace node::crypto { constexpr unsigned kNoAuthTagLength = static_cast(-1); #define VARIANTS_COMMON(V) \ - V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR) \ - V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR) \ - V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR) \ - V(CBC_128, AES_Cipher, ncrypto::Cipher::AES_128_CBC) \ - V(CBC_192, AES_Cipher, ncrypto::Cipher::AES_192_CBC) \ - V(CBC_256, AES_Cipher, ncrypto::Cipher::AES_256_CBC) \ - V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM) \ - V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM) \ - V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM) \ + V(CTR_128, AES_CTR_Cipher, ncrypto::Cipher::AES_128_CTR()) \ + V(CTR_192, AES_CTR_Cipher, ncrypto::Cipher::AES_192_CTR()) \ + V(CTR_256, AES_CTR_Cipher, ncrypto::Cipher::AES_256_CTR()) \ + V(CBC_128, AES_Cipher, ncrypto::Cipher::AES_128_CBC()) \ + V(CBC_192, AES_Cipher, ncrypto::Cipher::AES_192_CBC()) \ + V(CBC_256, AES_Cipher, ncrypto::Cipher::AES_256_CBC()) \ + V(GCM_128, AES_Cipher, ncrypto::Cipher::AES_128_GCM()) \ + V(GCM_192, AES_Cipher, ncrypto::Cipher::AES_192_GCM()) \ + V(GCM_256, AES_Cipher, ncrypto::Cipher::AES_256_GCM()) \ VARIANTS_KW(V) #ifdef OPENSSL_IS_BORINGSSL @@ -33,16 +33,16 @@ constexpr unsigned kNoAuthTagLength = static_cast(-1); V(KW_256, AES_KW_Cipher, static_cast(nullptr)) #else #define VARIANTS_KW(V) \ - V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW) \ - V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW) \ - V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW) + V(KW_128, AES_Cipher, ncrypto::Cipher::AES_128_KW()) \ + V(KW_192, AES_Cipher, ncrypto::Cipher::AES_192_KW()) \ + V(KW_256, AES_Cipher, ncrypto::Cipher::AES_256_KW()) #endif #if OPENSSL_WITH_AES_OCB #define VARIANTS_OCB(V) \ - V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB) \ - V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB) \ - V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB) + V(OCB_128, AES_Cipher, ncrypto::Cipher::AES_128_OCB()) \ + V(OCB_192, AES_Cipher, ncrypto::Cipher::AES_192_OCB()) \ + V(OCB_256, AES_Cipher, ncrypto::Cipher::AES_256_OCB()) #else #define VARIANTS_OCB(V) #endif diff --git a/src/crypto/crypto_chacha20_poly1305.cc b/src/crypto/crypto_chacha20_poly1305.cc index 1cdb933c65d4..56ba78be7ccd 100644 --- a/src/crypto/crypto_chacha20_poly1305.cc +++ b/src/crypto/crypto_chacha20_poly1305.cc @@ -105,7 +105,7 @@ Maybe ChaCha20Poly1305CipherTraits::AdditionalConfig( ChaCha20Poly1305CipherConfig* params) { Environment* env = Environment::GetCurrent(args); - params->cipher = ncrypto::Cipher::CHACHA20_POLY1305; + params->cipher = ncrypto::Cipher::CHACHA20_POLY1305(); #ifndef OPENSSL_IS_BORINGSSL // On BoringSSL, ChaCha20-Poly1305 is not exposed via the EVP_CIPHER registry diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index 95564f02b343..c9c73037fe97 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -2507,9 +2507,11 @@ int SecureContext::TicketKeyCallback(SSL* ssl, ArrayBufferViewContents aes_key(aes.As()); if (enc) { - EVP_EncryptInit_ex(ectx, Cipher::AES_128_CBC, nullptr, aes_key.data(), iv); + EVP_EncryptInit_ex( + ectx, Cipher::AES_128_CBC(), nullptr, aes_key.data(), iv); } else { - EVP_DecryptInit_ex(ectx, Cipher::AES_128_CBC, nullptr, aes_key.data(), iv); + EVP_DecryptInit_ex( + ectx, Cipher::AES_128_CBC(), nullptr, aes_key.data(), iv); } return r; @@ -2531,8 +2533,11 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl, if (enc) { memcpy(name, sc->ticket_key_name_, sizeof(sc->ticket_key_name_)); if (!ncrypto::CSPRNG(iv, 16) || - EVP_EncryptInit_ex( - ectx, Cipher::AES_128_CBC, nullptr, sc->ticket_key_aes_, iv) <= 0 || + EVP_EncryptInit_ex(ectx, + Cipher::AES_128_CBC(), + nullptr, + sc->ticket_key_aes_, + iv) <= 0 || !InitTicketHmac( hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_))) { return -1; @@ -2546,7 +2551,7 @@ int SecureContext::TicketCompatibilityCallback(SSL* ssl, } if (EVP_DecryptInit_ex( - ectx, Cipher::AES_128_CBC, nullptr, sc->ticket_key_aes_, iv) <= 0 || + ectx, Cipher::AES_128_CBC(), nullptr, sc->ticket_key_aes_, iv) <= 0 || !InitTicketHmac( hctx, sc->ticket_key_hmac_, sizeof(sc->ticket_key_hmac_))) { return -1; diff --git a/src/node.cc b/src/node.cc index 7d51cbd5d0c7..1a65ad89e667 100644 --- a/src/node.cc +++ b/src/node.cc @@ -533,7 +533,17 @@ static void PlatformInit(ProcessInitializationFlags::Flags flags) { init_process_flags.store(flags); if (!(flags & ProcessInitializationFlags::kNoStdioInitialization)) { - atexit(ResetStdio); + // Arrange for ResetStdio() to run at exit. A function-local static with a + // destructor is used instead of atexit() because on macOS atexit() calls + // dladdr() to locate the image that owns the callback, which does a linear + // scan of the (very large) symbol table and costs about a millisecond of + // startup time. Static destructors and atexit() handlers are registered in + // the same list and run in reverse order of registration, so the ordering + // relative to other handlers is unchanged. + static const struct ResetStdioAtExit { + ~ResetStdioAtExit() { ResetStdio(); } + } reset_stdio_at_exit; + (void)reset_stdio_at_exit; } #ifdef __POSIX__ @@ -1214,7 +1224,14 @@ InitializeOncePerProcessInternal(const std::vector& args, OPENSSL_INIT_set_config_file_flags(settings, CONF_MFLAGS_IGNORE_MISSING_FILE); - OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG, settings); + // OPENSSL_INIT_NO_ATEXIT: do not let OpenSSL register OPENSSL_cleanup() + // with atexit(). Nothing needs OpenSSL to be torn down when the process + // exits, and on macOS atexit() itself is expensive: it calls dladdr(), + // which linearly scans the executable's (very large) symbol table and + // costs about a millisecond on every process start. This must be part of + // the first OPENSSL_init_crypto() call in the process to take effect. + OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_NO_ATEXIT, + settings); OPENSSL_INIT_free(settings); if (ERR_peek_error() != 0) {