Commit 40966706 authored by Mingtao Yang's avatar Mingtao Yang Committed by Facebook Github Bot

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
parent d1ddabb0
......@@ -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 \
......
......@@ -22,41 +22,19 @@
#include <folly/SharedMutex.h>
#include <folly/SpinLock.h>
#include <folly/ThreadId.h>
#include <folly/ssl/Init.h>
// ---------------------------------------------------------------------
// 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<std::mutex> 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<PasswordCollector> 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<uintptr_t>(SSL_get_ex_data(ssl,
sNextProtocolsExDataIndex_));
uintptr_t selected_index = reinterpret_cast<uintptr_t>(
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<SSLLock[]>& locks() {
static auto locksInst = new std::unique_ptr<SSLLock[]>();
return *locksInst;
}
static std::map<int, SSLContext::SSLLockType>& lockTypes() {
static auto lockTypesInst = new std::map<int, SSLContext::SSLLockType>();
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<unsigned long>(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<int, SSLLockType> inLockTypes) {
lockTypes() = inLockTypes;
}
void SSLContext::setSSLLockTypes(std::map<int, SSLLockType> inLockTypes) {
std::lock_guard<std::mutex> 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<int, SSLLockType> inLockTypes) {
std::lock_guard<std::mutex> g(initMutex());
CHECK(!initialized_) << "OpenSSL is already initialized";
setSSLLockTypesLocked(std::move(inLockTypes));
initializeOpenSSLLocked();
}
bool SSLContext::isSSLLockDisabled(int lockId) {
std::lock_guard<std::mutex> 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<int, LockType> 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<std::mutex> g(initMutex());
initialized_ = true;
}
void SSLContext::initializeOpenSSL() {
std::lock_guard<std::mutex> 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<std::mutex> 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) {
......
......@@ -30,9 +30,11 @@
#include <folly/folly-config.h>
#endif
#include <folly/Portability.h>
#include <folly/Range.h>
#include <folly/io/async/ssl/OpenSSLUtils.h>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/OpenSSLLockTypes.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
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<int, SSLLockType> 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<int, SSLLockType> 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<int, ssl::LockType> 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<int> advertisedNextProtocolWeights_;
std::discrete_distribution<int> 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<int, SSLLockType> inLockTypes);
};
typedef std::shared_ptr<SSLContext> SSLContextPtr;
......
......@@ -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();
......
......@@ -18,6 +18,7 @@
#include <folly/io/async/AsyncSocketException.h>
#include <folly/io/async/SSLContext.h>
#include <folly/io/async/ssl/SSLErrors.h>
#include <folly/ssl/Init.h>
#include <folly/portability/GTest.h>
#include <folly/portability/OpenSSL.h>
......@@ -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);
......
/*
* 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 <folly/io/async/test/AsyncSSLSocketTest.h>
#include <functional>
#include <folly/init/Init.h>
#include <folly/io/async/SSLContext.h>
#include <folly/portability/GTest.h>
#include <folly/ssl/Init.h>
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();
}
......@@ -14,6 +14,7 @@
* limitations under the License.
*/
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/detail/OpenSSLThreading.h>
#include <stdexcept>
......@@ -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
}
}
}
......@@ -16,6 +16,8 @@
#pragma once
#include <cstdint>
// This must come before the OpenSSL includes.
#include <folly/portability/Windows.h>
......@@ -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
......
/*
* 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 <folly/ssl/Init.h>
#include <mutex>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/detail/OpenSSLThreading.h>
#include <glog/logging.h>
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<std::mutex> g(initMutex());
initializeOpenSSLLocked();
}
void cleanup() {
std::lock_guard<std::mutex> g(initMutex());
cleanupOpenSSLLocked();
}
void markInitialized() {
std::lock_guard<std::mutex> g(initMutex());
initialized_ = true;
}
void setLockTypesAndInit(LockTypeMapping inLockTypes) {
std::lock_guard<std::mutex> g(initMutex());
CHECK(!initialized_) << "OpenSSL is already initialized";
detail::setLockTypes(std::move(inLockTypes));
initializeOpenSSLLocked();
}
void setLockTypes(LockTypeMapping inLockTypes) {
std::lock_guard<std::mutex> 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
/*
* 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 <map>
#include <folly/ssl/OpenSSLLockTypes.h>
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
/*
* 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 <map>
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<int, LockType>;
} // ssl
} // folly
/*
* 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 <folly/ssl/detail/OpenSSLThreading.h>
#include <mutex>
#include <folly/Portability.h>
#include <folly/SharedMutex.h>
#include <folly/SpinLock.h>
#include <glog/logging.h>
// 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 <folly/portability/Windows.h>
#include <openssl/crypto.h>
#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<int, LockType>& lockTypes() {
static auto lockTypesInst = new std::map<int, LockType>();
return *lockTypesInst;
}
void setLockTypes(std::map<int, LockType> 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<SSLLock[]>& locks() {
static auto locksInst = new std::unique_ptr<SSLLock[]>();
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<unsigned long>(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
/*
* 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 <map>
#include <folly/ssl/OpenSSLLockTypes.h>
namespace folly {
namespace ssl {
namespace detail {
bool isSSLLockDisabled(int lockId);
void setLockTypes(std::map<int, LockType> inLockTypes);
void installThreadingLocks();
void cleanupThreadingLocks();
}
}
}
......@@ -18,10 +18,10 @@
#include <folly/Range.h>
#include <folly/String.h>
#include <folly/io/async/SSLContext.h>
#include <folly/portability/GTest.h>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <folly/ssl/Init.H>
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();
}
};
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment