blob: ab3d99b2b6e1bed68d82ef6455e8d77fdb7246df [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)));
111 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100112 pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str());
113 pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str());
Jim Kingb0b710a2015-07-28 13:31:27 -0400114 pServerSocketFactory->server(true);
John Sirois9f0d9612016-02-12 16:15:43 -0700115 pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
James E. King, III82ae9572017-08-05 12:23:54 -0400116 shared_ptr<TTransport> connectedClient;
Jim Kingb0b710a2015-07-28 13:31:27 -0400117
118 try
119 {
120 pServerSocket->listen();
John Sirois9f0d9612016-02-12 16:15:43 -0700121 mPort = pServerSocket->getPort();
Jim Kingb0b710a2015-07-28 13:31:27 -0400122 mCVar.notify_one();
123 lock.unlock();
124
125 connectedClient = pServerSocket->accept();
126 uint8_t buf[2];
127 buf[0] = 'O';
128 buf[1] = 'K';
129 connectedClient->write(&buf[0], 2);
130 connectedClient->flush();
131 }
132
133 catch (apache::thrift::transport::TTransportException& ex)
134 {
135 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200136 BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400137 }
138
139 if (connectedClient)
140 {
141 connectedClient->close();
142 connectedClient.reset();
143 }
144
145 pServerSocket->close();
146 pServerSocket.reset();
147 }
148 catch (std::exception& ex)
149 {
150 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
151 }
152 }
153
154 void client(apache::thrift::transport::SSLProtocol protocol)
155 {
156 try
157 {
James E. King, III82ae9572017-08-05 12:23:54 -0400158 shared_ptr<TSSLSocketFactory> pClientSocketFactory;
159 shared_ptr<TSSLSocket> pClientSocket;
Jim Kingb0b710a2015-07-28 13:31:27 -0400160
161 try
162 {
163 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
164 pClientSocketFactory->authenticate(true);
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100165 pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str());
166 pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str());
167 pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str());
John Sirois9f0d9612016-02-12 16:15:43 -0700168 pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
Jim Kingb0b710a2015-07-28 13:31:27 -0400169 pClientSocket->open();
170
171 uint8_t buf[3];
172 buf[0] = 0;
173 buf[1] = 0;
174 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
175 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
176 mConnected = true;
177 }
178 catch (apache::thrift::transport::TTransportException& ex)
179 {
180 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200181 BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400182 }
183
184 if (pClientSocket)
185 {
186 pClientSocket->close();
187 pClientSocket.reset();
188 }
189 }
190 catch (std::exception& ex)
191 {
192 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
193 }
194 }
195
196 static const char *protocol2str(size_t protocol)
197 {
198 static const char *strings[apache::thrift::transport::LATEST + 1] =
199 {
200 "SSLTLS",
201 "SSLv2",
202 "SSLv3",
203 "TLSv1_0",
204 "TLSv1_1",
205 "TLSv1_2"
206 };
207 return strings[protocol];
208 }
209
210 boost::mutex mMutex;
211 boost::condition_variable mCVar;
John Sirois9f0d9612016-02-12 16:15:43 -0700212 int mPort;
Jim Kingb0b710a2015-07-28 13:31:27 -0400213 bool mConnected;
214};
215
216BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFixture)
217
218BOOST_AUTO_TEST_CASE(ssl_security_matrix)
219{
220 try
221 {
222 // matrix of connection success between client and server with different SSLProtocol selections
Marco Schroeter016dbac2019-08-21 16:13:23 +0200223 static_assert(apache::thrift::transport::LATEST == 5, "Mismatch in assumed number of ssl protocols");
Tobias Mayer05604e22023-02-18 14:50:15 +0100224 bool ossl1 = OPENSSL_VERSION_MAJOR == 1;
Jim Kingb0b710a2015-07-28 13:31:27 -0400225 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
226 {
227 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
228 // client
Tobias Mayer05604e22023-02-18 14:50:15 +0100229 /* SSLTLS */ { true, false, false, ossl1, ossl1, true },
Jim Kingb0b710a2015-07-28 13:31:27 -0400230 /* SSLv2 */ { false, false, false, false, false, false },
231 /* SSLv3 */ { false, false, true, false, false, false },
Tobias Mayer05604e22023-02-18 14:50:15 +0100232 /* TLSv1_0 */ { ossl1, false, false, ossl1, false, false },
233 /* TLSv1_1 */ { ossl1, false, false, false, ossl1, false },
Jim Kingb0b710a2015-07-28 13:31:27 -0400234 /* TLSv1_2 */ { true, false, false, false, false, true }
235 };
236
237 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si)
238 {
239 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci)
240 {
241 if (si == 1 || ci == 1)
242 {
243 // Skip all SSLv2 cases - protocol not supported
244 continue;
245 }
246
Nobuaki Sukegawab8192602016-03-13 08:55:38 +0900247#ifdef OPENSSL_NO_SSL3
248 if (si == 2 || ci == 2)
249 {
250 // Skip all SSLv3 cases - protocol not supported
251 continue;
252 }
253#endif
254
Jim Kingb0b710a2015-07-28 13:31:27 -0400255 boost::mutex::scoped_lock lock(mMutex);
256
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200257 BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%")
Jim Kingb0b710a2015-07-28 13:31:27 -0400258 % protocol2str(si) % protocol2str(ci));
259
260 mConnected = false;
James E. King, III533405e2017-10-28 18:25:45 -0400261 // thread_group manages the thread lifetime - ignore the return value of create_thread
Jim Kingb0b710a2015-07-28 13:31:27 -0400262 boost::thread_group threads;
James E. King, III533405e2017-10-28 18:25:45 -0400263 (void)threads.create_thread(bind(&SecurityFixture::server, this, static_cast<apache::thrift::transport::SSLProtocol>(si)));
Jim Kingb0b710a2015-07-28 13:31:27 -0400264 mCVar.wait(lock); // wait for listen() to succeed
265 lock.unlock();
James E. King, III533405e2017-10-28 18:25:45 -0400266 (void)threads.create_thread(bind(&SecurityFixture::client, this, static_cast<apache::thrift::transport::SSLProtocol>(ci)));
Jim Kingb0b710a2015-07-28 13:31:27 -0400267 threads.join_all();
268
269 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
270 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
271 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
272 }
273 }
274 }
275 catch (std::exception& ex)
276 {
277 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
278 }
279}
280
281BOOST_AUTO_TEST_SUITE_END()