From: Anirudh Ramachandran Date: Mon, 26 Sep 2016 21:55:14 +0000 (-0700) Subject: SSL_SESSION wrapper X-Git-Tag: v2016.10.03.00~14 X-Git-Url: http://plrg.eecs.uci.edu/git/?p=folly.git;a=commitdiff_plain;h=4450b4acfcc29ebce843c89fa7cd5c9c80b7eee1 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 --- diff --git a/folly/Makefile.am b/folly/Makefile.am index d1cdf4bd..1d204e01 100644 --- a/folly/Makefile.am +++ b/folly/Makefile.am @@ -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 index 00000000..ce7084c3 --- /dev/null +++ b/folly/io/async/test/SSLSessionTest.cpp @@ -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 +#include +#include +#include +#include + +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 clientCtx, + std::shared_ptr 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 clientCtx; + std::shared_ptr dfServerCtx; + // Use the same SSLContext to continue the handshake after + // tlsext_hostname match. + std::shared_ptr 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 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 sess = + folly::make_unique(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 sess = + folly::make_unique(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 sess = + folly::make_unique(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 index 00000000..e7424abb --- /dev/null +++ b/folly/ssl/SSLSession.h @@ -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 +#include +#include + +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( + session, + takeOwnership)) {} + + // Deserialize from a string + explicit SSLSession(const std::string& serializedSession) + : impl_(folly::make_unique(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 impl_; +}; + +} // namespace ssl +} // namespace folly diff --git a/folly/ssl/detail/OpenSSLVersionFinder.h b/folly/ssl/detail/OpenSSLVersionFinder.h new file mode 100644 index 00000000..8ff6cdd9 --- /dev/null +++ b/folly/ssl/detail/OpenSSLVersionFinder.h @@ -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 + +#include +#include + +#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 index 00000000..7a83c1e7 --- /dev/null +++ b/folly/ssl/detail/SSLSessionImpl.cpp @@ -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 +#include + +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(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 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(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 index 00000000..0adfa134 --- /dev/null +++ b/folly/ssl/detail/SSLSessionImpl.h @@ -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 +#include +#include + +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