blob: c22d79b933993fa3a95e93de5c3f575bcedec6a8 [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
21#include <boost/test/auto_unit_test.hpp>
22#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>
32#include "TestPortFixture.h"
33#include <vector>
34#ifdef linux
35#include <signal.h>
36#endif
37
38using apache::thrift::transport::TSSLServerSocket;
39using apache::thrift::transport::TServerTransport;
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
46boost::filesystem::path keyDir;
47boost::filesystem::path certFile(const std::string& filename)
48{
49 return keyDir / filename;
50}
51boost::mutex gMutex;
52
53struct GlobalFixture
54{
55 GlobalFixture()
56 {
57 using namespace boost::unit_test::framework;
58 try
59 {
60 for (int i = 0; i < master_test_suite().argc; ++i)
61 {
62 BOOST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
63 }
64
65 #ifdef linux
66 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
67 // disconnected can cause a SIGPIPE signal...
68 signal(SIGPIPE, SIG_IGN);
69 #endif
70
71 TSSLSocketFactory::setManualOpenSSLInitialization(true);
72 apache::thrift::transport::initializeOpenSSL();
73
74 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
75 if (!boost::filesystem::exists(certFile("server.crt")))
76 {
77 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
78 BOOST_REQUIRE_MESSAGE(boost::filesystem::exists(certFile("server.crt")),
79 "The last argument to this test must be the directory containing the test certificate(s).");
80 }
81 }
82 catch (std::exception& ex)
83 {
84 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
85 }
86 }
87
88 virtual ~GlobalFixture()
89 {
90 try
91 {
92 apache::thrift::transport::cleanupOpenSSL();
93#ifdef linux
94 signal(SIGPIPE, SIG_DFL);
95#endif
96 }
97 catch (std::exception& ex)
98 {
99 BOOST_MESSAGE(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
100 }
101 }
102};
103
104BOOST_GLOBAL_FIXTURE(GlobalFixture)
105
106struct SecurityFixture : public TestPortFixture
107{
108 void server(apache::thrift::transport::SSLProtocol protocol)
109 {
110 try
111 {
112 boost::mutex::scoped_lock lock(mMutex);
113
114 boost::shared_ptr<TSSLSocketFactory> pServerSocketFactory;
115 boost::shared_ptr<TSSLServerSocket> pServerSocket;
116
117 pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
118 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
119 pServerSocketFactory->loadCertificate(certFile("server.crt").native().c_str());
120 pServerSocketFactory->loadPrivateKey(certFile("server.key").native().c_str());
121 pServerSocketFactory->server(true);
122 pServerSocket.reset(new TSSLServerSocket("localhost", m_serverPort, pServerSocketFactory));
123 boost::shared_ptr<TTransport> connectedClient;
124
125 try
126 {
127 pServerSocket->listen();
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 {
141 boost::mutex::scoped_lock lock(gMutex);
142 BOOST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
143 }
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 {
164 boost::shared_ptr<TSSLSocketFactory> pClientSocketFactory;
165 boost::shared_ptr<TSSLSocket> pClientSocket;
166
167 try
168 {
169 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
170 pClientSocketFactory->authenticate(true);
171 pClientSocketFactory->loadCertificate(certFile("client.crt").native().c_str());
172 pClientSocketFactory->loadPrivateKey(certFile("client.key").native().c_str());
173 pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").native().c_str());
174 pClientSocket = pClientSocketFactory->createSocket("localhost", m_serverPort);
175 pClientSocket->open();
176
177 uint8_t buf[3];
178 buf[0] = 0;
179 buf[1] = 0;
180 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
181 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
182 mConnected = true;
183 }
184 catch (apache::thrift::transport::TTransportException& ex)
185 {
186 boost::mutex::scoped_lock lock(gMutex);
187 BOOST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
188 }
189
190 if (pClientSocket)
191 {
192 pClientSocket->close();
193 pClientSocket.reset();
194 }
195 }
196 catch (std::exception& ex)
197 {
198 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
199 }
200 }
201
202 static const char *protocol2str(size_t protocol)
203 {
204 static const char *strings[apache::thrift::transport::LATEST + 1] =
205 {
206 "SSLTLS",
207 "SSLv2",
208 "SSLv3",
209 "TLSv1_0",
210 "TLSv1_1",
211 "TLSv1_2"
212 };
213 return strings[protocol];
214 }
215
216 boost::mutex mMutex;
217 boost::condition_variable mCVar;
218 bool mConnected;
219};
220
221BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFixture)
222
223BOOST_AUTO_TEST_CASE(ssl_security_matrix)
224{
225 try
226 {
227 // matrix of connection success between client and server with different SSLProtocol selections
228 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
229 {
230 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
231 // client
232 /* SSLTLS */ { true, false, false, true, true, true },
233 /* SSLv2 */ { false, false, false, false, false, false },
234 /* SSLv3 */ { false, false, true, false, false, false },
235 /* TLSv1_0 */ { true, false, false, true, false, false },
236 /* TLSv1_1 */ { true, false, false, false, true, false },
237 /* TLSv1_2 */ { true, false, false, false, false, true }
238 };
239
240 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si)
241 {
242 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci)
243 {
244 if (si == 1 || ci == 1)
245 {
246 // Skip all SSLv2 cases - protocol not supported
247 continue;
248 }
249
250 boost::mutex::scoped_lock lock(mMutex);
251
252 BOOST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%")
253 % protocol2str(si) % protocol2str(ci));
254
255 mConnected = false;
256 boost::thread_group threads;
257 threads.create_thread(boost::bind(&SecurityFixture::server, this, static_cast<apache::thrift::transport::SSLProtocol>(si)));
258 mCVar.wait(lock); // wait for listen() to succeed
259 lock.unlock();
260 threads.create_thread(boost::bind(&SecurityFixture::client, this, static_cast<apache::thrift::transport::SSLProtocol>(ci)));
261 threads.join_all();
262
263 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
264 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
265 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
266 }
267 }
268 }
269 catch (std::exception& ex)
270 {
271 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
272 }
273}
274
275BOOST_AUTO_TEST_SUITE_END()