Commit 52a8633f authored by Andrew Huang's avatar Andrew Huang Committed by Facebook GitHub Bot

Implement abstracted SSL session in folly

Summary: Repurpose the existing `folly::ssl::SSLSession` to be an abstraction for SSL sessions, with the internal implementation hidden. The intention is to soon move to using these abstracted sessions instead of directly using OpenSSL sessions.

Reviewed By: yfeldblum, mingtaoy

Differential Revision: D20143848

fbshipit-source-id: 01e175668c0afcf8b91d19a289368496717d3150
parent 0e29494c
......@@ -36,6 +36,8 @@
#include <folly/io/async/ssl/BasicTransportCertificate.h>
#include <folly/lang/Bits.h>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/SSLSession.h>
#include <folly/ssl/detail/OpenSSLSession.h>
using std::shared_ptr;
......@@ -312,6 +314,7 @@ void AsyncSSLSocket::init() {
(void)sslBioMethodInitializer;
setup_SSL_CTX(ctx_->getSSLCtx());
sslSessionV2_ = std::make_shared<ssl::detail::OpenSSLSession>();
}
void AsyncSSLSocket::closeNow() {
......@@ -861,6 +864,10 @@ SSL_SESSION* AsyncSSLSocket::getSSLSession() {
return sslSession_;
}
std::shared_ptr<ssl::SSLSession> AsyncSSLSocket::getSSLSessionV2() {
return sslSessionV2_;
}
const SSL* AsyncSSLSocket::getSSL() const {
return ssl_.get();
}
......
......@@ -35,6 +35,7 @@
#include <folly/portability/OpenSSL.h>
#include <folly/portability/Sockets.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <folly/ssl/SSLSession.h>
namespace folly {
......@@ -462,6 +463,13 @@ class AsyncSSLSocket : public virtual AsyncSocket {
*/
SSL_SESSION* getSSLSession();
/**
* Currently unsupported. Eventually intended to replace getSSLSession()
* once TLS 1.3 is enabled by default.
* Get an abstracted SSL Session.
*/
std::shared_ptr<ssl::SSLSession> getSSLSessionV2();
/**
* Get a handle to the SSL struct.
*/
......@@ -972,6 +980,9 @@ class AsyncSSLSocket : public virtual AsyncSocket {
std::unique_ptr<ReadCallback> asyncOperationFinishCallback_;
// Whether this socket is currently waiting on SSL_accept
bool waitingOnAccept_{false};
// Unsupported. Currently used for getSSLSessionV2().
std::shared_ptr<ssl::SSLSession> sslSessionV2_{nullptr};
};
} // namespace folly
......@@ -19,14 +19,30 @@
#include <folly/net/NetOps.h>
#include <folly/net/NetworkSocket.h>
#include <folly/portability/GTest.h>
#include <folly/portability/OpenSSL.h>
#include <folly/portability/Sockets.h>
#include <folly/ssl/detail/OpenSSLSession.h>
#include <memory>
using folly::ssl::SSLSession;
using folly::ssl::detail::OpenSSLSession;
namespace folly {
class SimpleCallbackManager {
public:
static int sessionCallback(SSL* ssl, SSL_SESSION* session) {
auto sessionPtr = folly::ssl::SSLSessionUniquePtr(session);
auto socket = folly::AsyncSSLSocket::getFromSSL(ssl);
auto sslSession =
std::dynamic_pointer_cast<folly::ssl::detail::OpenSSLSession>(
socket->getSSLSessionV2());
sslSession->setActiveSession(std::move(sessionPtr));
return 1;
}
};
void getfds(NetworkSocket fds[2]) {
if (netops::socketpair(PF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
FAIL() << "failed to create socketpair: " << errnoStr(errno);
......@@ -44,6 +60,13 @@ void getctx(
std::shared_ptr<folly::SSLContext> serverCtx) {
clientCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
clientCtx->loadTrustedCertificates(kTestCA);
auto clientCtx_ = clientCtx->getSSLCtx();
// The following two actions (setting session cache mode and
// setting the session callback) will eventually be done in
// SSLContext instead
SSL_CTX_set_session_cache_mode(clientCtx_, SSL_SESS_CACHE_CLIENT);
SSL_CTX_sess_set_new_cb(clientCtx_, SimpleCallbackManager::sessionCallback);
serverCtx->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
serverCtx->loadCertificate(kTestCert);
......@@ -71,99 +94,8 @@ class SSLSessionTest : public testing::Test {
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;
{
NetworkSocket 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 = std::make_unique<SSLSession>(clientPtr->getSSLSession());
ASSERT_NE(sess.get(), nullptr);
}
{
NetworkSocket 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;
{
NetworkSocket 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 =
std::make_unique<SSLSession>(clientPtr->getSSLSession());
sessiondata = sess->serialize();
ASSERT_TRUE(!sessiondata.empty());
}
{
NetworkSocket fds[2];
getfds(fds);
AsyncSSLSocket::UniquePtr clientSock(
new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
auto clientPtr = clientSock.get();
std::unique_ptr<SSLSession> sess =
std::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) {
std::shared_ptr<SSLSession> sslSession;
NetworkSocket fds[2];
getfds(fds);
AsyncSSLSocket::UniquePtr clientSock(
......@@ -178,76 +110,15 @@ TEST_F(SSLSessionTest, GetSessionID) {
eventBase.loop();
ASSERT_TRUE(client.handshakeSuccess_);
std::unique_ptr<SSLSession> sess =
std::make_unique<SSLSession>(clientPtr->getSSLSession());
ASSERT_NE(sess, nullptr);
auto sessID = sess->getSessionID();
ASSERT_GE(sessID.length(), 0);
}
TEST_F(SSLSessionTest, ServerAuthWithPeerName) {
NetworkSocket fds[2];
getfds(fds);
clientCtx->authenticate(true, true, std::string(kTestCertCN));
clientCtx->setVerificationOption(
folly::SSLContext::SSLVerifyPeerEnum::VERIFY);
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), true, true);
SSLHandshakeServerParseClientHello server(
std::move(serverSock), false, false);
sslSession = clientPtr->getSSLSessionV2();
ASSERT_NE(sslSession, nullptr);
eventBase.loop();
ASSERT_TRUE(client.handshakeVerify_);
ASSERT_TRUE(client.handshakeSuccess_);
ASSERT_FALSE(client.handshakeError_);
std::unique_ptr<SSLSession> sess =
std::make_unique<SSLSession>(clientPtr->getSSLSession());
ASSERT_NE(sess, nullptr);
// The underlying SSL_SESSION is set in the session callback
// that is attached to the SSL_CTX. The session is guaranteed to
// be resumable here in TLS 1.2, but not in TLS 1.3
auto opensslSession = std::dynamic_pointer_cast<OpenSSLSession>(sslSession);
auto sessionPtr = opensslSession->getActiveSession();
ASSERT_NE(sessionPtr.get(), nullptr);
}
TEST_F(SSLSessionTest, ServerAuth_MismatchedPeerName) {
NetworkSocket fds[2];
getfds(fds);
clientCtx->authenticate(true, true, "foo");
clientCtx->setVerificationOption(
folly::SSLContext::SSLVerifyPeerEnum::VERIFY);
AsyncSSLSocket::UniquePtr clientSock(
new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
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.handshakeVerify_);
ASSERT_FALSE(client.handshakeSuccess_);
ASSERT_TRUE(client.handshakeError_);
}
TEST_F(SSLSessionTest, ServerAuth_MismatchedServerName) {
NetworkSocket fds[2];
getfds(fds);
clientCtx->authenticate(true, true);
clientCtx->setVerificationOption(
folly::SSLContext::SSLVerifyPeerEnum::VERIFY);
AsyncSSLSocket::UniquePtr clientSock(
new AsyncSSLSocket(clientCtx, &eventBase, fds[0], serverName));
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.handshakeVerify_);
ASSERT_FALSE(client.handshakeSuccess_);
ASSERT_TRUE(client.handshakeError_);
}
} // namespace folly
......@@ -16,49 +16,24 @@
#pragma once
#include <folly/Memory.h>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/detail/SSLSessionImpl.h>
namespace folly {
namespace ssl {
/**
* An abstraction for SSL sessions.
*
* SSLSession allows users to store and pass SSL sessions (e.g for
* resumption) while abstracting away implementation-specific
* details.
*
* SSLSession is intended to be used by deriving a new class
* which handles the implementation details, and downcasting
* whenever access is needed to the underlying implementation.
*/
class SSLSession {
public:
// Holds and takes ownership of an SSL_SESSION object by incrementing refcount
explicit SSLSession(SSL_SESSION* session, bool takeOwnership = true)
: impl_(
std::make_unique<detail::SSLSessionImpl>(session, takeOwnership)) {}
// Deserialize from a string
explicit SSLSession(const std::string& serializedSession)
: impl_(std::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_;
virtual ~SSLSession() = default;
};
} // namespace ssl
......
......@@ -14,34 +14,36 @@
* limitations under the License.
*/
#pragma once
#include <folly/Range.h>
#include <folly/ssl/detail/OpenSSLSession.h>
#include <folly/portability/OpenSSL.h>
#include <string>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <atomic>
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};
};
OpenSSLSession::~OpenSSLSession() {
SSL_SESSION* session = activeSession_.load();
if (session) {
SSL_SESSION_free(session);
}
}
void OpenSSLSession::setActiveSession(SSLSessionUniquePtr s) {
SSL_SESSION* oldSession = activeSession_.exchange(s.release());
if (oldSession) {
SSL_SESSION_free(oldSession);
}
}
SSLSessionUniquePtr OpenSSLSession::getActiveSession() {
SSL_SESSION* session = activeSession_.load();
if (session) {
SSL_SESSION_up_ref(session);
}
return SSLSessionUniquePtr(session);
}
} // namespace detail
} // namespace ssl
......
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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/portability/OpenSSL.h>
#include <folly/ssl/OpenSSLPtrTypes.h>
#include <folly/ssl/SSLSession.h>
#include <atomic>
namespace folly {
namespace ssl {
namespace detail {
/**
* Internal implementation detail.
*
* This class should generally not be
* directly, but instead downcast from SSLSession only when
* access is needed to OpenSSL's SSL_SESSION (e.g during resumption).
*
* See also SSLSession.h for more information.
*/
class OpenSSLSession : public SSLSession {
public:
~OpenSSLSession();
/**
* Set the underlying SSL session. Any previously held session
* will have its reference count decremented by 1.
*/
void setActiveSession(SSLSessionUniquePtr s);
/*
* Get the underlying SSL session.
*/
SSLSessionUniquePtr getActiveSession();
private:
std::atomic<SSL_SESSION*> activeSession_;
};
} // namespace detail
} // namespace ssl
} // namespace folly
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* 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 <glog/logging.h>
#include <folly/portability/OpenSSL.h>
#include <folly/ssl/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());
auto longLen = long(serializedSession.length());
if ((session_ = d2i_SSL_SESSION(nullptr, &sessionData, longLen)) == nullptr) {
throw std::runtime_error("Cannot deserialize SSLSession string");
}
}
SSLSessionImpl::~SSLSessionImpl() {
downRef();
}
std::string SSLSessionImpl::serialize() 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[size_t(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;
ptr = SSL_SESSION_get_id(session_, &len);
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_) {
SSL_SESSION_up_ref(session_);
}
}
void SSLSessionImpl::downRef() {
if (session_) {
SSL_SESSION_free(session_);
}
}
} // namespace detail
} // namespace ssl
} // namespace folly
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