SSL_SESSION wrapper
authorAnirudh Ramachandran <avr@fb.com>
Mon, 26 Sep 2016 21:55:14 +0000 (14:55 -0700)
committerFacebook Github Bot 7 <facebook-github-bot-7-bot@fb.com>
Mon, 26 Sep 2016 22:08:29 +0000 (15:08 -0700)
Summary:
This is a start to wrapping various SSL objects going forward so different
binaries can choose different version of OpenSSL (i.e., BoringSSL, OpenSSL
1.1.0, OpenSSL 1.0.2, etc.). There's no change to the caller - everyone just
uses 'SSLSession', but the implementation details vary

Reviewed By: siyengar

Differential Revision: D3707791

fbshipit-source-id: f895334a768cb7d43b41af40c9bc06be5307cc7f

folly/Makefile.am
folly/io/async/test/SSLSessionTest.cpp [new file with mode: 0644]
folly/ssl/SSLSession.h [new file with mode: 0644]
folly/ssl/detail/OpenSSLVersionFinder.h [new file with mode: 0644]
folly/ssl/detail/SSLSessionImpl.cpp [new file with mode: 0644]
folly/ssl/detail/SSLSessionImpl.h [new file with mode: 0644]

index d1cdf4b..1d204e0 100644 (file)
@@ -326,6 +326,9 @@ nobase_follyinclude_HEADERS = \
        SpookyHashV1.h \
        SpookyHashV2.h \
        ssl/OpenSSLHash.h \
+       ssl/SSLSession.h \
+       ssl/detail/OpenSSLVersionFinder.h \
+       ssl/detail/SSLSessionImpl.h \
        stats/BucketedTimeSeries-defs.h \
        stats/BucketedTimeSeries.h \
        stats/Histogram-defs.h \
@@ -475,6 +478,7 @@ libfolly_la_SOURCES = \
        SpookyHashV1.cpp \
        SpookyHashV2.cpp \
        ssl/OpenSSLHash.cpp \
+       ssl/detail/SSLSessionImpl.cpp \
        stats/Instantiations.cpp \
        Subprocess.cpp \
        ThreadCachedArena.cpp \
