blob: 72a4c2a28f09933517bf15b352b9e7d984999b6a [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>
29#include <thrift/transport/TSSLServerSocket.h>
30#include <thrift/transport/TSSLSocket.h>
31#include <thrift/transport/TTransport.h>
32#include <vector>
33#ifdef __linux__
34#include <signal.h>
35#endif
36
37using apache::thrift::transport::TServerTransport;
38using apache::thrift::transport::TSSLServerSocket;
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
45using std::bind;
46using std::shared_ptr;
47
48boost::filesystem::path keyDir;
49boost::filesystem::path certFile(const std::string& filename) {
50 return keyDir / filename;
51}
52std::string certString(const std::string& filename) {
53 std::ifstream ifs(certFile(filename).string());
54 if(!ifs.is_open() || !ifs.good()) {
55 throw(std::runtime_error("Failed to open key file " + filename + " for reading"));
56 }
57 std::stringstream buffer;
58 buffer << ifs.rdbuf();
59 return buffer.str();
60}
61boost::mutex gMutex;
62
63struct GlobalFixture {
64 GlobalFixture() {
65 using namespace boost::unit_test::framework;
66 for (int i = 0; i < master_test_suite().argc; ++i) {
67 BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
68 }
69
70#ifdef __linux__
71 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
72 // disconnected can cause a SIGPIPE signal...
73 signal(SIGPIPE, SIG_IGN);
74#endif
75
76 TSSLSocketFactory::setManualOpenSSLInitialization(true);
77 apache::thrift::transport::initializeOpenSSL();
78
79 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
80 if (!boost::filesystem::exists(certFile("server.crt"))) {
81 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
82 if (!boost::filesystem::exists(certFile("server.crt"))) {
83 throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
84 }
85 }
86 }
87
88 virtual ~GlobalFixture() {
89 apache::thrift::transport::cleanupOpenSSL();
90#ifdef __linux__
91 signal(SIGPIPE, SIG_DFL);
92#endif
93 }
94};
95
96#if (BOOST_VERSION >= 105900)
97BOOST_GLOBAL_FIXTURE(GlobalFixture);
98#else
99BOOST_GLOBAL_FIXTURE(GlobalFixture)
100#endif
101
102struct SecurityFromBufferFixture {
103 void server(apache::thrift::transport::SSLProtocol protocol) {
104 try {
105 boost::mutex::scoped_lock lock(mMutex);
106
107 shared_ptr<TSSLSocketFactory> pServerSocketFactory;
108 shared_ptr<TSSLServerSocket> pServerSocket;
109
110 pServerSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
111 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
112 pServerSocketFactory->loadCertificateFromBuffer(certString("server.crt").c_str());
113 pServerSocketFactory->loadPrivateKeyFromBuffer(certString("server.key").c_str());
114 pServerSocketFactory->server(true);
115 pServerSocket.reset(new TSSLServerSocket("localhost", 0, pServerSocketFactory));
116 shared_ptr<TTransport> connectedClient;
117
118 try {
119 pServerSocket->listen();
120 mPort = pServerSocket->getPort();
121 mCVar.notify_one();
122 lock.unlock();
123
124 connectedClient = pServerSocket->accept();
125 uint8_t buf[2];
126 buf[0] = 'O';
127 buf[1] = 'K';
128 connectedClient->write(&buf[0], 2);
129 connectedClient->flush();
130 }
131
132 catch (apache::thrift::transport::TTransportException& ex) {
133 boost::mutex::scoped_lock lock(gMutex);
134 BOOST_TEST_MESSAGE(boost::format("SRV %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
135 }
136
137 if (connectedClient) {
138 connectedClient->close();
139 connectedClient.reset();
140 }
141
142 pServerSocket->close();
143 pServerSocket.reset();
144 } catch (std::exception& ex) {
145 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
146 }
147 }
148
149 void client(apache::thrift::transport::SSLProtocol protocol) {
150 try {
151 shared_ptr<TSSLSocketFactory> pClientSocketFactory;
152 shared_ptr<TSSLSocket> pClientSocket;
153
154 try {
155 pClientSocketFactory.reset(new TSSLSocketFactory(static_cast<apache::thrift::transport::SSLProtocol>(protocol)));
156 pClientSocketFactory->authenticate(true);
157 pClientSocketFactory->loadCertificateFromBuffer(certString("client.crt").c_str());
158 pClientSocketFactory->loadPrivateKeyFromBuffer(certString("client.key").c_str());
159 pClientSocketFactory->loadTrustedCertificatesFromBuffer(certString("CA.pem").c_str());
160 pClientSocket = pClientSocketFactory->createSocket("localhost", mPort);
161 pClientSocket->open();
162
163 uint8_t buf[3];
164 buf[0] = 0;
165 buf[1] = 0;
166 BOOST_CHECK_EQUAL(2, pClientSocket->read(&buf[0], 2));
167 BOOST_CHECK_EQUAL(0, memcmp(&buf[0], "OK", 2));
168 mConnected = true;
169 } catch (apache::thrift::transport::TTransportException& ex) {
170 boost::mutex::scoped_lock lock(gMutex);
171 BOOST_TEST_MESSAGE(boost::format("CLI %1% Exception: %2%") % boost::this_thread::get_id() % ex.what());
172 }
173
174 if (pClientSocket) {
175 pClientSocket->close();
176 pClientSocket.reset();
177 }
178 } catch (std::exception& ex) {
179 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
180 }
181 }
182
183 static const char* protocol2str(size_t protocol) {
184 static const char* strings[apache::thrift::transport::LATEST + 1]
185 = {"SSLTLS", "SSLv2", "SSLv3", "TLSv1_0", "TLSv1_1", "TLSv1_2"};
186 return strings[protocol];
187 }
188
189 boost::mutex mMutex;
190 boost::condition_variable mCVar;
191 int mPort;
192 bool mConnected;
193};
194
195BOOST_FIXTURE_TEST_SUITE(BOOST_TEST_MODULE, SecurityFromBufferFixture)
196
197BOOST_AUTO_TEST_CASE(ssl_security_matrix) {
198 try {
199 // matrix of connection success between client and server with different SSLProtocol selections
200 static_assert(apache::thrift::transport::LATEST == 5, "Mismatch in assumed number of ssl protocols");
201 bool matrix[apache::thrift::transport::LATEST + 1][apache::thrift::transport::LATEST + 1] =
202 {
203 // server = SSLTLS SSLv2 SSLv3 TLSv1_0 TLSv1_1 TLSv1_2
204 // client
205 /* SSLTLS */ { true, false, false, true, true, true },
206 /* SSLv2 */ { false, false, false, false, false, false },
207 /* SSLv3 */ { false, false, true, false, false, false },
208 /* TLSv1_0 */ { true, false, false, true, false, false },
209 /* TLSv1_1 */ { true, false, false, false, true, false },
210 /* TLSv1_2 */ { true, false, false, false, false, true }
211 };
212
213 for (size_t si = 0; si <= apache::thrift::transport::LATEST; ++si) {
214 for (size_t ci = 0; ci <= apache::thrift::transport::LATEST; ++ci) {
215 if (si == 1 || ci == 1) {
216 // Skip all SSLv2 cases - protocol not supported
217 continue;
218 }
219
220#ifdef OPENSSL_NO_SSL3
221 if (si == 2 || ci == 2) {
222 // Skip all SSLv3 cases - protocol not supported
223 continue;
224 }
225#endif
226
227 boost::mutex::scoped_lock lock(mMutex);
228
229 BOOST_TEST_MESSAGE(boost::format("TEST: Server = %1%, Client = %2%") % protocol2str(si)
230 % protocol2str(ci));
231
232 mConnected = false;
233 // thread_group manages the thread lifetime - ignore the return value of create_thread
234 boost::thread_group threads;
235 (void)threads.create_thread(bind(&SecurityFromBufferFixture::server, this,
236 static_cast<apache::thrift::transport::SSLProtocol>(si)));
237 mCVar.wait(lock); // wait for listen() to succeed
238 lock.unlock();
239 (void)threads.create_thread(bind(&SecurityFromBufferFixture::client, this,
240 static_cast<apache::thrift::transport::SSLProtocol>(ci)));
241 threads.join_all();
242
243 BOOST_CHECK_MESSAGE(mConnected == matrix[ci][si],
244 boost::format(" Server = %1%, Client = %2% expected mConnected == %3% but was %4%")
245 % protocol2str(si) % protocol2str(ci) % matrix[ci][si] % mConnected);
246 }
247 }
248 } catch (std::exception& ex) {
249 BOOST_FAIL(boost::format("%1%: %2%") % typeid(ex).name() % ex.what());
250 }
251}
252
253BOOST_AUTO_TEST_SUITE_END()