mirror of
https://github.com/apache/thrift.git
synced 2026-10-29 08:55:16 +08:00
Client: cpp,c_glib OpenSSL 4.0 removes SSLv3_method(), TLSv1_method(), TLSv1_1_method(), TLSv1_2_method(), ERR_remove_state() and ASN1_STRING_data(), and returns const pointers from X509_get_subject_name(), X509_NAME_get_entry() and X509_NAME_ENTRY_get_data(). Neither binding compiles against it. The per-version methods are replaced by TLS_method() with the protocol window pinned through SSL_CTX_set_min_proto_version() and SSL_CTX_set_max_proto_version(), which is OpenSSL's own documented migration and reproduces the old methods exactly: TLSv1_2_method() meant TLS 1.2 only, and min == max == TLS1_2_VERSION means the same. No enum value is dropped, so SSLv3, TLSv1_0, TLSv1_1, TLSv1_2 and LATEST keep working on 4.0 rather than starting to throw "Unknown protocol". The replacement is selected from 1.1.0 onwards rather than only on 4.0, because no CI configuration builds against 4.0: gating it there would ship a code path that nothing ever compiles, let alone runs. At >= 1.1.0 the existing SSL matrix tests cover it on the OpenSSL 3.x that CI does have. The same reasoning applies to the const qualifiers, whose consumers have all taken const since 1.1.0. LibreSSL is excluded throughout: it reports OPENSSL_VERSION_NUMBER as 0x20000000L but keeps the pre-1.1.0 signatures, so it stays on the path it compiles today. SSLTLS is untouched. SSLv23_method() is not removed in 4.0 -- it is an unconditional #define for TLS_method() -- so that arm needed no change, and the protocol floor for it still comes from the SSL_CTX_set_options() call below, not from a version window. SSLv3 keeps SSLv3_method() below 4.0, which is the only release that removes it, rather than becoming a TLS_method() window like the others: SSL_CTX_set_min_proto_version() consults the security level and would refuse SSL3_VERSION at the default one, where the method function does not, and the context is built before TSSLSocketFactory::ciphers() lowers it. On 4.0 the protocol is gone outright and the arm is compiled out. ERR_remove_state() has been an empty stub since 1.1.0, so in c_glib it is gated to where it still does something: before 1.1.0, and on LibreSSL, which implements it and has never shipped OPENSSL_thread_stop(). The C++ binding gets the matching guard so LibreSSL reaches the ERR_remove_state() arm instead of losing per-thread cleanup on both paths. Tests: - SecurityTest gains explicit_protocol_version_window, which asserts the min/max window per enum value instead of merely that a context was created -- the latter passes on a remapping that silently leaves the floor to the library default. Proven two-state: with the window not pinned it fails 8 assertions, LATEST among them, reporting floor 0. It also asserts that an unavailable SSLv3 is reported rather than quietly satisfied with another version. - Both matrix tests skip SSLv3 on 4.0. OPENSSL_NO_SSL3 is not defined there, so without this they try SSLv3 and time out. - testtransportsslsocket gains the same two cases for c_glib. - TSSLSocketMatchNameTest built its subject name through X509_get_subject_name(), which is const as of 4.0, then mutated it; it now builds a separate X509_NAME. It also had no SIGPIPE handler while deliberately provoking rejected handshakes, so it died with status 141 in 6 of 20 runs before this change and 11 of 20 after -- the allocation pattern shifts the race. It is ignored now, as SecurityTest already does, and 25 of 25 runs pass. Verified by building and running lib/cpp and lib/c_glib against three libraries: the distribution OpenSSL 3.0.2, a from-source 4.0.2, and a from-source 3.0.2 configured with enable-ssl3 so that the one otherwise uncompiled arm is covered. All 16 matrix cells pass identically on the first two, and all 25 on the third -- cell for cell what master produces there. Unpatched master fails to compile against 4.0.2 with 7 errors in C++ and 5 in c_glib. Co-Authored-By: Sebastian Andrzej Siewior <sebastian@breakpoint.cc> Co-Authored-By: Jaipaul Cheernam <jaipaul.cheernam@est.tech> Co-Authored-By: Brad Smith <brad@comstyle.com> Co-Authored-By: loqs <7595223+loqs@users.noreply.github.com> Co-Authored-By: Finn R. Gärtner <65015656+FinnRG@users.noreply.github.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
266 lines
10 KiB
C++
266 lines
10 KiB
C++
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you 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.
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE SecurityFromBufferTest
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/format.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <boost/thread.hpp>
|
|
#include <stdexcept>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <openssl/opensslv.h>
|
|
#include <thrift/transport/TSSLServerSocket.h>
|
|
#include <thrift/transport/TSSLSocket.h>
|
|
#include <thrift/transport/TTransport.h>
|
|
#include <vector>
|
|
#ifdef HAVE_SIGNAL_H
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
using apache::thrift::transport::TServerTransport;
|
|
using apache::thrift::transport::TSSLServerSocket;
|
|
using apache::thrift::transport::TSSLSocket;
|
|
using apache::thrift::transport::TSSLSocketFactory;
|
|
using apache::thrift::transport::TTransport;
|
|
using apache::thrift::transport::TTransportException;
|
|
using apache::thrift::transport::TTransportFactory;
|
|
|
|
using std::bind;
|
|
using std::shared_ptr;
|
|
|
|
boost::filesystem::path keyDir;
|
|
boost::filesystem::path certFile(const std::string& filename) {
|
|
return keyDir / filename;
|
|
}
|
|
std::string certString(const std::string& filename) {
|
|
std::ifstream ifs(certFile(filename).string());
|
|
if(!ifs.is_open() || !ifs.good()) {
|
|
throw(std::runtime_error("Failed to open key file " + filename + " for reading"));
|
|
}
|
|
std::stringstream buffer;
|
|
buffer << ifs.rdbuf();
|
|
return buffer.str();
|
|
}
|
|
boost::mutex gMutex;
|
|
|
|
struct GlobalFixture {
|
|
GlobalFixture() {
|
|
using namespace boost::unit_test::framework;
|
|
for (int i = 0; i < master_test_suite().argc; ++i) {
|
|
BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
|
|
}
|
|
|
|
#ifdef __linux__
|
|
// OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
|
|
// disconnected can cause a SIGPIPE signal...
|
|
signal(SIGPIPE, SIG_IGN);
|
|
#endif
|
|
|
|
TSSLSocketFactory::setManualOpenSSLInitialization(true);
|
|
apache::thrift::transport::initializeOpenSSL();
|
|
|
|
keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
|
|
if (!boost::filesystem::exists(certFile("server.crt"))) {
|
|
keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
|
|
if (!boost::filesystem::exists(certFile("server.crt"))) {
|
|
throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual ~GlobalFixture() {
|
|
apache::thrift::transport::cleanupOpenSSL();
|
|
#ifdef __linux__
|
|
signal(SIGPIPE, SIG_DFL);
|
|
#endif
|
|
}
|
|
};
|
|
|
|
#if (BOOST_VERSION >= 105900)
|
|
BOOST_GLOBAL_FIXTURE(GlobalFixture);
|
|
#else
|
|
BOOST_GLOBAL_FIXTURE(GlobalFixture)
|
|
#endif
|
|
|
|
struct SecurityFromBufferFixture {
|
|
void server(apache::thrift::transport::SSLProtocol protocol) {
|
|
try {
|
|
boost::mutex::scoped_lock lock(mMutex);
|
|
|
|
shared_ptr<TSSLSocketFactory> pServerSocketFactory;
|
|
shared_ptr<TSSLServerSocket> pServerSocket;
|
|
|
|
pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
// OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
|
|
// to @SECLEVEL=0 or 1, so specify it to test all combinations.
|
|
pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0");
|
|
#else
|
|
pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
|
|
#endif
|
|
pServerSocketFactory->loadCertificateFromBuffer(certString("server.crt").c_str());
|
|
pServerSocketFactory->loadPrivateKeyFromBuffer(certString("server.key").c_str());
|
|
pServerSocketFactory->server(true);
|
|
pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
|
|
shared_ptr<TTransport> connectedClient;
|
|
|
|
try {
|
|
pServerSocket->listen();
|
|
mPort = pServerSocket->getPort();
|
|
mCVar.notify_one();
|
|
lock.unlock();
|
|
|
|
connectedClient = pServerSocket->accept();
|
|
uint8_t buf[2];
|
|
buf[0] = 'O';
|
|
buf[1] = 'K';
|
|
connectedClient->write(&buf[0], 2);
|
|
connectedClient->flush();
|
|
}
|
|
|
|
catch (apache::thrift::transport::TTransportException& ex) {
|
|
boost::mutex::scoped_lock lock(gMutex);
|
|
BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
|
|
}
|
|
|
|
if (connectedClient) {
|
|
connectedClient->close();
|
|
connectedClient.reset();
|
|
}
|
|
|
|
pServerSocket->close();
|
|
pServerSocket.reset();
|
|
} catch (std::exception& ex) {
|
|
BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
|
|
}
|
|
}
|
|
|
|
void client(apache::thrift::transport::SSLProtocol protocol) {
|
|
try {
|
|
shared_ptr<TSSLSocketFactory> pClientSocketFactory;
|
|
shared_ptr<TSSLSocket> pClientSocket;
|
|
|
|
try {
|
|
pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
|
|
pClientSocketFactory->authenticate(true);
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
|
|
// OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
|
|
// to @SECLEVEL=0 or 1, so specify it to test all combinations.
|
|
pClientSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0");
|
|
#endif
|
|
pClientSocketFactory->loadCertificateFromBuffer(certString("client.crt").c_str());
|
|
pClientSocketFactory->loadPrivateKeyFromBuffer(certString("client.key").c_str());
|
|
pClientSocketFactory->loadTrustedCertificatesFromBuffer(certString("CA.pem").c_str());
|
|
pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
|
|
pClientSocket->open();
|
|
|
|
uint8_t buf[3];
|
|
buf[0] = 0;
|
|
buf[1] = 0;
|
|
BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
|
|
BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
|
|
mConnected = true;
|
|
} catch (apache::thrift::transport::TTransportException& ex) {
|
|
boost::mutex::scoped_lock lock(gMutex);
|
|
BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
|
|
}
|
|
|
|
if (pClientSocket) {
|
|
pClientSocket->close();
|
|
pClientSocket.reset();
|
|
}
|
|
} catch (std::exception& ex) {
|
|
BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
|
|
}
|
|
}
|
|
|
|
static const char* protocol2str(size_t protocol) {
|
|
static const char* strings[apache::thrift::transport::LATEST + 1]
|
|
= {"SSLTLS", "SSLv2", "SSLv3", "TLSv1_0", "TLSv1_1", "TLSv1_2"};
|
|
return strings[protocol];
|
|
}
|
|
|
|
boost::mutex mMutex;
|
|
boost::condition_variable mCVar;
|
|
int mPort;
|
|
bool mConnected;
|
|
};
|
|
|
|
BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFromBufferFixture)
|
|
|
|
BOOST_AUTO_TEST_CASE(ssl_security_matrix) {
|
|
try {
|
|
// matrix of connection success between client and server with different SSLProtocol selections
|
|
static_assert(apache::thrift::transport::LATEST == 5, "Mismatch in assumed number of ssl protocols");
|
|
bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
|
|
{
|
|
// server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
|
|
// client
|
|
/* SSLTLS */ { true, false, false, false, false, true },
|
|
/* SSLv2 */ { false, false, false, false, false, false },
|
|
/* SSLv3 */ { false, false, true, false, false, false },
|
|
/* TLSv1_0 */ { false, false, false, true, false, false },
|
|
/* TLSv1_1 */ { false, false, false, false, true, false },
|
|
/* TLSv1_2 */ { true, false, false, false, false, true }
|
|
};
|
|
|
|
for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si) {
|
|
for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci) {
|
|
if (si == 1 || ci == 1) {
|
|
// Skip all SSLv2 cases - protocol not supported
|
|
continue;
|
|
}
|
|
|
|
#if defined(OPENSSL_NO_SSL3) || OPENSSL_VERSION_NUMBER >= 0x40000000L
|
|
if (si == 2 || ci == 2) {
|
|
// Skip all SSLv3 cases - protocol not supported
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
boost::mutex::scoped_lock lock(mMutex);
|
|
|
|
BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%") % protocol2str(si)
|
|
% protocol2str(ci));
|
|
|
|
mConnected = false;
|
|
// thread_group manages the thread lifetime - ignore the return value of create_thread
|
|
boost::thread_group threads;
|
|
(void)threads.create_thread(bind(&SecurityFromBufferFixture::server, this,
|
|
static_cast<apache::thrift::transport::SSLProtocol>(si)));
|
|
mCVar.wait(lock); // wait for listen() to succeed
|
|
lock.unlock();
|
|
(void)threads.create_thread(bind(&SecurityFromBufferFixture::client, this,
|
|
static_cast<apache::thrift::transport::SSLProtocol>(ci)));
|
|
threads.join_all();
|
|
|
|
BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
|
|
boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
|
|
% protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
|
|
}
|
|
}
|
|
} catch (std::exception& ex) {
|
|
BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|