diff --git a/folly/io/async/test/SSLSessionTest.cpp b/folly/io/async/test/SSLSessionTest.cpp
new file mode 100644 (file)
index 0000000..ce7084c
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2016 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 <folly/portability/GTest.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+#include <folly/ssl/SSLSession.h>
+
+using namespace std;
+using namespace testing;
+using folly::ssl::SSLSession;
+
+namespace folly {
+
+const char* testCert = "folly/io/async/test/certs/tests-cert.pem";
+const char* testKey = "folly/io/async/test/certs/tests-key.pem";
+const char* testCA = "folly/io/async/test/certs/ca-cert.pem";
+
+void getfds(int fds[2]) {
+  if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
+    LOG(ERROR) << "failed to create socketpair: " << strerror(errno);
+  }
+  for (int idx = 0; idx < 2; ++idx) {
+    int flags = fcntl(fds[idx], F_GETFL, 0);
+    if (flags == -1) {
+      LOG(ERROR) << "failed to get flags for socket " << idx << ": "
+                 << strerror(errno);
+    }
+    if (fcntl(fds[idx], F_SETFL, flags | O_NONBLOCK) != 0) {
+      LOG(ERROR) << "failed to put socket " << idx
+                 << " in non-blocking mode: " << strerror(errno);
+    }
+  }
+}
+
+void getctx(
+    std::shared_ptr<folly::SSLContext> clientCtx,
+    std::shared_ptr<folly::SSLContext> serverCtx) {
+  clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+  serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+  serverCtx->loadCertificate(testCert);
+  serverCtx->loadPrivateKey(testKey);
+}
+
+class SSLSessionTest : public testing::Test {
+ public:
+  void SetUp() override {
+    clientCtx.reset(new folly::SSLContext());
+    dfServerCtx.reset(new folly::SSLContext());
+    hskServerCtx.reset(new folly::SSLContext());
+    serverName = "xyz.newdev.facebook.com";
+    getctx(clientCtx, dfServerCtx);
+  }
+
+  void TearDown() override {}
+
+  folly::EventBase eventBase;
+  std::shared_ptr<SSLContext> clientCtx;
+  std::shared_ptr<SSLContext> dfServerCtx;
+  // Use the same SSLContext to continue the handshake after
+  // tlsext_hostname match.
+  std::shared_ptr<SSLContext> hskServerCtx;
+  std::string serverName;
+};
+
+/**
+ * 1. Client sends TLSEXT_HOSTNAME in client hello.
+ * 2. Server found a match SSL_CTX and use this SSL_CTX to
+ *    continue the SSL handshake.
+ * 3. Server sends back TLSEXT_HOSTNAME in server hello.
+ */
+TEST_F(SSLSessionTest, BasicTest) {
+  std::unique_ptr<SSLSession> sess;
+
+  {
+    int fds[2];
+    getfds(fds);
+    AsyncSSLSocket::UniquePtr clientSock(
+        new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
+    auto clientPtr = clientSock.get();
+    AsyncSSLSocket::UniquePtr serverSock(
+        new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
+    SSLHandshakeClient client(std::move(clientSock), false, false);
+    SSLHandshakeServerParseClientHello server(
+        std::move(serverSock), false, false);
+
+    eventBase.loop();
+    ASSERT_TRUE(client.handshakeSuccess_);
+
+    sess.reset(new SSLSession(clientPtr->getSSLSession()));
+    ASSERT_NE(sess.get(), nullptr);
+  }
+
+  {
+    int fds[2];
+    getfds(fds);
+    AsyncSSLSocket::UniquePtr clientSock(
+        new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
+    auto clientPtr = clientSock.get();
+    clientSock->setSSLSession(sess->getRawSSLSessionDangerous(), true);
+    AsyncSSLSocket::UniquePtr serverSock(
+        new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
+    SSLHandshakeClient client(std::move(clientSock), false, false);
+    SSLHandshakeServerParseClientHello server(
+        std::move(serverSock), false, false);
+
+    eventBase.loop();
+    ASSERT_TRUE(client.handshakeSuccess_);
+    ASSERT_TRUE(clientPtr->getSSLSessionReused());
+  }
+}
+TEST_F(SSLSessionTest, SerializeDeserializeTest) {
+  std::string sessiondata;
+
+  {
+    int fds[2];
+    getfds(fds);
+    AsyncSSLSocket::UniquePtr clientSock(
+        new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
+    auto clientPtr = clientSock.get();
+    AsyncSSLSocket::UniquePtr serverSock(
+        new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
+    SSLHandshakeClient client(std::move(clientSock), false, false);
+    SSLHandshakeServerParseClientHello server(
+        std::move(serverSock), false, false);
+
+    eventBase.loop();
+    ASSERT_TRUE(client.handshakeSuccess_);
+
+    std::unique_ptr<SSLSession> sess =
+        folly::make_unique<SSLSession>(clientPtr->getSSLSession());
+    sessiondata = sess->serialize();
+    ASSERT_TRUE(!sessiondata.empty());
+  }
+
+  {
+    int fds[2];
+    getfds(fds);
+    AsyncSSLSocket::UniquePtr clientSock(
+        new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
+    auto clientPtr = clientSock.get();
+    std::unique_ptr<SSLSession> sess =
+        folly::make_unique<SSLSession>(sessiondata);
+    ASSERT_NE(sess.get(), nullptr);
+    clientSock->setSSLSession(sess->getRawSSLSessionDangerous(), true);
+    AsyncSSLSocket::UniquePtr serverSock(
+        new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
+    SSLHandshakeClient client(std::move(clientSock), false, false);
+    SSLHandshakeServerParseClientHello server(
+        std::move(serverSock), false, false);
+
+    eventBase.loop();
+    ASSERT_TRUE(client.handshakeSuccess_);
+    ASSERT_TRUE(clientPtr->getSSLSessionReused());
+  }
+}
+
+TEST_F(SSLSessionTest, GetSessionID) {
+  int fds[2];
+  getfds(fds);
+  AsyncSSLSocket::UniquePtr clientSock(
+      new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
+  auto clientPtr = clientSock.get();
+  AsyncSSLSocket::UniquePtr serverSock(
+      new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
+  SSLHandshakeClient client(std::move(clientSock), false, false);
+  SSLHandshakeServerParseClientHello server(
+      std::move(serverSock), false, false);
+
+  eventBase.loop();
+  ASSERT_TRUE(client.handshakeSuccess_);
+
+  std::unique_ptr<SSLSession> sess =
+      folly::make_unique<SSLSession>(clientPtr->getSSLSession());
+  ASSERT_NE(sess, nullptr);
+  auto sessID = sess->getSessionID();
+  ASSERT_GE(sessID.length(), 0);
+}
+}
diff --git a/folly/ssl/SSLSession.h b/folly/ssl/SSLSession.h
new file mode 100644 (file)
index 0000000..e7424ab
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2016 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 <folly/Memory.h>
+#include <folly/ssl/detail/OpenSSLVersionFinder.h>
+#include <folly/ssl/detail/SSLSessionImpl.h>
+
+namespace folly {
+namespace ssl {
+
+class SSLSession {
+ public:
+  // Holds and takes ownership of an SSL_SESSION object by incrementing refcount
+  explicit SSLSession(SSL_SESSION* session, bool takeOwnership = true)
+      : impl_(folly::make_unique<detail::SSLSessionImpl>(
+            session,
+            takeOwnership)) {}
+
+  // Deserialize from a string
+  explicit SSLSession(const std::string& serializedSession)
+      : impl_(folly::make_unique<detail::SSLSessionImpl>(serializedSession)) {}
+
+  // Serialize to a string that is suitable to store in a persistent cache
+  std::string serialize() const {
+    return impl_->serialize();
+  }
+
+  // Get Session ID. Returns an empty container if session isn't set
+  std::string getSessionID() const {
+    return impl_->getSessionID();
+  }
+
+  // Get a const raw SSL_SESSION ptr without incrementing referecnce count
+  // (Warning: do not use)
+  const SSL_SESSION* getRawSSLSession() const {
+    return impl_->getRawSSLSession();
+  }
+
+  // Get raw SSL_SESSION pointer
+  // Warning: do not use unless you know what you're doing - caller needs to
+  // decrement refcount using SSL_SESSION_free or this will leak
+  SSL_SESSION* getRawSSLSessionDangerous() {
+    return impl_->getRawSSLSessionDangerous();
+  }
+
+ private:
+  std::unique_ptr<detail::SSLSessionImpl> impl_;
+};
+
+} // namespace ssl
+} // namespace folly
diff --git a/folly/ssl/detail/OpenSSLVersionFinder.h b/folly/ssl/detail/OpenSSLVersionFinder.h
new file mode 100644 (file)
index 0000000..8ff6cdd
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016 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 <folly/Conv.h>
+
+#include <openssl/crypto.h>
+#include <openssl/opensslv.h>
+
+#define OPENSSL_IS_101                      \
+  (OPENSSL_VERSION_NUMBER >= 0x1000105fL && \
+   OPENSSL_VERSION_NUMBER < 0x1000200fL)
+#define OPENSSL_IS_102                      \
+  (OPENSSL_VERSION_NUMBER >= 0x1000200fL && \
+   OPENSSL_VERSION_NUMBER < 0x10100000L)
+#define OPENSSL_IS_110 (OPENSSL_VERSION_NUMBER >= 0x10100000L)
+
+// This is used to find the OpenSSL version at runtime. Just returning
+// OPENSSL_VERSION_NUMBER is insufficient as runtime version may be different
+// from the compile-time version
+struct OpenSSLVersionFinder {
+  static std::string getOpenSSLLongVersion(void) {
+#ifdef OPENSSL_VERSION_TEXT
+    return SSLeay_version(SSLEAY_VERSION);
+#elif defined(OPENSSL_VERSION_NUMBER)
+    return folly::format("0x{:x}", OPENSSL_VERSION_NUMBER).str();
+#else
+    return "";
+#endif
+  }
+
+  uint64_t getOpenSSLNumericVersion(void) {
+#ifdef OPENSSL_VERSION_NUMBER
+    return SSLeay();
+#else
+    return 0;
+#endif
+  }
+};
diff --git a/folly/ssl/detail/SSLSessionImpl.cpp b/folly/ssl/detail/SSLSessionImpl.cpp
new file mode 100644 (file)
index 0000000..7a83c1e
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2016 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/SSLSessionImpl.h>
+#include <folly/ssl/detail/OpenSSLVersionFinder.h>
+
+namespace folly {
+namespace ssl {
+namespace detail {
+
+//
+// Wrapper OpenSSL 1.0.2 (and possibly 1.0.1)
+//
+
+SSLSessionImpl::SSLSessionImpl(SSL_SESSION* session, bool takeOwnership)
+    : session_(session) {
+  if (session_ == nullptr) {
+    throw std::runtime_error("SSL_SESSION is null");
+  }
+  // If we're not given ownership, we need to up the refcount so the SSL_SESSION
+  // object won't be freed while SSLSessionImpl is alive
+  if (!takeOwnership) {
+    upRef();
+  }
+}
+
+SSLSessionImpl::SSLSessionImpl(const std::string& serializedSession) {
+  auto sessionData =
+      reinterpret_cast<const unsigned char*>(serializedSession.data());
+  if ((session_ = d2i_SSL_SESSION(
+           nullptr, &sessionData, serializedSession.length())) == nullptr) {
+    throw std::runtime_error("Cannot deserialize SSLSession string");
+  }
+}
+
+SSLSessionImpl::~SSLSessionImpl() {
+  downRef();
+}
+
+std::string SSLSessionImpl::serialize(void) const {
+  std::string ret;
+
+  // Get the length first, then we know how much space to allocate.
+  auto len = i2d_SSL_SESSION(session_, nullptr);
+
+  if (len > 0) {
+    std::unique_ptr<unsigned char[]> uptr(new unsigned char[len]);
+    auto p = uptr.get();
+    auto written = i2d_SSL_SESSION(session_, &p);
+    if (written <= 0) {
+      VLOG(2) << "Could not serialize SSL_SESSION!";
+    } else {
+      ret.assign(uptr.get(), uptr.get() + written);
+    }
+  }
+  return ret;
+}
+
+std::string SSLSessionImpl::getSessionID() const {
+  std::string ret;
+  if (session_) {
+    const unsigned char* ptr = nullptr;
+    unsigned int len = 0;
+#if defined(OPENSSL_IS_102) || defined(OPENSSL_IS_101)
+    len = session_->session_id_length;
+    ptr = session_->session_id;
+#elif defined(OPENSSL_IS_110) || defined(OPENSSL_IS_BORINGSSL)
+    ptr = SSL_SESSION_get_id(session_, &len);
+#endif
+    ret.assign(ptr, ptr + len);
+  }
+  return ret;
+}
+
+const SSL_SESSION* SSLSessionImpl::getRawSSLSession() const {
+  return const_cast<SSL_SESSION*>(session_);
+}
+
+SSL_SESSION* SSLSessionImpl::getRawSSLSessionDangerous() {
+  upRef();
+  return session_;
+}
+
+void SSLSessionImpl::upRef() {
+  if (session_) {
+#if defined(OPENSSL_IS_102) || defined(OPENSSL_IS_101)
+    CRYPTO_add(&session_->references, 1, CRYPTO_LOCK_SSL_SESSION);
+#elif defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_110)
+    SSL_SESSION_up_ref(&session_);
+#endif
+  }
+}
+
+void SSLSessionImpl::downRef() {
+  if (session_) {
+    SSL_SESSION_free(session_);
+  }
+}
+
+} // namespace detail
+} // namespace ssl
+} // namespace folly
diff --git a/folly/ssl/detail/SSLSessionImpl.h b/folly/ssl/detail/SSLSessionImpl.h
new file mode 100644 (file)
index 0000000..0adfa13
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2016 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 <folly/Range.h>
+#include <openssl/ssl.h>
+#include <string>
+
+namespace folly {
+namespace ssl {
+namespace detail {
+
+class SSLSessionImpl {
+ public:
+  explicit SSLSessionImpl(SSL_SESSION* session, bool takeOwnership = true);
+  explicit SSLSessionImpl(const std::string& serializedSession);
+  virtual ~SSLSessionImpl();
+  std::string serialize() const;
+  std::string getSessionID() const;
+  const SSL_SESSION* getRawSSLSession() const;
+  SSL_SESSION* getRawSSLSessionDangerous();
+
+ private:
+  void upRef();
+  void downRef();
+
+  SSL_SESSION* session_{nullptr};
+};
+
+} // namespace detail
+} // namespace ssl
+} // namespace folly