blob: 32f2378d38b2b3a546a81b37574828745ece6364 [file] [log] [blame]
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +02001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#define BOOST_TEST_MODULE SecurityFromBufferTest
21#include <boost/filesystem.hpp>
22#include <boost/foreach.hpp>
23#include <boost/format.hpp>
24#include <boost/test/unit_test.hpp>
25#include <boost/thread.hpp>
26#include <stdexcept>
27#include <fstream>
28#include <memory>
Tobias Mayer0d95b8c2023-03-10 09:02:38 +010029#include <openssl/opensslv.h>
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +020030#include <thrift/transport/TSSLServerSocket.h>
31#include <thrift/transport/TSSLSocket.h>
32#include <thrift/transport/TTransport.h>
33#include <vector>
Mario Emmenlauer29b083e2018-07-05 14:09:27 +020034#ifdef HAVE_SIGNAL_H
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +020035#include <signal.h>
36#endif
37
38using apache::thrift::transport::TServerTransport;
39using apache::thrift::transport::TSSLServerSocket;
40using apache::thrift::transport::TSSLSocket;
41using apache::thrift::transport::TSSLSocketFactory;
42using apache::thrift::transport::TTransport;
43using apache::thrift::transport::TTransportException;
44using apache::thrift::transport::TTransportFactory;
45
46using std::bind;
47using std::shared_ptr;
48
49boost::filesystem::path keyDir;
50boost::filesystem::path certFile(const std::string& filename) {
51 return keyDir / filename;
52}
53std::string certString(const std::string& filename) {
54 std::ifstream ifs(certFile(filename).string());
55 if(!ifs.is_open() || !ifs.good()) {
56 throw(std::runtime_error("Failed to open key file " + filename + " for reading"));
57 }
58 std::stringstream buffer;
59 buffer << ifs.rdbuf();
60 return buffer.str();
61}
62boost::mutex gMutex;
63
64struct GlobalFixture {
65 GlobalFixture() {
66 using namespace boost::unit_test::framework;
67 for (int i = 0; i < master_test_suite().argc; ++i) {
68 BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
69 }
70
71#ifdef __linux__
72 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
73 // disconnected can cause a SIGPIPE signal...
74 signal(SIGPIPE, SIG_IGN);
75#endif
76
77 TSSLSocketFactory::setManualOpenSSLInitialization(true);
78 apache::thrift::transport::initializeOpenSSL();
79
80 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
81 if (!boost::filesystem::exists(certFile("server.crt"))) {
82 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
83 if (!boost::filesystem::exists(certFile("server.crt"))) {
84 throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
85 }
86 }
87 }
88
89 virtual ~GlobalFixture() {
90 apache::thrift::transport::cleanupOpenSSL();
91#ifdef __linux__
92 signal(SIGPIPE, SIG_DFL);
93#endif
94 }
95};
96
97#if (BOOST_VERSION >= 105900)
98BOOST_GLOBAL_FIXTURE(GlobalFixture);
99#else
100BOOST_GLOBAL_FIXTURE(GlobalFixture)
101#endif
102
103struct SecurityFromBufferFixture {
104 void server(apache::thrift::transport::SSLProtocol protocol) {
105 try {
106 boost::mutex::scoped_lock lock(mMutex);
107
108 shared_ptr<TSSLSocketFactory> pServerSocketFactory;
109 shared_ptr<TSSLServerSocket> pServerSocket;
110
111 pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
Thomas3ea763e2024-03-05 21:30:29 +0900112 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
Thomas8148f2f2024-02-26 21:45:05 +0900113 // OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
114 // to @SECLEVEL=0 or 1, so specify it to test all combinations.
115 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0");
116 #else
117 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
118 #endif
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +0200119 pServerSocketFactory->loadCertificateFromBuffer(certString("server.crt").c_str());
120 pServerSocketFactory->loadPrivateKeyFromBuffer(certString("server.key").c_str());
121 pServerSocketFactory->server(true);
122 pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
123 shared_ptr<TTransport> connectedClient;
124
125 try {
126 pServerSocket->listen();
127 mPort = pServerSocket->getPort();
128 mCVar.notify_one();
129 lock.unlock();
130
131 connectedClient = pServerSocket->accept();
132 uint8_t buf[2];
133 buf[0] = 'O';
134 buf[1] = 'K';
135 connectedClient->write(&buf[0], 2);
136 connectedClient->flush();
137 }
138
139 catch (apache::thrift::transport::TTransportException& ex) {
140 boost::mutex::scoped_lock lock(gMutex);
141 BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
142 }
143
144 if (connectedClient) {
145 connectedClient->close();
146 connectedClient.reset();
147 }
148
149 pServerSocket->close();
150 pServerSocket.reset();
151 } catch (std::exception& ex) {
152 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
153 }
154 }
155
156 void client(apache::thrift::transport::SSLProtocol protocol) {
157 try {
158 shared_ptr<TSSLSocketFactory> pClientSocketFactory;
159 shared_ptr<TSSLSocket> pClientSocket;
160
161 try {
162 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
163 pClientSocketFactory->authenticate(true);
Thomas3ea763e2024-03-05 21:30:29 +0900164 #if OPENSSL_VERSION_NUMBER >= 0x10100000L
Thomas8148f2f2024-02-26 21:45:05 +0900165 // OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
166 // to @SECLEVEL=0 or 1, so specify it to test all combinations.
167 pClientSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0");
168 #endif
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +0200169 pClientSocketFactory->loadCertificateFromBuffer(certString("client.crt").c_str());
170 pClientSocketFactory->loadPrivateKeyFromBuffer(certString("client.key").c_str());
171 pClientSocketFactory->loadTrustedCertificatesFromBuffer(certString("CA.pem").c_str());
172 pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
173 pClientSocket->open();
174
175 uint8_t buf[3];
176 buf[0] = 0;
177 buf[1] = 0;
178 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
179 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
180 mConnected = true;
181 } catch (apache::thrift::transport::TTransportException& ex) {
182 boost::mutex::scoped_lock lock(gMutex);
183 BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
184 }
185
186 if (pClientSocket) {
187 pClientSocket->close();
188 pClientSocket.reset();
189 }
190 } catch (std::exception& ex) {
191 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
192 }
193 }
194
195 static const char* protocol2str(size_t protocol) {
196 static const char* strings[apache::thrift::transport::LATEST + 1]
197 = {"SSLTLS", "SSLv2", "SSLv3", "TLSv1_0", "TLSv1_1", "TLSv1_2"};
198 return strings[protocol];
199 }
200
201 boost::mutex mMutex;
202 boost::condition_variable mCVar;
203 int mPort;
204 bool mConnected;
205};
206
207BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFromBufferFixture)
208
209BOOST_AUTO_TEST_CASE(ssl_security_matrix) {
210 try {
211 // matrix of connection success between client and server with different SSLProtocol selections
212 static_assert(apache::thrift::transport::LATEST == 5, "Mismatch in assumed number of ssl protocols");
213 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
214 {
215 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
216 // client
Thomas3ea763e2024-03-05 21:30:29 +0900217 /* SSLTLS */ { true, false, false, true, true, true },
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +0200218 /* SSLv2 */ { false, false, false, false, false, false },
219 /* SSLv3 */ { false, false, true, false, false, false },
Thomas3ea763e2024-03-05 21:30:29 +0900220 /* TLSv1_0 */ { true, false, false, true, false, false },
221 /* TLSv1_1 */ { true, false, false, false, true, false },
Mario Emmenlauer0f14e2f2019-10-15 11:25:10 +0200222 /* TLSv1_2 */ { true, false, false, false, false, true }
223 };
224
225 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si) {
226 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci) {
227 if (si == 1 || ci == 1) {
228 // Skip all SSLv2 cases - protocol not supported
229 continue;
230 }
231
232#ifdef OPENSSL_NO_SSL3
233 if (si == 2 || ci == 2) {
234 // Skip all SSLv3 cases - protocol not supported
235 continue;
236 }
237#endif
238
239 boost::mutex::scoped_lock lock(mMutex);
240
241 BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%") % protocol2str(si)
242 % protocol2str(ci));
243
244 mConnected = false;
245 // thread_group manages the thread lifetime - ignore the return value of create_thread
246 boost::thread_group threads;
247 (void)threads.create_thread(bind(&SecurityFromBufferFixture::server, this,
248 static_cast<apache::thrift::transport::SSLProtocol>(si)));
249 mCVar.wait(lock); // wait for listen() to succeed
250 lock.unlock();
251 (void)threads.create_thread(bind(&SecurityFromBufferFixture::client, this,
252 static_cast<apache::thrift::transport::SSLProtocol>(ci)));
253 threads.join_all();
254
255 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
256 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
257 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
258 }
259 }
260 } catch (std::exception& ex) {
261 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
262 }
263}
264
265BOOST_AUTO_TEST_SUITE_END()