Split out SSL test server for reuse
authorDaniel Sommermann <dcsommer@whatsapp.com>
Wed, 25 Jan 2017 17:32:24 +0000 (09:32 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Wed, 25 Jan 2017 17:33:11 +0000 (09:33 -0800)
Summary:
The TestSSLServer is useful to have on its own for reuse. This
diff separates it out to make reuse possible.

Reviewed By: yfeldblum

Differential Revision: D4457976

fbshipit-source-id: 1fe6b71e930859ef04487e29047dcf603a79834b

folly/Makefile.am
folly/io/async/test/AsyncSSLSocketTest.cpp
folly/io/async/test/AsyncSSLSocketTest.h
folly/io/async/test/SSLSessionTest.cpp
folly/io/async/test/TestSSLServer.cpp [new file with mode: 0644]
folly/io/async/test/TestSSLServer.h [new file with mode: 0644]

index 2fc98c82137d161ffe16258cd77cada7d67b1e87..80dbbdf140d933a2f564b94d6d2c021831c65b22 100644 (file)
@@ -250,6 +250,7 @@ nobase_follyinclude_HEADERS = \
        io/async/test/MockTimeoutManager.h \
        io/async/test/ScopedBoundPort.h \
        io/async/test/SocketPair.h \
+       io/async/test/TestSSLServer.h \
        io/async/test/TimeUtil.h \
        io/async/test/UndelayedDestruction.h \
        io/async/test/Util.h \
index 87c1453ce1cd492fc8a0feb9e652075b115dea1f..b5c6430ac268d3f18f7f56cb2154efde08b45641 100644 (file)
@@ -15,7 +15,6 @@
  */
 #include <folly/io/async/test/AsyncSSLSocketTest.h>
 
-#include <pthread.h>
 #include <signal.h>
 
 #include <folly/SocketAddress.h>
@@ -53,45 +52,9 @@ uint32_t TestSSLAsyncCacheServer::asyncCallbacks_ = 0;
 uint32_t TestSSLAsyncCacheServer::asyncLookups_ = 0;
 uint32_t TestSSLAsyncCacheServer::lookupDelay_ = 0;
 
-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";
-
 constexpr size_t SSLClient::kMaxReadBufferSz;
 constexpr size_t SSLClient::kMaxReadsPerEvent;
 
-TestSSLServer::TestSSLServer(SSLServerAcceptCallbackBase* acb, bool enableTFO)
-    : ctx_(new folly::SSLContext),
-      acb_(acb),
-      socket_(folly::AsyncServerSocket::newSocket(&evb_)) {
-  // Set up the SSL context
-  ctx_->loadCertificate(testCert);
-  ctx_->loadPrivateKey(testKey);
-  ctx_->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-
-  acb_->ctx_ = ctx_;
-  acb_->base_ = &evb_;
-
-  // Enable TFO
-  if (enableTFO) {
-    LOG(INFO) << "server TFO enabled";
-    socket_->setTFOEnabled(true, 1000);
-  }
-
-  // set up the listening socket
-  socket_->bind(0);
-  socket_->getAddress(&address_);
-  socket_->listen(100);
-  socket_->addAcceptCallback(acb_, &evb_);
-  socket_->startAccepting();
-
-  int ret = pthread_create(&thread_, nullptr, Main, this);
-  assert(ret == 0);
-  (void)ret;
-
-  std::cerr << "Accepting connections on " << address_ << std::endl;
-}
-
 void getfds(int fds[2]) {
   if (socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
     FAIL() << "failed to create socketpair: " << strerror(errno);
@@ -115,10 +78,8 @@ void getctx(
   clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 
   serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  serverCtx->loadCertificate(
-      testCert);
-  serverCtx->loadPrivateKey(
-      testKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadPrivateKey(kTestKey);
 }
 
 void sslsocketpair(
@@ -1023,16 +984,16 @@ TEST(AsyncSSLSocketTest, SSLParseClientHelloSuccess) {
   auto serverCtx = std::make_shared<SSLContext>();
   serverCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
   serverCtx->ciphers("ECDHE-RSA-AES128-SHA:AES128-SHA:AES256-SHA");
-  serverCtx->loadPrivateKey(testKey);
-  serverCtx->loadCertificate(testCert);
-  serverCtx->loadTrustedCertificates(testCA);
-  serverCtx->loadClientCAList(testCA);
+  serverCtx->loadPrivateKey(kTestKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadTrustedCertificates(kTestCA);
+  serverCtx->loadClientCAList(kTestCA);
 
   clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
   clientCtx->ciphers("AES256-SHA:AES128-SHA");
-  clientCtx->loadPrivateKey(testKey);
-  clientCtx->loadCertificate(testCert);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadPrivateKey(kTestKey);
+  clientCtx->loadCertificate(kTestCert);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   int fds[2];
   getfds(fds);
@@ -1214,7 +1175,7 @@ TEST(AsyncSSLSocketTest, SSLHandshakeValidationSuccess) {
     new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
 
   SSLHandshakeClient client(std::move(clientSock), true, true);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   SSLHandshakeServer server(std::move(serverSock), true, true);
 
@@ -1252,7 +1213,7 @@ TEST(AsyncSSLSocketTest, SSLHandshakeValidationFailure) {
     new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
 
   SSLHandshakeClient client(std::move(clientSock), true, false);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   SSLHandshakeServer server(std::move(serverSock), true, true);
 
@@ -1292,7 +1253,7 @@ TEST(AsyncSSLSocketTest, OverrideSSLCtxDisableVerify) {
     new AsyncSSLSocket(dfServerCtx, &eventBase, fds[1], true));
 
   SSLHandshakeClientNoVerify client(std::move(clientSock), false, false);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   SSLHandshakeServerNoVerify server(std::move(serverSock), false, false);
 
@@ -1319,16 +1280,16 @@ TEST(AsyncSSLSocketTest, OverrideSSLCtxEnableVerify) {
   auto serverCtx = std::make_shared<SSLContext>();
   serverCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
   serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  serverCtx->loadPrivateKey(testKey);
-  serverCtx->loadCertificate(testCert);
-  serverCtx->loadTrustedCertificates(testCA);
-  serverCtx->loadClientCAList(testCA);
+  serverCtx->loadPrivateKey(kTestKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadTrustedCertificates(kTestCA);
+  serverCtx->loadClientCAList(kTestCA);
 
   clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
   clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  clientCtx->loadPrivateKey(testKey);
-  clientCtx->loadCertificate(testCert);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadPrivateKey(kTestKey);
+  clientCtx->loadCertificate(kTestCert);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   int fds[2];
   getfds(fds);
@@ -1436,16 +1397,16 @@ TEST(AsyncSSLSocketTest, ClientCertHandshakeSuccess) {
   serverCtx->setVerificationOption(
       SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT);
   serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  serverCtx->loadPrivateKey(testKey);
-  serverCtx->loadCertificate(testCert);
-  serverCtx->loadTrustedCertificates(testCA);
-  serverCtx->loadClientCAList(testCA);
+  serverCtx->loadPrivateKey(kTestKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadTrustedCertificates(kTestCA);
+  serverCtx->loadClientCAList(kTestCA);
 
   clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::VERIFY);
   clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  clientCtx->loadPrivateKey(testKey);
-  clientCtx->loadCertificate(testCert);
-  clientCtx->loadTrustedCertificates(testCA);
+  clientCtx->loadPrivateKey(kTestKey);
+  clientCtx->loadCertificate(kTestCert);
+  clientCtx->loadTrustedCertificates(kTestCA);
 
   int fds[2];
   getfds(fds);
@@ -1481,10 +1442,10 @@ TEST(AsyncSSLSocketTest, NoClientCertHandshakeError) {
   serverCtx->setVerificationOption(
       SSLContext::SSLVerifyPeerEnum::VERIFY_REQ_CLIENT_CERT);
   serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  serverCtx->loadPrivateKey(testKey);
-  serverCtx->loadCertificate(testCert);
-  serverCtx->loadTrustedCertificates(testCA);
-  serverCtx->loadClientCAList(testCA);
+  serverCtx->loadPrivateKey(kTestKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadTrustedCertificates(kTestCA);
+  serverCtx->loadClientCAList(kTestCA);
   clientCtx->setVerificationOption(SSLContext::SSLVerifyPeerEnum::NO_VERIFY);
   clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 
@@ -1509,8 +1470,8 @@ TEST(AsyncSSLSocketTest, NoClientCertHandshakeError) {
 }
 
 TEST(AsyncSSLSocketTest, LoadCertFromMemory) {
-  auto cert = getFileAsBuf(testCert);
-  auto key = getFileAsBuf(testKey);
+  auto cert = getFileAsBuf(kTestCert);
+  auto key = getFileAsBuf(kTestKey);
 
   ssl::BioUniquePtr certBio(BIO_new(BIO_s_mem()));
   BIO_write(certBio.get(), cert.data(), cert.size());
@@ -1533,7 +1494,7 @@ TEST(AsyncSSLSocketTest, LoadCertFromMemory) {
   auto ctx = std::make_shared<SSLContext>();
   ctx->loadPrivateKeyFromBufferPEM(key);
   ctx->loadCertificateFromBufferPEM(cert);
-  ctx->loadTrustedCertificates(testCA);
+  ctx->loadTrustedCertificates(kTestCA);
 
   ssl::SSLUniquePtr ssl(ctx->createSSL());
 
index 1aba12ed0580c9589dc22a723ffe724975e794b8..7965f8e451932cf6034cd52f98826fbf23ac6a27 100644 (file)
@@ -28,6 +28,7 @@
 #include <folly/io/async/AsyncTransport.h>
 #include <folly/io/async/EventBase.h>
 #include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/io/async/test/TestSSLServer.h>
 #include <folly/portability/GTest.h>
 #include <folly/portability/Sockets.h>
 #include <folly/portability/Unistd.h>
 
 namespace folly {
 
-enum StateEnum {
-  STATE_WAITING,
-  STATE_SUCCEEDED,
-  STATE_FAILED
-};
-
 // The destructors of all callback classes assert that the state is
 // STATE_SUCCEEDED, for both possitive and negative tests. The tests
 // are responsible for setting the succeeded state properly before the
@@ -375,57 +370,6 @@ public:
   std::string errorString_;
 };
 
-class SSLServerAcceptCallbackBase:
-public folly::AsyncServerSocket::AcceptCallback {
-public:
-  explicit SSLServerAcceptCallbackBase(HandshakeCallback *hcb):
-  state(STATE_WAITING), hcb_(hcb) {}
-
-  ~SSLServerAcceptCallbackBase() {
-    EXPECT_EQ(STATE_SUCCEEDED, state);
-  }
-
-  void acceptError(const std::exception& ex) noexcept override {
-    std::cerr << "SSLServerAcceptCallbackBase::acceptError "
-              << ex.what() << std::endl;
-    state = STATE_FAILED;
-  }
-
-  void connectionAccepted(
-      int fd, const folly::SocketAddress& /* clientAddr */) noexcept override {
-    if (socket_) {
-      socket_->detachEventBase();
-    }
-    printf("Connection accepted\n");
-    try {
-      // Create a AsyncSSLSocket object with the fd. The socket should be
-      // added to the event base and in the state of accepting SSL connection.
-      socket_ = AsyncSSLSocket::newSocket(ctx_, base_, fd);
-    } catch (const std::exception &e) {
-      LOG(ERROR) << "Exception %s caught while creating a AsyncSSLSocket "
-        "object with socket " << e.what() << fd;
-      ::close(fd);
-      acceptError(e);
-      return;
-    }
-
-    connAccepted(socket_);
-  }
-
-  virtual void connAccepted(
-    const std::shared_ptr<folly::AsyncSSLSocket> &s) = 0;
-
-  void detach() {
-    socket_->detachEventBase();
-  }
-
-  StateEnum state;
-  HandshakeCallback *hcb_;
-  std::shared_ptr<folly::SSLContext> ctx_;
-  std::shared_ptr<AsyncSSLSocket> socket_;
-  folly::EventBase* base_;
-};
-
 class SSLServerAcceptCallback: public SSLServerAcceptCallbackBase {
 public:
   uint32_t timeout_;
@@ -614,46 +558,6 @@ class ConnectTimeoutCallback : public SSLServerAcceptCallbackBase {
   }
 };
 
-class TestSSLServer {
- protected:
-  EventBase evb_;
-  std::shared_ptr<folly::SSLContext> ctx_;
-  SSLServerAcceptCallbackBase *acb_;
-  std::shared_ptr<folly::AsyncServerSocket> socket_;
-  folly::SocketAddress address_;
-  pthread_t thread_;
-
-  static void *Main(void *ctx) {
-    TestSSLServer *self = static_cast<TestSSLServer*>(ctx);
-    self->evb_.loop();
-    self->acb_->detach();
-    std::cerr << "Server thread exited event loop" << std::endl;
-    return nullptr;
-  }
-
- public:
-  // Create a TestSSLServer.
-  // This immediately starts listening on the given port.
-  explicit TestSSLServer(
-      SSLServerAcceptCallbackBase* acb,
-      bool enableTFO = false);
-
-  // Kill the thread.
-  ~TestSSLServer() {
-    evb_.runInEventBaseThread([&](){
-      socket_->stopAccepting();
-    });
-    std::cerr << "Waiting for server thread to exit" << std::endl;
-    pthread_join(thread_, nullptr);
-  }
-
-  EventBase &getEventBase() { return evb_; }
-
-  const folly::SocketAddress& getAddress() const {
-    return address_;
-  }
-};
-
 class TestSSLAsyncCacheServer : public TestSSLServer {
  public:
   explicit TestSSLAsyncCacheServer(SSLServerAcceptCallbackBase *acb,
index be0bfd643d8b657725184baa89239fc8f463e778..77ab567f3c771524edb4448a9eac444a480212c3 100644 (file)
@@ -25,10 +25,6 @@ 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);
@@ -52,8 +48,8 @@ void getctx(
   clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
 
   serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-  serverCtx->loadCertificate(testCert);
-  serverCtx->loadPrivateKey(testKey);
+  serverCtx->loadCertificate(kTestCert);
+  serverCtx->loadPrivateKey(kTestKey);
 }
 
 class SSLSessionTest : public testing::Test {
diff --git a/folly/io/async/test/TestSSLServer.cpp b/folly/io/async/test/TestSSLServer.cpp
new file mode 100644 (file)
index 0000000..dabb1d5
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 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/TestSSLServer.h>
+
+namespace folly {
+
+const char* kTestCert = "folly/io/async/test/certs/tests-cert.pem";
+const char* kTestKey = "folly/io/async/test/certs/tests-key.pem";
+const char* kTestCA = "folly/io/async/test/certs/ca-cert.pem";
+
+TestSSLServer::TestSSLServer(SSLServerAcceptCallbackBase* acb, bool enableTFO)
+    : ctx_(new SSLContext),
+      acb_(acb),
+      socket_(AsyncServerSocket::newSocket(&evb_)) {
+  // Set up the SSL context
+  ctx_->loadCertificate(kTestCert);
+  ctx_->loadPrivateKey(kTestKey);
+  ctx_->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+  acb_->ctx_ = ctx_;
+  acb_->base_ = &evb_;
+
+  // Enable TFO
+  if (enableTFO) {
+    LOG(INFO) << "server TFO enabled";
+    socket_->setTFOEnabled(true, 1000);
+  }
+
+  // set up the listening socket
+  socket_->bind(0);
+  socket_->getAddress(&address_);
+  socket_->listen(100);
+  socket_->addAcceptCallback(acb_, &evb_);
+  socket_->startAccepting();
+
+  thread_ = std::thread([&] { Main(); });
+  LOG(INFO) << "Accepting connections on " << address_;
+}
+
+TestSSLServer::~TestSSLServer() {
+  if (thread_.joinable()) {
+    evb_.runInEventBaseThread([&]() { socket_->stopAccepting(); });
+    LOG(INFO) << "Waiting for server thread to exit";
+    thread_.join();
+  }
+}
+}
diff --git a/folly/io/async/test/TestSSLServer.h b/folly/io/async/test/TestSSLServer.h
new file mode 100644 (file)
index 0000000..a710d37
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 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/SocketAddress.h>
+#include <folly/experimental/TestUtil.h>
+#include <folly/io/async/AsyncSSLSocket.h>
+#include <folly/io/async/AsyncServerSocket.h>
+#include <folly/io/async/AsyncSocket.h>
+#include <folly/io/async/AsyncTimeout.h>
+#include <folly/io/async/AsyncTransport.h>
+#include <folly/io/async/EventBase.h>
+#include <folly/io/async/ssl/SSLErrors.h>
+#include <folly/portability/GTest.h>
+#include <folly/portability/Sockets.h>
+#include <folly/portability/Unistd.h>
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <list>
+
+namespace folly {
+
+extern const char* kTestCert;
+extern const char* kTestKey;
+extern const char* kTestCA;
+
+enum StateEnum { STATE_WAITING, STATE_SUCCEEDED, STATE_FAILED };
+
+class HandshakeCallback;
+
+class SSLServerAcceptCallbackBase : public AsyncServerSocket::AcceptCallback {
+ public:
+  explicit SSLServerAcceptCallbackBase(HandshakeCallback* hcb)
+      : state(STATE_WAITING), hcb_(hcb) {}
+
+  ~SSLServerAcceptCallbackBase() {
+    EXPECT_EQ(STATE_SUCCEEDED, state);
+  }
+
+  void acceptError(const std::exception& ex) noexcept override {
+    LOG(WARNING) << "SSLServerAcceptCallbackBase::acceptError " << ex.what();
+    state = STATE_FAILED;
+  }
+
+  void connectionAccepted(
+      int fd,
+      const SocketAddress& /* clientAddr */) noexcept override {
+    if (socket_) {
+      socket_->detachEventBase();
+    }
+    LOG(INFO) << "Connection accepted";
+    try {
+      // Create a AsyncSSLSocket object with the fd. The socket should be
+      // added to the event base and in the state of accepting SSL connection.
+      socket_ = AsyncSSLSocket::newSocket(ctx_, base_, fd);
+    } catch (const std::exception& e) {
+      LOG(ERROR) << "Exception %s caught while creating a AsyncSSLSocket "
+                    "object with socket "
+                 << e.what() << fd;
+      ::close(fd);
+      acceptError(e);
+      return;
+    }
+
+    connAccepted(socket_);
+  }
+
+  virtual void connAccepted(const std::shared_ptr<AsyncSSLSocket>& s) = 0;
+
+  void detach() {
+    socket_->detachEventBase();
+  }
+
+  StateEnum state;
+  HandshakeCallback* hcb_;
+  std::shared_ptr<SSLContext> ctx_;
+  std::shared_ptr<AsyncSSLSocket> socket_;
+  EventBase* base_;
+};
+
+class TestSSLServer {
+ public:
+  // Create a TestSSLServer.
+  // This immediately starts listening on the given port.
+  explicit TestSSLServer(
+      SSLServerAcceptCallbackBase* acb,
+      bool enableTFO = false);
+
+  // Kills the thread.
+  virtual ~TestSSLServer();
+
+  EventBase& getEventBase() {
+    return evb_;
+  }
+
+  const SocketAddress& getAddress() const {
+    return address_;
+  }
+
+ protected:
+  void Main() {
+    evb_.loop();
+    acb_->detach();
+    LOG(INFO) << "Server thread exited event loop";
+  }
+
+  EventBase evb_;
+  std::shared_ptr<SSLContext> ctx_;
+  SSLServerAcceptCallbackBase* acb_;
+  std::shared_ptr<AsyncServerSocket> socket_;
+  SocketAddress address_;
+  std::thread thread_;
+};
+}