blob: 4c6c732671f3d3a0edd1f12f5e5f62385d31b7e4 [file] [log] [blame]
Jim Kingb0b710a2015-07-28 13:31:27 -04001/*
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 SecurityTest
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020021#include <boost/test/unit_test.hpp>
Jim Kingb0b710a2015-07-28 13:31:27 -040022#include <boost/filesystem.hpp>
23#include <boost/foreach.hpp>
24#include <boost/format.hpp>
Jim Kingb0b710a2015-07-28 13:31:27 -040025#include <boost/thread.hpp>
cyy316723a2019-01-05 16:35:14 +080026#include <memory>
Tobias Mayer0d95b8c2023-03-10 09:02:38 +010027#include <openssl/opensslv.h>
Jim Kingb0b710a2015-07-28 13:31:27 -040028#include <thrift/transport/TSSLServerSocket.h>
29#include <thrift/transport/TSSLSocket.h>
30#include <thrift/transport/TTransport.h>
Jim Kingb0b710a2015-07-28 13:31:27 -040031#include <vector>
Mario Emmenlauer29b083e2018-07-05 14:09:27 +020032#ifdef HAVE_SIGNAL_H
Jim Kingb0b710a2015-07-28 13:31:27 -040033#include <signal.h>
34#endif
35
36using apache::thrift::transport::TSSLServerSocket;
37using apache::thrift::transport::TServerTransport;
38using apache::thrift::transport::TSSLSocket;
39using apache::thrift::transport::TSSLSocketFactory;
40using apache::thrift::transport::TTransport;
41using apache::thrift::transport::TTransportException;
42using apache::thrift::transport::TTransportFactory;
43
cyy316723a2019-01-05 16:35:14 +080044using std::bind;
45using std::shared_ptr;
James E. King, III82ae9572017-08-05 12:23:54 -040046
Jim Kingb0b710a2015-07-28 13:31:27 -040047boost::filesystem::path keyDir;
48boost::filesystem::path certFile(const std::string& filename)
49{
50 return keyDir / filename;
51}
52boost::mutex gMutex;
53
54struct GlobalFixture
55{
56 GlobalFixture()
57 {
58 using namespace boost::unit_test::framework;
James E. King, III82ae9572017-08-05 12:23:54 -040059 for (int i = 0; i < master_test_suite().argc; ++i)
60 {
61 BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
62 }
Jim Kingb0b710a2015-07-28 13:31:27 -040063
John Sirois9ed45e92016-02-11 11:53:05 -070064 #ifdef __linux__
James E. King, III82ae9572017-08-05 12:23:54 -040065 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
66 // disconnected can cause a SIGPIPE signal...
67 signal(SIGPIPE, SIG_IGN);
Jim Kingb0b710a2015-07-28 13:31:27 -040068 #endif
69
James E. King, III82ae9572017-08-05 12:23:54 -040070 TSSLSocketFactory::setManualOpenSSLInitialization(true);
71 apache::thrift::transport::initializeOpenSSL();
Jim Kingb0b710a2015-07-28 13:31:27 -040072
James E. King, III82ae9572017-08-05 12:23:54 -040073 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
74 if (!boost::filesystem::exists(certFile("server.crt")))
75 {
76 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
77 if (!boost::filesystem::exists(certFile("server.crt")))
78 {
79 throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
80 }
81 }
Jim Kingb0b710a2015-07-28 13:31:27 -040082 }
83
84 virtual ~GlobalFixture()
85 {
James E. King, III82ae9572017-08-05 12:23:54 -040086 apache::thrift::transport::cleanupOpenSSL();
John Sirois9ed45e92016-02-11 11:53:05 -070087#ifdef __linux__
James E. King, III82ae9572017-08-05 12:23:54 -040088 signal(SIGPIPE, SIG_DFL);
Jim Kingb0b710a2015-07-28 13:31:27 -040089#endif
Jim Kingb0b710a2015-07-28 13:31:27 -040090 }
91};
92
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020093#if (BOOST_VERSION >= 105900)
94BOOST_GLOBAL_FIXTURE(GlobalFixture);
95#else
Jim Kingb0b710a2015-07-28 13:31:27 -040096BOOST_GLOBAL_FIXTURE(GlobalFixture)
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020097#endif
Jim Kingb0b710a2015-07-28 13:31:27 -040098
John Sirois9f0d9612016-02-12 16:15:43 -070099struct SecurityFixture
Jim Kingb0b710a2015-07-28 13:31:27 -0400100{
101 void server(apache::thrift::transport::SSLProtocol protocol)
102 {
103 try
104 {
105 boost::mutex::scoped_lock lock(mMutex);
106
James E. King, III82ae9572017-08-05 12:23:54 -0400107 shared_ptr<TSSLSocketFactory> pServerSocketFactory;
108 shared_ptr<TSSLServerSocket> pServerSocket;
Jim Kingb0b710a2015-07-28 13:31:27 -0400109
110 pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
Thomas8148f2f2024-02-26 21:45:05 +0900111 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && OPENSSL_VERSION_NUMBER < 0x30000000L
112 // OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
113 // to @SECLEVEL=0 or 1, so specify it to test all combinations.
114 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0:@STRENGTH");
115 #else
116 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
117 #endif
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100118 pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str());
119 pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str());
Jim Kingb0b710a2015-07-28 13:31:27 -0400120 pServerSocketFactory->server(true);
John Sirois9f0d9612016-02-12 16:15:43 -0700121 pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
James E. King, III82ae9572017-08-05 12:23:54 -0400122 shared_ptr<TTransport> connectedClient;
Jim Kingb0b710a2015-07-28 13:31:27 -0400123
124 try
125 {
126 pServerSocket->listen();
John Sirois9f0d9612016-02-12 16:15:43 -0700127 mPort = pServerSocket->getPort();
Jim Kingb0b710a2015-07-28 13:31:27 -0400128 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 {
141 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200142 BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400143 }
144
145 if (connectedClient)
146 {
147 connectedClient->close();
148 connectedClient.reset();
149 }
150
151 pServerSocket->close();
152 pServerSocket.reset();
153 }
154 catch (std::exception& ex)
155 {
156 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
157 }
158 }
159
160 void client(apache::thrift::transport::SSLProtocol protocol)
161 {
162 try
163 {
James E. King, III82ae9572017-08-05 12:23:54 -0400164 shared_ptr<TSSLSocketFactory> pClientSocketFactory;
165 shared_ptr<TSSLSocket> pClientSocket;
Jim Kingb0b710a2015-07-28 13:31:27 -0400166
167 try
168 {
169 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
170 pClientSocketFactory->authenticate(true);
Thomas8148f2f2024-02-26 21:45:05 +0900171 #if OPENSSL_VERSION_NUMBER >= 0x10100000L && OPENSSL_VERSION_NUMBER < 0x30000000L
172 // OpenSSL 1.1.0 introduced @SECLEVEL. Modern distributions limit TLS 1.0/1.1
173 // to @SECLEVEL=0 or 1, so specify it to test all combinations.
174 pClientSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@SECLEVEL=0");
175 #endif
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100176 pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str());
177 pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str());
178 pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str());
John Sirois9f0d9612016-02-12 16:15:43 -0700179 pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
Jim Kingb0b710a2015-07-28 13:31:27 -0400180 pClientSocket->open();
181
182 uint8_t buf[3];
183 buf[0] = 0;
184 buf[1] = 0;
185 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
186 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
187 mConnected = true;
188 }
189 catch (apache::thrift::transport::TTransportException& ex)
190 {
191 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200192 BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400193 }
194
195 if (pClientSocket)
196 {
197 pClientSocket->close();
198 pClientSocket.reset();
199 }
200 }
201 catch (std::exception& ex)
202 {
203 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
204 }
205 }
206
207 static const char *protocol2str(size_t protocol)
208 {
209 static const char *strings[apache::thrift::transport::LATEST + 1] =
210 {
211 "SSLTLS",
212 "SSLv2",
213 "SSLv3",
214 "TLSv1_0",
215 "TLSv1_1",
216 "TLSv1_2"
217 };
218 return strings[protocol];
219 }
220
221 boost::mutex mMutex;
222 boost::condition_variable mCVar;
John Sirois9f0d9612016-02-12 16:15:43 -0700223 int mPort;
Jim Kingb0b710a2015-07-28 13:31:27 -0400224 bool mConnected;
225};
226
227BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFixture)
228
229BOOST_AUTO_TEST_CASE(ssl_security_matrix)
230{
231 try
232 {
233 // matrix of connection success between client and server with different SSLProtocol selections
Marco Schroeter016dbac2019-08-21 16:13:23 +0200234 static_assert(apache::thrift::transport::LATEST == 5, "Mismatch in assumed number of ssl protocols");
Thomas8148f2f2024-02-26 21:45:05 +0900235 bool ossl1x = (OPENSSL_VERSION_NUMBER < 0x30000000L);
Jim Kingb0b710a2015-07-28 13:31:27 -0400236 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
237 {
238 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
239 // client
Thomas8148f2f2024-02-26 21:45:05 +0900240 /* SSLTLS */ { true, false, false, ossl1x, ossl1x, true },
Jim Kingb0b710a2015-07-28 13:31:27 -0400241 /* SSLv2 */ { false, false, false, false, false, false },
242 /* SSLv3 */ { false, false, true, false, false, false },
Thomas8148f2f2024-02-26 21:45:05 +0900243 /* TLSv1_0 */ { ossl1x, false, false, ossl1x, false, false },
244 /* TLSv1_1 */ { ossl1x, false, false, false, ossl1x, false },
Jim Kingb0b710a2015-07-28 13:31:27 -0400245 /* TLSv1_2 */ { true, false, false, false, false, true }
246 };
247
248 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si)
249 {
250 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci)
251 {
252 if (si == 1 || ci == 1)
253 {
254 // Skip all SSLv2 cases - protocol not supported
255 continue;
256 }
257
Nobuaki Sukegawab8192602016-03-13 08:55:38 +0900258#ifdef OPENSSL_NO_SSL3
259 if (si == 2 || ci == 2)
260 {
261 // Skip all SSLv3 cases - protocol not supported
262 continue;
263 }
264#endif
265
Jim Kingb0b710a2015-07-28 13:31:27 -0400266 boost::mutex::scoped_lock lock(mMutex);
267
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200268 BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%")
Jim Kingb0b710a2015-07-28 13:31:27 -0400269 % protocol2str(si) % protocol2str(ci));
270
271 mConnected = false;
James E. King, III533405e2017-10-28 18:25:45 -0400272 // thread_group manages the thread lifetime - ignore the return value of create_thread
Jim Kingb0b710a2015-07-28 13:31:27 -0400273 boost::thread_group threads;
James E. King, III533405e2017-10-28 18:25:45 -0400274 (void)threads.create_thread(bind(&SecurityFixture::server, this, static_cast<apache::thrift::transport::SSLProtocol>(si)));
Jim Kingb0b710a2015-07-28 13:31:27 -0400275 mCVar.wait(lock); // wait for listen() to succeed
276 lock.unlock();
James E. King, III533405e2017-10-28 18:25:45 -0400277 (void)threads.create_thread(bind(&SecurityFixture::client, this, static_cast<apache::thrift::transport::SSLProtocol>(ci)));
Jim Kingb0b710a2015-07-28 13:31:27 -0400278 threads.join_all();
279
280 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
281 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
282 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
283 }
284 }
285 }
286 catch (std::exception& ex)
287 {
288 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
289 }
290}
291
292BOOST_AUTO_TEST_SUITE_END()