blob: 23650d628012ee54ca11963f2fc91468a464c772 [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/bind.hpp>
23#include <boost/filesystem.hpp>
24#include <boost/foreach.hpp>
25#include <boost/format.hpp>
26#include <boost/make_shared.hpp>
27#include <boost/shared_ptr.hpp>
28#include <boost/thread.hpp>
29#include <thrift/transport/TSSLServerSocket.h>
30#include <thrift/transport/TSSLSocket.h>
31#include <thrift/transport/TTransport.h>
Jim Kingb0b710a2015-07-28 13:31:27 -040032#include <vector>
John Sirois9ed45e92016-02-11 11:53:05 -070033#ifdef __linux__
Jim Kingb0b710a2015-07-28 13:31:27 -040034#include <signal.h>
35#endif
36
37using apache::thrift::transport::TSSLServerSocket;
38using apache::thrift::transport::TServerTransport;
39using apache::thrift::transport::TSSLSocket;
40using apache::thrift::transport::TSSLSocketFactory;
41using apache::thrift::transport::TTransport;
42using apache::thrift::transport::TTransportException;
43using apache::thrift::transport::TTransportFactory;
44
45boost::filesystem::path keyDir;
46boost::filesystem::path certFile(const std::string& filename)
47{
48 return keyDir / filename;
49}
50boost::mutex gMutex;
51
52struct GlobalFixture
53{
54 GlobalFixture()
55 {
56 using namespace boost::unit_test::framework;
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020057 for (int i = 0; i < master_test_suite().argc; ++i)
58 {
59 BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
60 }
Jim Kingb0b710a2015-07-28 13:31:27 -040061
John Sirois9ed45e92016-02-11 11:53:05 -070062 #ifdef __linux__
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020063 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
64 // disconnected can cause a SIGPIPE signal...
65 signal(SIGPIPE, SIG_IGN);
Jim Kingb0b710a2015-07-28 13:31:27 -040066 #endif
67
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020068 TSSLSocketFactory::setManualOpenSSLInitialization(true);
69 apache::thrift::transport::initializeOpenSSL();
Jim Kingb0b710a2015-07-28 13:31:27 -040070
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020071 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
72 if (!boost::filesystem::exists(certFile("server.crt")))
73 {
74 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
75 if (!boost::filesystem::exists(certFile("server.crt")))
76 {
77 throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
78 }
79 }
Jim Kingb0b710a2015-07-28 13:31:27 -040080 }
81
82 virtual ~GlobalFixture()
83 {
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020084 apache::thrift::transport::cleanupOpenSSL();
John Sirois9ed45e92016-02-11 11:53:05 -070085#ifdef __linux__
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020086 signal(SIGPIPE, SIG_DFL);
Jim Kingb0b710a2015-07-28 13:31:27 -040087#endif
Jim Kingb0b710a2015-07-28 13:31:27 -040088 }
89};
90
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020091#if (BOOST_VERSION >= 105900)
92BOOST_GLOBAL_FIXTURE(GlobalFixture);
93#else
Jim Kingb0b710a2015-07-28 13:31:27 -040094BOOST_GLOBAL_FIXTURE(GlobalFixture)
Konrad Grochowskie9bdb412015-09-25 20:17:36 +020095#endif
Jim Kingb0b710a2015-07-28 13:31:27 -040096
John Sirois9f0d9612016-02-12 16:15:43 -070097struct SecurityFixture
Jim Kingb0b710a2015-07-28 13:31:27 -040098{
99 void server(apache::thrift::transport::SSLProtocol protocol)
100 {
101 try
102 {
103 boost::mutex::scoped_lock lock(mMutex);
104
105 boost::shared_ptr<TSSLSocketFactory> pServerSocketFactory;
106 boost::shared_ptr<TSSLServerSocket> pServerSocket;
107
108 pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
109 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100110 pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str());
111 pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str());
Jim Kingb0b710a2015-07-28 13:31:27 -0400112 pServerSocketFactory->server(true);
John Sirois9f0d9612016-02-12 16:15:43 -0700113 pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
Jim Kingb0b710a2015-07-28 13:31:27 -0400114 boost::shared_ptr<TTransport> connectedClient;
115
116 try
117 {
118 pServerSocket->listen();
John Sirois9f0d9612016-02-12 16:15:43 -0700119 mPort = pServerSocket->getPort();
Jim Kingb0b710a2015-07-28 13:31:27 -0400120 mCVar.notify_one();
121 lock.unlock();
122
123 connectedClient = pServerSocket->accept();
124 uint8_t buf[2];
125 buf[0] = 'O';
126 buf[1] = 'K';
127 connectedClient->write(&buf[0], 2);
128 connectedClient->flush();
129 }
130
131 catch (apache::thrift::transport::TTransportException& ex)
132 {
133 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200134 BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400135 }
136
137 if (connectedClient)
138 {
139 connectedClient->close();
140 connectedClient.reset();
141 }
142
143 pServerSocket->close();
144 pServerSocket.reset();
145 }
146 catch (std::exception& ex)
147 {
148 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
149 }
150 }
151
152 void client(apache::thrift::transport::SSLProtocol protocol)
153 {
154 try
155 {
156 boost::shared_ptr<TSSLSocketFactory> pClientSocketFactory;
157 boost::shared_ptr<TSSLSocket> pClientSocket;
158
159 try
160 {
161 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
162 pClientSocketFactory->authenticate(true);
Antonio Di Monaco796667b2016-01-04 23:05:19 +0100163 pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str());
164 pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str());
165 pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str());
John Sirois9f0d9612016-02-12 16:15:43 -0700166 pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
Jim Kingb0b710a2015-07-28 13:31:27 -0400167 pClientSocket->open();
168
169 uint8_t buf[3];
170 buf[0] = 0;
171 buf[1] = 0;
172 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
173 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
174 mConnected = true;
175 }
176 catch (apache::thrift::transport::TTransportException& ex)
177 {
178 boost::mutex::scoped_lock lock(gMutex);
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200179 BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
Jim Kingb0b710a2015-07-28 13:31:27 -0400180 }
181
182 if (pClientSocket)
183 {
184 pClientSocket->close();
185 pClientSocket.reset();
186 }
187 }
188 catch (std::exception& ex)
189 {
190 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
191 }
192 }
193
194 static const char *protocol2str(size_t protocol)
195 {
196 static const char *strings[apache::thrift::transport::LATEST + 1] =
197 {
198 "SSLTLS",
199 "SSLv2",
200 "SSLv3",
201 "TLSv1_0",
202 "TLSv1_1",
203 "TLSv1_2"
204 };
205 return strings[protocol];
206 }
207
208 boost::mutex mMutex;
209 boost::condition_variable mCVar;
John Sirois9f0d9612016-02-12 16:15:43 -0700210 int mPort;
Jim Kingb0b710a2015-07-28 13:31:27 -0400211 bool mConnected;
212};
213
214BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFixture)
215
216BOOST_AUTO_TEST_CASE(ssl_security_matrix)
217{
218 try
219 {
220 // matrix of connection success between client and server with different SSLProtocol selections
221 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
222 {
223 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
224 // client
225 /* SSLTLS */ { true, false, false, true, true, true },
226 /* SSLv2 */ { false, false, false, false, false, false },
227 /* SSLv3 */ { false, false, true, false, false, false },
228 /* TLSv1_0 */ { true, false, false, true, false, false },
229 /* TLSv1_1 */ { true, false, false, false, true, false },
230 /* TLSv1_2 */ { true, false, false, false, false, true }
231 };
232
233 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si)
234 {
235 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci)
236 {
237 if (si == 1 || ci == 1)
238 {
239 // Skip all SSLv2 cases - protocol not supported
240 continue;
241 }
242
Nobuaki Sukegawab8192602016-03-13 08:55:38 +0900243#ifdef OPENSSL_NO_SSL3
244 if (si == 2 || ci == 2)
245 {
246 // Skip all SSLv3 cases - protocol not supported
247 continue;
248 }
249#endif
250
Jim Kingb0b710a2015-07-28 13:31:27 -0400251 boost::mutex::scoped_lock lock(mMutex);
252
Konrad Grochowskie9bdb412015-09-25 20:17:36 +0200253 BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%")
Jim Kingb0b710a2015-07-28 13:31:27 -0400254 % protocol2str(si) % protocol2str(ci));
255
256 mConnected = false;
257 boost::thread_group threads;
258 threads.create_thread(boost::bind(&SecurityFixture::server, this, static_cast<apache::thrift::transport::SSLProtocol>(si)));
259 mCVar.wait(lock); // wait for listen() to succeed
260 lock.unlock();
261 threads.create_thread(boost::bind(&SecurityFixture::client, this, static_cast<apache::thrift::transport::SSLProtocol>(ci)));
262 threads.join_all();
263
264 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
265 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
266 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
267 }
268 }
269 }
270 catch (std::exception& ex)
271 {
272 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
273 }
274}
275
276BOOST_AUTO_TEST_SUITE_END()