Commit 4450b4ac authored by Anirudh Ramachandran's avatar Anirudh Ramachandran Committed by Facebook Github Bot 7

SSL_SESSION wrapper

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
parent 67dac89c
......@@ -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 \
......
/*
* 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);
}
}
/*
* 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
/*
* 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
}
};
/*
* 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
/*
* 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
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