From: Mingtao Yang Date: Tue, 25 Jul 2017 18:52:40 +0000 (-0700) Subject: Move OpenSSL locking code out of SSLContext X-Git-Tag: v2017.07.31.00~35 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=409667068c1dbbac5e453fc80a316d777f2caae9 Move OpenSSL locking code out of SSLContext Summary: OpenSSL 1.1.0 deprecates the callback based locking setup in favor for platform native mutexes. Added `OPENSSL_init_ssl` in the OpenSSL portability module for OpenSSL API < 1.1.0. This implements the standard OpenSSL library initialization routines (taken from SSLContext::initializeOpenSSL). Added `OPENSSL_cleanup` in the OpenSSL portability module for OpenSSL API < 1.1.0. This implements the cleanup routine from SSLContext::cleanupOpenSSL. Removed `SSLContext::SSLLockType`. Replaced with `folly::ssl::LockType`. Reviewed By: mzlee, ngoyal Differential Revision: D5404777 fbshipit-source-id: 7e5d9bf4a6683afb5560ada0a5b73cac3ff2662b --- diff --git a/folly/Makefile.am b/folly/Makefile.am index c2c8a838..df98a756 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -379,11 +379,14 @@ nobase_follyinclude_HEADERS = \ sorted_vector_types.h \ SparseByteSet.h \ SpinLock.h \ + ssl/Init.h \ ssl/OpenSSLCertUtils.h \ ssl/OpenSSLHash.h \ ssl/OpenSSLPtrTypes.h \ ssl/OpenSSLVersionFinder.h \ ssl/SSLSession.h \ + ssl/OpenSSLLockTypes.h \ + ssl/detail/OpenSSLThreading.h \ ssl/detail/SSLSessionImpl.h \ stats/detail/Bucket.h \ stats/BucketedTimeSeries-defs.h \ @@ -551,8 +554,10 @@ libfolly_la_SOURCES = \ Optional.cpp \ Singleton.cpp \ SocketAddress.cpp \ + ssl/Init.cpp \ ssl/OpenSSLCertUtils.cpp \ ssl/OpenSSLHash.cpp \ + ssl/detail/OpenSSLThreading.cpp \ ssl/detail/SSLSessionImpl.cpp \ stats/BucketedTimeSeries.cpp \ stats/Histogram.cpp \ diff --git a/folly/io/async/SSLContext.cpp b/folly/io/async/SSLContext.cpp index 1ccd7308..95ae99a1 100644 --- a/folly/io/async/SSLContext.cpp +++ b/folly/io/async/SSLContext.cpp @@ -22,41 +22,19 @@ #include #include #include +#include // --------------------------------------------------------------------- // SSLContext implementation // --------------------------------------------------------------------- - -struct CRYPTO_dynlock_value { - std::mutex mutex; -}; - namespace folly { // // For OpenSSL portability API using namespace folly::ssl; -bool SSLContext::initialized_ = false; - -namespace { - -std::mutex& initMutex() { - static std::mutex m; - return m; -} - -} // anonymous namespace - -#ifdef OPENSSL_NPN_NEGOTIATED -int SSLContext::sNextProtocolsExDataIndex_ = -1; -#endif - // SSLContext implementation SSLContext::SSLContext(SSLVersion version) { - { - std::lock_guard g(initMutex()); - initializeOpenSSLLocked(); - } + folly::ssl::init(); ctx_ = SSL_CTX_new(SSLv23_method()); if (ctx_ == nullptr) { @@ -352,10 +330,6 @@ void SSLContext::loadClientCAList(const char* path) { SSL_CTX_set_client_CA_list(ctx_, clientCAs); } -void SSLContext::randomize() { - RAND_poll(); -} - void SSLContext::passwordCollector( std::shared_ptr collector) { if (collector == nullptr) { @@ -586,6 +560,9 @@ size_t SSLContext::pickNextProtocols() { int SSLContext::advertisedNextProtocolCallback(SSL* ssl, const unsigned char** out, unsigned int* outlen, void* data) { + static int nextProtocolsExDataIndex = SSL_get_ex_new_index( + 0, (void*)"Advertised next protocol index", nullptr, nullptr, nullptr); + SSLContext* context = (SSLContext*)data; if (context == nullptr || context->advertisedNextProtocols_.empty()) { *out = nullptr; @@ -594,8 +571,8 @@ int SSLContext::advertisedNextProtocolCallback(SSL* ssl, *out = context->advertisedNextProtocols_[0].protocols; *outlen = context->advertisedNextProtocols_[0].length; } else { - uintptr_t selected_index = reinterpret_cast(SSL_get_ex_data(ssl, - sNextProtocolsExDataIndex_)); + uintptr_t selected_index = reinterpret_cast( + SSL_get_ex_data(ssl, nextProtocolsExDataIndex)); if (selected_index) { --selected_index; *out = context->advertisedNextProtocols_[selected_index].protocols; @@ -603,7 +580,7 @@ int SSLContext::advertisedNextProtocolCallback(SSL* ssl, } else { auto i = context->pickNextProtocols(); uintptr_t selected = i + 1; - SSL_set_ex_data(ssl, sNextProtocolsExDataIndex_, (void*)selected); + SSL_set_ex_data(ssl, nextProtocolsExDataIndex, (void*)selected); *out = context->advertisedNextProtocols_[i].protocols; *outlen = context->advertisedNextProtocols_[i].length; } @@ -721,126 +698,8 @@ int SSLContext::passwordCallback(char* password, return length; } -struct SSLLock { - explicit SSLLock( - SSLContext::SSLLockType inLockType = SSLContext::LOCK_MUTEX) : - lockType(inLockType) { - } - - void lock(bool read) { - if (lockType == SSLContext::LOCK_MUTEX) { - mutex.lock(); - } else if (lockType == SSLContext::LOCK_SPINLOCK) { - spinLock.lock(); - } else if (lockType == SSLContext::LOCK_SHAREDMUTEX) { - if (read) { - sharedMutex.lock_shared(); - } else { - sharedMutex.lock(); - } - } - // lockType == LOCK_NONE, no-op - } - - void unlock(bool read) { - if (lockType == SSLContext::LOCK_MUTEX) { - mutex.unlock(); - } else if (lockType == SSLContext::LOCK_SPINLOCK) { - spinLock.unlock(); - } else if (lockType == SSLContext::LOCK_SHAREDMUTEX) { - if (read) { - sharedMutex.unlock_shared(); - } else { - sharedMutex.unlock(); - } - } - // lockType == LOCK_NONE, no-op - } - - SSLContext::SSLLockType lockType; - folly::SpinLock spinLock{}; - std::mutex mutex; - SharedMutex sharedMutex; -}; - -// Statics are unsafe in environments that call exit(). -// If one thread calls exit() while another thread is -// references a member of SSLContext, bad things can happen. -// SSLContext runs in such environments. -// Instead of declaring a static member we "new" the static -// member so that it won't be destructed on exit(). -static std::unique_ptr& locks() { - static auto locksInst = new std::unique_ptr(); - return *locksInst; -} - -static std::map& lockTypes() { - static auto lockTypesInst = new std::map(); - return *lockTypesInst; -} - -static void callbackLocking(int mode, int n, const char*, int) { - if (mode & CRYPTO_LOCK) { - locks()[size_t(n)].lock(mode & CRYPTO_READ); - } else { - locks()[size_t(n)].unlock(mode & CRYPTO_READ); - } -} - -static unsigned long callbackThreadID() { - return static_cast(folly::getCurrentThreadID()); -} - -static CRYPTO_dynlock_value* dyn_create(const char*, int) { - return new CRYPTO_dynlock_value; -} - -static void dyn_lock(int mode, - struct CRYPTO_dynlock_value* lock, - const char*, int) { - if (lock != nullptr) { - if (mode & CRYPTO_LOCK) { - lock->mutex.lock(); - } else { - lock->mutex.unlock(); - } - } -} - -static void dyn_destroy(struct CRYPTO_dynlock_value* lock, const char*, int) { - delete lock; -} - -void SSLContext::setSSLLockTypesLocked(std::map inLockTypes) { - lockTypes() = inLockTypes; -} - -void SSLContext::setSSLLockTypes(std::map inLockTypes) { - std::lock_guard g(initMutex()); - if (initialized_) { - // We set the locks on initialization, so if we are already initialized - // this would have no affect. - LOG(INFO) << "Ignoring setSSLLockTypes after initialization"; - return; - } - setSSLLockTypesLocked(std::move(inLockTypes)); -} - -void SSLContext::setSSLLockTypesAndInitOpenSSL( - std::map inLockTypes) { - std::lock_guard g(initMutex()); - CHECK(!initialized_) << "OpenSSL is already initialized"; - setSSLLockTypesLocked(std::move(inLockTypes)); - initializeOpenSSLLocked(); -} - -bool SSLContext::isSSLLockDisabled(int lockId) { - std::lock_guard g(initMutex()); - CHECK(initialized_) << "OpenSSL is not initialized yet"; - const auto& sslLocks = lockTypes(); - const auto it = sslLocks.find(lockId); - return it != sslLocks.end() && - it->second == SSLContext::SSLLockType::LOCK_NONE; +void SSLContext::setSSLLockTypes(std::map inLockTypes) { + folly::ssl::setLockTypes(inLockTypes); } #if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH) @@ -849,63 +708,8 @@ void SSLContext::enableFalseStart() { } #endif -void SSLContext::markInitialized() { - std::lock_guard g(initMutex()); - initialized_ = true; -} - void SSLContext::initializeOpenSSL() { - std::lock_guard g(initMutex()); - initializeOpenSSLLocked(); -} - -void SSLContext::initializeOpenSSLLocked() { - if (initialized_) { - return; - } - SSL_library_init(); - SSL_load_error_strings(); - ERR_load_crypto_strings(); - // static locking - locks().reset(new SSLLock[size_t(CRYPTO_num_locks())]); - for (auto it: lockTypes()) { - locks()[size_t(it.first)].lockType = it.second; - } - CRYPTO_set_id_callback(callbackThreadID); - CRYPTO_set_locking_callback(callbackLocking); - // dynamic locking - CRYPTO_set_dynlock_create_callback(dyn_create); - CRYPTO_set_dynlock_lock_callback(dyn_lock); - CRYPTO_set_dynlock_destroy_callback(dyn_destroy); - randomize(); -#ifdef OPENSSL_NPN_NEGOTIATED - sNextProtocolsExDataIndex_ = SSL_get_ex_new_index(0, - (void*)"Advertised next protocol index", nullptr, nullptr, nullptr); -#endif - initialized_ = true; -} - -void SSLContext::cleanupOpenSSL() { - std::lock_guard g(initMutex()); - cleanupOpenSSLLocked(); -} - -void SSLContext::cleanupOpenSSLLocked() { - if (!initialized_) { - return; - } - - CRYPTO_set_id_callback(nullptr); - CRYPTO_set_locking_callback(nullptr); - CRYPTO_set_dynlock_create_callback(nullptr); - CRYPTO_set_dynlock_lock_callback(nullptr); - CRYPTO_set_dynlock_destroy_callback(nullptr); - CRYPTO_cleanup_all_ex_data(); - ERR_free_strings(); - EVP_cleanup(); - ERR_clear_error(); - locks().reset(); - initialized_ = false; + folly::ssl::init(); } void SSLContext::setOptions(long options) { diff --git a/folly/io/async/SSLContext.h b/folly/io/async/SSLContext.h index f6aa3794..bf377ec8 100644 --- a/folly/io/async/SSLContext.h +++ b/folly/io/async/SSLContext.h @@ -30,9 +30,11 @@ #include #endif +#include #include #include #include +#include #include namespace folly { @@ -421,8 +423,6 @@ class SSLContext { return ctx_; } - enum SSLLockType { LOCK_MUTEX, LOCK_SPINLOCK, LOCK_SHAREDMUTEX, LOCK_NONE }; - /** * Set preferences for how to treat locks in OpenSSL. This must be * called before the instantiation of any SSLContext objects, otherwise @@ -443,24 +443,8 @@ class SSLContext { * * setSSLLockTypes({{CRYPTO_LOCK_SSL_SESSION, SSLContext::LOCK_NONE}}) */ - static void setSSLLockTypes(std::map lockTypes); - - /** - * Set the lock types and initialize OpenSSL in an atomic fashion. This - * aborts if the library has already been initialized. - */ - static void setSSLLockTypesAndInitOpenSSL( - std::map lockTypes); - - /** - * Determine if the SSL lock with the specified id (i.e. - * CRYPTO_LOCK_SSL_SESSION) is disabled. This should be called after - * initializeOpenSSL. This will only check if the specified lock has been - * explicitly set to LOCK_NONE. - * - * This is not safe to call while setSSLLockTypes is being called. - */ - static bool isSSLLockDisabled(int lockId); + FOLLY_DEPRECATED("Use folly::ssl::setLockTypes") + static void setSSLLockTypes(std::map lockTypes); /** * Examine OpenSSL's error stack, and return a string description of the @@ -498,24 +482,8 @@ class SSLContext { */ static bool matchName(const char* host, const char* pattern, int size); - /** - * Functions for setting up and cleaning up openssl. - * They can be invoked during the start of the application. - */ + FOLLY_DEPRECATED("Use folly::ssl::init") static void initializeOpenSSL(); - static void cleanupOpenSSL(); - - /** - * Mark openssl as initialized without actually performing any initialization. - * Please use this only if you are using a library which requires that it must - * make its own calls to SSL_library_init() and related functions. - */ - static void markInitialized(); - - /** - * Default randomize method. - */ - static void randomize(); protected: SSL_CTX* ctx_; @@ -552,8 +520,6 @@ class SSLContext { std::vector advertisedNextProtocolWeights_; std::discrete_distribution nextProtocolDistribution_; - static int sNextProtocolsExDataIndex_; - static int advertisedNextProtocolCallback(SSL* ssl, const unsigned char** out, unsigned int* outlen, void* data); static int selectNextProtocolCallback( @@ -593,11 +559,6 @@ class SSLContext { #endif std::string providedCiphersString_; - - // Functions are called when locked by the calling function. - static void initializeOpenSSLLocked(); - static void cleanupOpenSSLLocked(); - static void setSSLLockTypesLocked(std::map inLockTypes); }; typedef std::shared_ptr SSLContextPtr; diff --git a/folly/io/async/test/AsyncSSLSocketTest2.cpp b/folly/io/async/test/AsyncSSLSocketTest2.cpp index f0b5dd7f..9b214785 100644 --- a/folly/io/async/test/AsyncSSLSocketTest2.cpp +++ b/folly/io/async/test/AsyncSSLSocketTest2.cpp @@ -190,58 +190,13 @@ TEST(AsyncSSLSocketTest2, AttachDetachSSLContext) { EXPECT_TRUE(f.within(std::chrono::seconds(3)).get()); } -TEST(AsyncSSLSocketTest2, SSLContextLocks) { - SSLContext::initializeOpenSSL(); -// these are checks based on the locks that are set in the main below -#ifdef CRYPTO_LOCK_EVP_PKEY - EXPECT_TRUE(SSLContext::isSSLLockDisabled(CRYPTO_LOCK_EVP_PKEY)); -#endif -#ifdef CRYPTO_LOCK_SSL_SESSION - EXPECT_FALSE(SSLContext::isSSLLockDisabled(CRYPTO_LOCK_SSL_SESSION)); -#endif -#ifdef CRYPTO_LOCK_ERR - EXPECT_FALSE(SSLContext::isSSLLockDisabled(CRYPTO_LOCK_ERR)); -#endif -} -TEST(AsyncSSLSocketTest2, SSLContextLocksSetAfterInitIgnored) { - SSLContext::initializeOpenSSL(); - SSLContext::setSSLLockTypes({}); -#ifdef CRYPTO_LOCK_EVP_PKEY - EXPECT_TRUE(SSLContext::isSSLLockDisabled(CRYPTO_LOCK_EVP_PKEY)); -#endif -} - -TEST(AsyncSSLSocketTest2, SSLContextSetLocksAndInitialize) { - SSLContext::cleanupOpenSSL(); - SSLContext::setSSLLockTypesAndInitOpenSSL({}); - EXPECT_DEATH( - SSLContext::setSSLLockTypesAndInitOpenSSL({}), - "OpenSSL is already initialized"); - - SSLContext::cleanupOpenSSL(); - SSLContext::initializeOpenSSL(); - EXPECT_DEATH( - SSLContext::setSSLLockTypesAndInitOpenSSL({}), - "OpenSSL is already initialized"); -} } // folly int main(int argc, char *argv[]) { #ifdef SIGPIPE signal(SIGPIPE, SIG_IGN); #endif - folly::SSLContext::setSSLLockTypes({ -#ifdef CRYPTO_LOCK_EVP_PKEY - {CRYPTO_LOCK_EVP_PKEY, folly::SSLContext::LOCK_NONE}, -#endif -#ifdef CRYPTO_LOCK_SSL_SESSION - {CRYPTO_LOCK_SSL_SESSION, folly::SSLContext::LOCK_SPINLOCK}, -#endif -#ifdef CRYPTO_LOCK_SSL_CTX - {CRYPTO_LOCK_SSL_CTX, folly::SSLContext::LOCK_NONE} -#endif - }); testing::InitGoogleTest(&argc, argv); folly::init(&argc, &argv); return RUN_ALL_TESTS(); diff --git a/folly/io/async/test/AsyncSocketExceptionTest.cpp b/folly/io/async/test/AsyncSocketExceptionTest.cpp index 7e73df05..78d2341b 100644 --- a/folly/io/async/test/AsyncSocketExceptionTest.cpp +++ b/folly/io/async/test/AsyncSocketExceptionTest.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -54,7 +55,7 @@ TEST(AsyncSocketException, SimpleTest) { TEST(AsyncSocketException, SSLExceptionType) { { // Initiailzes OpenSSL everything. Else some of the calls will block - folly::SSLContext::initializeOpenSSL(); + folly::ssl::init(); SSLException eof(SSL_ERROR_ZERO_RETURN, 0, 0, 0); EXPECT_EQ(eof.getType(), AsyncSocketException::END_OF_FILE); diff --git a/folly/io/async/test/SSLContextInitializationTest.cpp b/folly/io/async/test/SSLContextInitializationTest.cpp new file mode 100644 index 00000000..656337a5 --- /dev/null +++ b/folly/io/async/test/SSLContextInitializationTest.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 2017-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include +#include +#include +#include + +namespace folly { + +void setupSSLLocks() { + folly::ssl::setLockTypes({ +#ifdef CRYPTO_LOCK_EVP_PKEY + {CRYPTO_LOCK_EVP_PKEY, folly::ssl::LockType::NONE}, +#endif +#ifdef CRYPTO_LOCK_SSL_SESSION + {CRYPTO_LOCK_SSL_SESSION, folly::ssl::LockType::SPINLOCK}, +#endif +#ifdef CRYPTO_LOCK_SSL_CTX + {CRYPTO_LOCK_SSL_CTX, folly::ssl::LockType::NONE} +#endif + }); +} + +TEST(SSLContextInitializationTest, SSLContextInitializeThenSetLocksAndInit) { + EXPECT_DEATH( + { + folly::ssl::init(); + folly::ssl::setLockTypesAndInit({}); + }, + "OpenSSL is already initialized"); +} + +TEST(SSLContextInitializationTest, SSLContextSetLocksAndInitialize) { + EXPECT_DEATH( + { + folly::ssl::setLockTypesAndInit({}); + folly::ssl::setLockTypesAndInit({}); + }, + "OpenSSL is already initialized"); +} + +TEST(SSLContextInitializationTest, SSLContextLocks) { + EXPECT_EXIT( + { + setupSSLLocks(); + folly::ssl::init(); +#ifdef CRYPTO_LOCK_EVP_PKEY + EXPECT_TRUE(folly::ssl::isLockDisabled(CRYPTO_LOCK_EVP_PKEY)); +#endif +#ifdef CRYPTO_LOCK_SSL_SESSION + EXPECT_FALSE(folly::ssl::isLockDisabled(CRYPTO_LOCK_SSL_SESSION)); +#endif +#ifdef CRYPTO_LOCK_ERR + EXPECT_FALSE(folly::ssl::isLockDisabled(CRYPTO_LOCK_ERR)); +#endif + if (::testing::Test::HasFailure()) { + exit(1); + } + LOG(INFO) << "SSLContextLocks passed"; + exit(0); + }, + ::testing::ExitedWithCode(0), + "SSLContextLocks passed"); +} + +TEST(SSLContextInitializationTest, SSLContextLocksSetAfterInitIgnored) { + EXPECT_EXIT( + { + setupSSLLocks(); + folly::ssl::init(); + folly::ssl::setLockTypes({}); +#ifdef CRYPTO_LOCK_EVP_PKEY + EXPECT_TRUE(folly::ssl::isLockDisabled(CRYPTO_LOCK_EVP_PKEY)); +#endif + if (::testing::Test::HasFailure()) { + exit(1); + } + LOG(INFO) << "SSLContextLocksSetAfterInitIgnored passed"; + exit(0); + }, + ::testing::ExitedWithCode(0), + "SSLContextLocksSetAfterInitIgnored passed"); +} +} // folly + +int main(int argc, char* argv[]) { +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + testing::InitGoogleTest(&argc, argv); + folly::init(&argc, &argv); + + return RUN_ALL_TESTS(); +} diff --git a/folly/portability/OpenSSL.cpp b/folly/portability/OpenSSL.cpp index f0dc9d72..5de51036 100644 --- a/folly/portability/OpenSSL.cpp +++ b/folly/portability/OpenSSL.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ #include +#include #include @@ -357,7 +358,41 @@ void ECDSA_SIG_get0( *ps = sig->s; } } -#endif + +/** + * Compatibility shim for OpenSSL < 1.1.0. + * + * For now, options and settings are ignored. We implement the most common + * behavior, which is to add all digests, ciphers, and strings. + */ +int OPENSSL_init_ssl(uint64_t, const OPENSSL_INIT_SETTINGS*) { + // OpenSSL >= 1.1.0 handles initializing the library, adding digests & + // ciphers, loading strings. Additionally, OpenSSL >= 1.1.0 uses platform + // native threading & mutexes, which means that we should handle setting up + // the necessary threading initialization in the compat layer as well. + SSL_library_init(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + OpenSSL_add_all_algorithms(); + + SSL_load_error_strings(); + ERR_load_crypto_strings(); + + // The caller should have used SSLContext::setLockTypes() prior to calling + // this function. + folly::ssl::detail::installThreadingLocks(); + return 0; +} + +void OPENSSL_cleanup() { + folly::ssl::detail::cleanupThreadingLocks(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + EVP_cleanup(); + ERR_clear_error(); +} + +#endif // !FOLLY_OPENSSL_IS_110 } } } diff --git a/folly/portability/OpenSSL.h b/folly/portability/OpenSSL.h index 113a9175..c1ed8c64 100644 --- a/folly/portability/OpenSSL.h +++ b/folly/portability/OpenSSL.h @@ -16,6 +16,8 @@ #pragma once +#include + // This must come before the OpenSSL includes. #include @@ -166,6 +168,11 @@ void RSA_get0_crt_params( const BIGNUM** iqmp); int ECDSA_SIG_set0(ECDSA_SIG* sig, BIGNUM* r, BIGNUM* s); void ECDSA_SIG_get0(const ECDSA_SIG* sig, const BIGNUM** pr, const BIGNUM** ps); + +using OPENSSL_INIT_SETTINGS = void; +int OPENSSL_init_ssl(uint64_t opts, const OPENSSL_INIT_SETTINGS* settings); +void OPENSSL_cleanup(); + #endif #if FOLLY_OPENSSL_IS_110 diff --git a/folly/ssl/Init.cpp b/folly/ssl/Init.cpp new file mode 100644 index 00000000..c6e8ba35 --- /dev/null +++ b/folly/ssl/Init.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2017-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include + +#include +#include +#include + +namespace folly { +namespace ssl { + +namespace { +bool initialized_ = false; + +std::mutex& initMutex() { + static std::mutex m; + return m; +} + +void initializeOpenSSLLocked() { + if (initialized_) { + return; + } + OPENSSL_init_ssl(0, nullptr); + randomize(); + initialized_ = true; +} + +void cleanupOpenSSLLocked() { + if (!initialized_) { + return; + } + + OPENSSL_cleanup(); + initialized_ = false; +} +} + +void init() { + std::lock_guard g(initMutex()); + initializeOpenSSLLocked(); +} + +void cleanup() { + std::lock_guard g(initMutex()); + cleanupOpenSSLLocked(); +} + +void markInitialized() { + std::lock_guard g(initMutex()); + initialized_ = true; +} + +void setLockTypesAndInit(LockTypeMapping inLockTypes) { + std::lock_guard g(initMutex()); + CHECK(!initialized_) << "OpenSSL is already initialized"; + detail::setLockTypes(std::move(inLockTypes)); + initializeOpenSSLLocked(); +} + +void setLockTypes(LockTypeMapping inLockTypes) { + std::lock_guard g(initMutex()); + if (initialized_) { + // We set the locks on initialization, so if we are already initialized + // this would have no affect. + LOG(INFO) << "Ignoring setSSLLockTypes after initialization"; + return; + } + detail::setLockTypes(std::move(inLockTypes)); +} + +void randomize() { + RAND_poll(); +} + +bool isLockDisabled(int lockId) { + return detail::isSSLLockDisabled(lockId); +} + +} // ssl +} // folly diff --git a/folly/ssl/Init.h b/folly/ssl/Init.h new file mode 100644 index 00000000..98a4fab9 --- /dev/null +++ b/folly/ssl/Init.h @@ -0,0 +1,86 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include + +namespace folly { +namespace ssl { +/** + * Initializes openssl. This should be invoked once, during the start of an + * application. For OpenSSL < 1.1.0, any lock types should be set with + * setLockTypes prior to the call to folly::ssl::init() + */ +void init(); + +/** + * Cleans up openssl. This should be invoked at most once during the lifetime + * of the application. OpenSSL >= 1.1.0 users do not need to manually invoke + * this method, as OpenSSL will automatically cleanup itself during the exit + * of the application. + */ +void cleanup(); + +/** + * Mark openssl as initialized without actually performing any initialization. + * Please use this only if you are using a library which requires that it must + * make its own calls to SSL_library_init() and related functions. + */ +void markInitialized(); + +/** + * Set preferences for how to treat locks in OpenSSL. This must be + * called before folly::ssl::init(), otherwise the defaults will be used. + * + * OpenSSL has a lock for each module rather than for each object or + * data that needs locking. Some locks protect only refcounts, and + * might be better as spinlocks rather than mutexes. Other locks + * may be totally unnecessary if the objects being protected are not + * shared between threads in the application. + * + * For a list of OpenSSL lock types, refer to crypto/crypto.h. + * + * By default, all locks are initialized as mutexes. OpenSSL's lock usage + * may change from version to version and you should know what you are doing + * before disabling any locks entirely. + * + * In newer versions of OpenSSL (>= 1.1.0), OpenSSL manages its own locks, + * and this function is a no-op. + * + * Example: if you don't share SSL sessions between threads in your + * application, you may be able to do this + * + * setSSLLockTypes({{ + * CRYPTO_LOCK_SSL_SESSION, + * SSLContext::SSLLockType::LOCK_NONE + * }}) + */ +void setLockTypes(LockTypeMapping inLockTypes); + +/** + * Set the lock types and initialize OpenSSL in an atomic fashion. This + * aborts if the library has already been initialized. + */ +void setLockTypesAndInit(LockTypeMapping lockTypes); + +bool isLockDisabled(int lockId); + +void randomize(); + +} // ssl +} // folly diff --git a/folly/ssl/OpenSSLLockTypes.h b/folly/ssl/OpenSSLLockTypes.h new file mode 100644 index 00000000..0e03c1f7 --- /dev/null +++ b/folly/ssl/OpenSSLLockTypes.h @@ -0,0 +1,32 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +namespace folly { +namespace ssl { + +enum class LockType { MUTEX, SPINLOCK, SHAREDMUTEX, NONE }; + +/** + * Map between an OpenSSL lock (see constants in crypto/crypto.h) and the + * implementation of the lock + */ +using LockTypeMapping = std::map; + +} // ssl +} // folly diff --git a/folly/ssl/detail/OpenSSLThreading.cpp b/folly/ssl/detail/OpenSSLThreading.cpp new file mode 100644 index 00000000..c9402b35 --- /dev/null +++ b/folly/ssl/detail/OpenSSLThreading.cpp @@ -0,0 +1,178 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include +#include +#include + +#include + +// We cannot directly use portability/openssl because it also depends on us. +// Therefore we directly use openssl includes. Order of includes is important +// here. See portability/openssl.h. +#include +#include + +#if !defined(OPENSSL_IS_BORINGSSL) +#define FOLLY_SSL_DETAIL_OPENSSL_IS_110 (OPENSSL_VERSION_NUMBER >= 0x10100000L) +#else +#define FOLLY_SSL_DETAIL_OPENSSL_IS_110 (false) +#endif + +// OpenSSL requires us to provide the implementation of CRYPTO_dynlock_value +// so it must be done in the global namespace. +struct CRYPTO_dynlock_value { + std::mutex mutex; +}; + +namespace folly { +namespace ssl { +namespace detail { + +static std::map& lockTypes() { + static auto lockTypesInst = new std::map(); + return *lockTypesInst; +} + +void setLockTypes(std::map inLockTypes) { +#if FOLLY_SSL_DETAIL_OPENSSL_IS_110 + LOG(INFO) << "setLockTypes() is unsupported on OpenSSL >= 1.1.0. " + << "OpenSSL now uses platform native mutexes"; +#endif + + lockTypes() = inLockTypes; +} + +bool isSSLLockDisabled(int lockId) { + const auto& sslLocks = lockTypes(); + const auto it = sslLocks.find(lockId); + return it != sslLocks.end() && it->second == LockType::NONE; +} + +namespace { +struct SSLLock { + explicit SSLLock(LockType inLockType = LockType::MUTEX) + : lockType(inLockType) {} + + void lock(bool read) { + if (lockType == LockType::MUTEX) { + mutex.lock(); + } else if (lockType == LockType::SPINLOCK) { + spinLock.lock(); + } else if (lockType == LockType::SHAREDMUTEX) { + if (read) { + sharedMutex.lock_shared(); + } else { + sharedMutex.lock(); + } + } + // lockType == LOCK_NONE, no-op + } + + void unlock(bool read) { + if (lockType == LockType::MUTEX) { + mutex.unlock(); + } else if (lockType == LockType::SPINLOCK) { + spinLock.unlock(); + } else if (lockType == LockType::SHAREDMUTEX) { + if (read) { + sharedMutex.unlock_shared(); + } else { + sharedMutex.unlock(); + } + } + // lockType == LOCK_NONE, no-op + } + + LockType lockType; + folly::SpinLock spinLock{}; + std::mutex mutex; + SharedMutex sharedMutex; +}; +} // end anonymous namespace + +// Statics are unsafe in environments that call exit(). +// If one thread calls exit() while another thread is +// references a member of SSLContext, bad things can happen. +// SSLContext runs in such environments. +// Instead of declaring a static member we "new" the static +// member so that it won't be destructed on exit(). +static std::unique_ptr& locks() { + static auto locksInst = new std::unique_ptr(); + return *locksInst; +} + +static void callbackLocking(int mode, int n, const char*, int) { + if (mode & CRYPTO_LOCK) { + locks()[size_t(n)].lock(mode & CRYPTO_READ); + } else { + locks()[size_t(n)].unlock(mode & CRYPTO_READ); + } +} + +static unsigned long callbackThreadID() { + return static_cast(folly::getCurrentThreadID()); +} + +static CRYPTO_dynlock_value* dyn_create(const char*, int) { + return new CRYPTO_dynlock_value; +} + +static void +dyn_lock(int mode, struct CRYPTO_dynlock_value* lock, const char*, int) { + if (lock != nullptr) { + if (mode & CRYPTO_LOCK) { + lock->mutex.lock(); + } else { + lock->mutex.unlock(); + } + } +} + +static void dyn_destroy(struct CRYPTO_dynlock_value* lock, const char*, int) { + delete lock; +} + +void installThreadingLocks() { + // static locking + locks().reset(new SSLLock[size_t(CRYPTO_num_locks())]); + for (auto it : lockTypes()) { + locks()[size_t(it.first)].lockType = it.second; + } + CRYPTO_set_id_callback(callbackThreadID); + CRYPTO_set_locking_callback(callbackLocking); + // dynamic locking + CRYPTO_set_dynlock_create_callback(dyn_create); + CRYPTO_set_dynlock_lock_callback(dyn_lock); + CRYPTO_set_dynlock_destroy_callback(dyn_destroy); +} + +void cleanupThreadingLocks() { + CRYPTO_set_id_callback(nullptr); + CRYPTO_set_locking_callback(nullptr); + CRYPTO_set_dynlock_create_callback(nullptr); + CRYPTO_set_dynlock_lock_callback(nullptr); + CRYPTO_set_dynlock_destroy_callback(nullptr); + locks().reset(); +} + +} // detail +} // ssl +} // folly diff --git a/folly/ssl/detail/OpenSSLThreading.h b/folly/ssl/detail/OpenSSLThreading.h new file mode 100644 index 00000000..5a7b9c5c --- /dev/null +++ b/folly/ssl/detail/OpenSSLThreading.h @@ -0,0 +1,31 @@ +/* + * Copyright 2004-present Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +#include + +#include + +namespace folly { +namespace ssl { +namespace detail { +bool isSSLLockDisabled(int lockId); +void setLockTypes(std::map inLockTypes); +void installThreadingLocks(); +void cleanupThreadingLocks(); +} +} +} diff --git a/folly/ssl/test/OpenSSLCertUtilsTest.cpp b/folly/ssl/test/OpenSSLCertUtilsTest.cpp index a337b4ff..46adbf4d 100644 --- a/folly/ssl/test/OpenSSLCertUtilsTest.cpp +++ b/folly/ssl/test/OpenSSLCertUtilsTest.cpp @@ -18,10 +18,10 @@ #include #include -#include #include #include #include +#include using namespace testing; using namespace folly; @@ -60,7 +60,7 @@ const std::string kTestCertWithSan = folly::stripLeftMargin(R"( class OpenSSLCertUtilsTest : public Test { public: void SetUp() override { - SSLContext::initializeOpenSSL(); + folly::ssl::init(); } };