Move OpenSSL locking code out of SSLContext
authorMingtao Yang <mingtao@fb.com>
Tue, 25 Jul 2017 18:52:40 +0000 (11:52 -0700)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 25 Jul 2017 19:16:08 +0000 (12:16 -0700)
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

14 files changed:
folly/Makefile.am
folly/io/async/SSLContext.cpp
folly/io/async/SSLContext.h
folly/io/async/test/AsyncSSLSocketTest2.cpp
folly/io/async/test/AsyncSocketExceptionTest.cpp
folly/io/async/test/SSLContextInitializationTest.cpp [new file with mode: 0644]
folly/portability/OpenSSL.cpp
folly/portability/OpenSSL.h
folly/ssl/Init.cpp [new file with mode: 0644]
folly/ssl/Init.h [new file with mode: 0644]
folly/ssl/OpenSSLLockTypes.h [new file with mode: 0644]
folly/ssl/detail/OpenSSLThreading.cpp [new file with mode: 0644]
folly/ssl/detail/OpenSSLThreading.h [new file with mode: 0644]
folly/ssl/test/OpenSSLCertUtilsTest.cpp

index c2c8a83833e23766541d741ae7e112fbd3b8edc1..df98a75643d3750f5198295e515b3cf89093d0ae 100644 (file)
@@ -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 \
index 1ccd7308ade0f2839f50006e8d2e9ffbe7313c54..95ae99a12edff49cdaa921f2cf243f769b0503b8 100644 (file)
 #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) {
index f6aa3794e26a9ab0eefe761d11d78ab3deaa2dc5..bf377ec8110f6db72344ef45d740b207b493477d 100644 (file)
 #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;
index f0b5dd7fbf5cd7b700b28e19c71e1e670c2b9de2..9b214785c69b6981e8710ef871e7ff892eba1a77 100644 (file)
@@ -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();
index 7e73df05d6cefbb933427dd7b94e7daf87d7e18c..78d2341b4d2b5e2de8f730ce8c58a242e54ac4d7 100644 (file)
@@ -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);
 
diff --git a/folly/io/async/test/SSLContextInitializationTest.cpp b/folly/io/async/test/SSLContextInitializationTest.cpp
new file mode 100644 (file)
index 0000000..656337a
--- /dev/null
@@ -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 <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();
+}
index f0dc9d726ceb1a21fd9e75d9510c9a3520063ad8..5de51036fb2e0291323f69ad67c34f1c79fd24e1 100644 (file)
@@ -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
 }
 }
 }
index 113a917531567d701abb78a86e7a86e867ac121a..c1ed8c64612924c7de3f1f24a1fba22250151b3c 100644 (file)
@@ -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
diff --git a/folly/ssl/Init.cpp b/folly/ssl/Init.cpp
new file mode 100644 (file)
index 0000000..c6e8ba3
--- /dev/null
@@ -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 <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
diff --git a/folly/ssl/Init.h b/folly/ssl/Init.h
new file mode 100644 (file)
index 0000000..98a4fab
--- /dev/null
@@ -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 <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
diff --git a/folly/ssl/OpenSSLLockTypes.h b/folly/ssl/OpenSSLLockTypes.h
new file mode 100644 (file)
index 0000000..0e03c1f
--- /dev/null
@@ -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 <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
diff --git a/folly/ssl/detail/OpenSSLThreading.cpp b/folly/ssl/detail/OpenSSLThreading.cpp
new file mode 100644 (file)
index 0000000..c9402b3
--- /dev/null
@@ -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 <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
diff --git a/folly/ssl/detail/OpenSSLThreading.h b/folly/ssl/detail/OpenSSLThreading.h
new file mode 100644 (file)
index 0000000..5a7b9c5
--- /dev/null
@@ -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 <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();
+}
+}
+}
index a337b4ff02e9043333eb3ac3da8fc632d2b9a1d2..46adbf4d5d207b7b3c19c7c0e705ad36f3764e08 100644 (file)
 
 #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();
   }
 };