]> git.proxmox.com Git - ceph.git/blob - ceph/src/seastar/tests/unit/tls_test.cc
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / seastar / tests / unit / tls_test.cc
1 /*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18
19 /*
20 * Copyright (C) 2015 Cloudius Systems, Ltd.
21 */
22
23 #include <iostream>
24
25 #include <seastar/core/do_with.hh>
26 #include <seastar/core/sstring.hh>
27 #include <seastar/core/reactor.hh>
28 #include <seastar/core/do_with.hh>
29 #include <seastar/core/loop.hh>
30 #include <seastar/core/sharded.hh>
31 #include <seastar/core/thread.hh>
32 #include <seastar/core/gate.hh>
33 #include <seastar/core/temporary_buffer.hh>
34 #include <seastar/core/iostream.hh>
35 #include <seastar/util/std-compat.hh>
36 #include <seastar/net/tls.hh>
37 #include <seastar/net/dns.hh>
38 #include <seastar/net/inet_address.hh>
39 #include <seastar/testing/test_case.hh>
40 #include <seastar/testing/thread_test_case.hh>
41
42 #include <boost/dll.hpp>
43
44 #include "loopback_socket.hh"
45 #include "tmpdir.hh"
46
47 #include <gnutls/gnutls.h>
48
49 #if 0
50
51 static void enable_gnutls_logging() {
52 gnutls_global_set_log_level(99);
53 gnutls_global_set_log_function([](int lv, const char * msg) {
54 std::cerr << "GNUTLS (" << lv << ") " << msg << std::endl;
55 });
56 }
57 #endif
58
59 static const auto cert_location = boost::dll::program_location().parent_path();
60
61 static std::string certfile(const std::string& file) {
62 return (cert_location / file).string();
63 }
64
65 using namespace seastar;
66
67 static future<> connect_to_ssl_addr(::shared_ptr<tls::certificate_credentials> certs, socket_address addr) {
68 return tls::connect(certs, addr, "www.google.com").then([](connected_socket s) {
69 return do_with(std::move(s), [](connected_socket& s) {
70 return do_with(s.output(), [&s](auto& os) {
71 static const sstring msg("GET / HTTP/1.0\r\n\r\n");
72 auto f = os.write(msg);
73 return f.then([&s, &os]() mutable {
74 auto f = os.flush();
75 return f.then([&s]() mutable {
76 return do_with(s.input(), [](auto& in) {
77 auto f = in.read();
78 return f.then([](temporary_buffer<char> buf) {
79 // std::cout << buf.get() << std::endl;
80
81 // Avoid passing a nullptr as an argument of strncmp().
82 // If the temporary_buffer is empty (e.g. due to the underlying TCP connection
83 // being reset) passing the buf.get() (which would be a nullptr) to strncmp()
84 // causes a runtime error which masks the actual issue.
85 if (buf) {
86 BOOST_CHECK(strncmp(buf.get(), "HTTP/", 5) == 0);
87 }
88 BOOST_CHECK(buf.size() > 8);
89 });
90 });
91 });
92 }).finally([&os] {
93 return os.close();
94 });
95 });
96 });
97 });
98 }
99
100 static future<> connect_to_ssl_google(::shared_ptr<tls::certificate_credentials> certs) {
101 static socket_address google;
102
103 if (google.is_unspecified()) {
104 return net::dns::resolve_name("www.google.com", net::inet_address::family::INET).then([certs](net::inet_address addr) {
105 google = socket_address(addr, 443);
106 return connect_to_ssl_google(certs);
107 });
108 }
109 return connect_to_ssl_addr(std::move(certs), google);
110 }
111
112 SEASTAR_TEST_CASE(test_simple_x509_client) {
113 auto certs = ::make_shared<tls::certificate_credentials>();
114 return certs->set_x509_trust_file(certfile("tls-ca-bundle.pem"), tls::x509_crt_format::PEM).then([certs]() {
115 return connect_to_ssl_google(certs);
116 });
117 }
118
119 SEASTAR_TEST_CASE(test_x509_client_with_system_trust) {
120 auto certs = ::make_shared<tls::certificate_credentials>();
121 return certs->set_system_trust().then([certs]() {
122 return connect_to_ssl_google(certs);
123 });
124 }
125
126 SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust) {
127 tls::credentials_builder b;
128 (void)b.set_system_trust();
129 return connect_to_ssl_google(b.build_certificate_credentials());
130 }
131
132 SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust_multiple) {
133 tls::credentials_builder b;
134 (void)b.set_system_trust();
135 auto creds = b.build_certificate_credentials();
136
137 return parallel_for_each(boost::irange(0, 20), [creds](auto i) { return connect_to_ssl_google(creds); });
138 }
139
140 SEASTAR_TEST_CASE(test_x509_client_with_priority_strings) {
141 static std::vector<sstring> prios( { "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
142 "NORMAL:+ARCFOUR-128", // means normal ciphers plus ARCFOUR-128.
143 "SECURE128:-VERS-SSL3.0:+COMP-DEFLATE", // means that only secure ciphers are enabled, SSL3.0 is disabled, and libz compression enabled.
144 "NONE:+VERS-TLS-ALL:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1",
145 "SECURE256:+SECURE128",
146 "NORMAL:%COMPAT",
147 "NORMAL:-MD5",
148 "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
149 "NORMAL:+ARCFOUR-128",
150 "SECURE128:-VERS-TLS1.0:+COMP-DEFLATE",
151 "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2"
152 });
153 return do_for_each(prios, [](const sstring & prio) {
154 tls::credentials_builder b;
155 (void)b.set_system_trust();
156 b.set_priority_string(prio);
157 return connect_to_ssl_google(b.build_certificate_credentials());
158 });
159 }
160
161 SEASTAR_TEST_CASE(test_x509_client_with_priority_strings_fail) {
162 static std::vector<sstring> prios( { "NONE",
163 "NONE:+CURVE-SECP256R1"
164 });
165 return do_for_each(prios, [](const sstring & prio) {
166 tls::credentials_builder b;
167 (void)b.set_system_trust();
168 b.set_priority_string(prio);
169 try {
170 return connect_to_ssl_google(b.build_certificate_credentials()).then([] {
171 BOOST_FAIL("Expected exception");
172 }).handle_exception([](auto ep) {
173 // ok.
174 });
175 } catch (...) {
176 // also ok
177 }
178 return make_ready_future<>();
179 });
180 }
181
182 SEASTAR_TEST_CASE(test_failed_connect) {
183 tls::credentials_builder b;
184 (void)b.set_system_trust();
185 return connect_to_ssl_addr(b.build_certificate_credentials(), ipv4_addr()).handle_exception([](auto) {});
186 }
187
188 SEASTAR_TEST_CASE(test_non_tls) {
189 ::listen_options opts;
190 opts.reuse_address = true;
191 auto addr = ::make_ipv4_address( {0x7f000001, 4712});
192 auto server = server_socket(seastar::listen(addr, opts));
193
194 auto c = server.accept();
195
196 tls::credentials_builder b;
197 (void)b.set_system_trust();
198
199 auto f = connect_to_ssl_addr(b.build_certificate_credentials(), addr);
200
201
202 return c.then([f = std::move(f)](accept_result ar) mutable {
203 ::connected_socket s = std::move(ar.connection);
204 std::cerr << "Established connection" << std::endl;
205 auto sp = std::make_unique<::connected_socket>(std::move(s));
206 timer<> t([s = std::ref(*sp)] {
207 std::cerr << "Killing server side" << std::endl;
208 s.get() = ::connected_socket();
209 });
210 t.arm(timer<>::clock::now() + std::chrono::seconds(5));
211 return std::move(f).finally([t = std::move(t), sp = std::move(sp)] {});
212 }).handle_exception([server = std::move(server)](auto ep) {
213 std::cerr << "Got expected exception" << std::endl;
214 });
215 }
216
217 SEASTAR_TEST_CASE(test_abort_accept_before_handshake) {
218 auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
219 return certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).then([certs] {
220 ::listen_options opts;
221 opts.reuse_address = true;
222 auto addr = ::make_ipv4_address( {0x7f000001, 4712});
223 auto server = server_socket(tls::listen(certs, addr, opts));
224 auto c = server.accept();
225 BOOST_CHECK(!c.available()); // should not be finished
226
227 server.abort_accept();
228
229 return c.then([](auto) { BOOST_FAIL("Should not reach"); }).handle_exception([](auto) {
230 // ok
231 }).finally([server = std::move(server)] {});
232 });
233 }
234
235 SEASTAR_TEST_CASE(test_abort_accept_after_handshake) {
236 return async([] {
237 auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
238 certs->set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
239
240 ::listen_options opts;
241 opts.reuse_address = true;
242 auto addr = ::make_ipv4_address( {0x7f000001, 4712});
243 auto server = tls::listen(certs, addr, opts);
244 auto sa = server.accept();
245
246 tls::credentials_builder b;
247 b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
248
249 auto c = tls::connect(b.build_certificate_credentials(), addr).get0();
250 server.abort_accept(); // should not affect the socket we got.
251
252 auto s = sa.get0();
253 auto out = c.output();
254 auto in = s.connection.input();
255
256 out.write("apa").get();
257 auto f = out.flush();
258 auto buf = in.read().get0();
259 f.get();
260 BOOST_CHECK(sstring(buf.begin(), buf.end()) == "apa");
261
262 out.close().get();
263 in.close().get();
264 });
265 }
266
267 SEASTAR_TEST_CASE(test_abort_accept_on_server_before_handshake) {
268 return async([] {
269 ::listen_options opts;
270 opts.reuse_address = true;
271 auto addr = ::make_ipv4_address( {0x7f000001, 4712});
272 auto server = server_socket(seastar::listen(addr, opts));
273 auto sa = server.accept();
274
275 tls::credentials_builder b;
276 b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
277
278 auto creds = b.build_certificate_credentials();
279 auto f = tls::connect(creds, addr);
280
281 server.abort_accept();
282 try {
283 sa.get();
284 } catch (...) {
285 }
286 server = {};
287
288 try {
289 // the connect as such should succeed, but the handshare following it
290 // should not.
291 auto c = f.get0();
292 auto out = c.output();
293 out.write("apa").get();
294 out.flush().get();
295 out.close().get();
296
297 BOOST_FAIL("Expected exception");
298 } catch (...) {
299 // ok
300 }
301 });
302 }
303
304
305 struct streams {
306 ::connected_socket s;
307 input_stream<char> in;
308 output_stream<char> out;
309
310 // note: using custom output_stream, because we don't want polled flush
311 streams(::connected_socket cs) : s(std::move(cs)), in(s.input()), out(s.output().detach(), 8192)
312 {}
313 };
314
315 static const sstring message = "hej lilla fisk du kan dansa fint";
316
317 class echoserver {
318 ::server_socket _socket;
319 ::shared_ptr<tls::server_credentials> _certs;
320 seastar::gate _gate;
321 bool _stopped = false;
322 size_t _size;
323 std::exception_ptr _ex;
324 public:
325 echoserver(size_t message_size, bool use_dh_params = true)
326 : _certs(
327 use_dh_params
328 ? ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>())
329 : ::make_shared<tls::server_credentials>()
330 )
331 , _size(message_size)
332 {}
333
334 future<> listen(socket_address addr, sstring crtfile, sstring keyfile, tls::client_auth ca = tls::client_auth::NONE, sstring trust = {}) {
335 _certs->set_client_auth(ca);
336 auto f = _certs->set_x509_key_file(crtfile, keyfile, tls::x509_crt_format::PEM);
337 if (!trust.empty()) {
338 f = f.then([this, trust = std::move(trust)] {
339 return _certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
340 });
341 }
342 return f.then([this, addr] {
343 ::listen_options opts;
344 opts.reuse_address = true;
345
346 _socket = tls::listen(_certs, addr, opts);
347
348 (void)try_with_gate(_gate, [this] {
349 return _socket.accept().then([this](accept_result ar) {
350 ::connected_socket s = std::move(ar.connection);
351 auto strms = ::make_lw_shared<streams>(std::move(s));
352 return repeat([strms, this]() {
353 return strms->in.read_exactly(_size).then([strms](temporary_buffer<char> buf) {
354 if (buf.empty()) {
355 return make_ready_future<stop_iteration>(stop_iteration::yes);
356 }
357 sstring tmp(buf.begin(), buf.end());
358 return strms->out.write(tmp).then([strms]() {
359 return strms->out.flush();
360 }).then([] {
361 return make_ready_future<stop_iteration>(stop_iteration::no);
362 });
363 });
364 }).finally([strms]{
365 return strms->out.close();
366 }).finally([strms]{});
367 }).handle_exception([this](auto ep) {
368 if (_stopped) {
369 return make_ready_future<>();
370 }
371 _ex = ep;
372 return make_ready_future<>();
373 });
374 }).handle_exception_type([] (const gate_closed_exception&) {/* ignore */});
375 return make_ready_future<>();
376 });
377 }
378
379 future<> stop() {
380 _stopped = true;
381 _socket.abort_accept();
382 return _gate.close().handle_exception([this] (std::exception_ptr ignored) {
383 if (_ex) {
384 std::rethrow_exception(_ex);
385 }
386 });
387 }
388 };
389
390 static future<> run_echo_test(sstring message,
391 int loops,
392 sstring trust,
393 sstring name,
394 sstring crt = certfile("test.crt"),
395 sstring key = certfile("test.key"),
396 tls::client_auth ca = tls::client_auth::NONE,
397 sstring client_crt = {},
398 sstring client_key = {},
399 bool do_read = true,
400 bool use_dh_params = true,
401 tls::dn_callback distinguished_name_callback = {}
402 )
403 {
404 static const auto port = 4711;
405
406 auto msg = ::make_shared<sstring>(std::move(message));
407 auto certs = ::make_shared<tls::certificate_credentials>();
408 auto server = ::make_shared<seastar::sharded<echoserver>>();
409 auto addr = ::make_ipv4_address( {0x7f000001, port});
410
411 assert(do_read || loops == 1);
412
413 future<> f = make_ready_future();
414
415 if (!client_crt.empty() && !client_key.empty()) {
416 f = certs->set_x509_key_file(client_crt, client_key, tls::x509_crt_format::PEM);
417 if (distinguished_name_callback) {
418 certs->set_dn_verification_callback(std::move(distinguished_name_callback));
419 }
420 }
421
422 return f.then([=] {
423 return certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
424 }).then([=] {
425 return server->start(msg->size(), use_dh_params).then([=]() {
426 sstring server_trust;
427 if (ca != tls::client_auth::NONE) {
428 server_trust = trust;
429 }
430 return server->invoke_on_all(&echoserver::listen, addr, crt, key, ca, server_trust);
431 }).then([=] {
432 return tls::connect(certs, addr, name).then([loops, msg, do_read](::connected_socket s) {
433 auto strms = ::make_lw_shared<streams>(std::move(s));
434 auto range = boost::irange(0, loops);
435 return do_for_each(range, [strms, msg](auto) {
436 auto f = strms->out.write(*msg);
437 return f.then([strms, msg]() {
438 return strms->out.flush().then([strms, msg] {
439 return strms->in.read_exactly(msg->size()).then([msg](temporary_buffer<char> buf) {
440 if (buf.empty()) {
441 throw std::runtime_error("Unexpected EOF");
442 }
443 sstring tmp(buf.begin(), buf.end());
444 BOOST_CHECK(*msg == tmp);
445 });
446 });
447 });
448 }).then_wrapped([strms, do_read] (future<> f1) {
449 // Always call close()
450 return (do_read ? strms->out.close() : make_ready_future<>()).then_wrapped([strms, f1 = std::move(f1)] (future<> f2) mutable {
451 // Verification errors will be reported by the call to output_stream::close(),
452 // which waits for the flush to actually happen. They can also be reported by the
453 // input_stream::read_exactly() call. We want to keep only one and avoid nested exception mess.
454 if (f1.failed()) {
455 (void)f2.handle_exception([] (std::exception_ptr ignored) { });
456 return std::move(f1);
457 }
458 (void)f1.handle_exception([] (std::exception_ptr ignored) { });
459 return f2;
460 }).finally([strms] { });
461 });
462 });
463 }).finally([server] {
464 return server->stop().finally([server]{});
465 });
466 });
467 }
468
469 /*
470 * Certificates:
471 *
472 * make -f tests/unit/mkcert.gmk domain=scylladb.org server=test
473 *
474 * -> test.crt
475 * test.csr
476 * catest.pem
477 * catest.key
478 *
479 * catest == snakeoil root authority for these self-signed certs
480 *
481 */
482 SEASTAR_TEST_CASE(test_simple_x509_client_server) {
483 // Make sure we load our own auth trust pem file, otherwise our certs
484 // will not validate
485 // Must match expected name with cert CA or give empty name to ignore
486 // server name
487 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
488 }
489
490 SEASTAR_TEST_CASE(test_simple_x509_client_server_again) {
491 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org");
492 }
493
494 #if GNUTLS_VERSION_NUMBER >= 0x030600
495 // Test #769 - do not set dh_params in server certs - let gnutls negotiate.
496 SEASTAR_TEST_CASE(test_simple_server_default_dhparams) {
497 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org",
498 certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE,
499 {}, {}, true, /* use_dh_params */ false
500 );
501 }
502 #endif
503
504 SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail) {
505 // Load a real trust authority here, which out certs are _not_ signed with.
506 return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), {}).then([] {
507 BOOST_FAIL("Should have gotten validation error");
508 }).handle_exception([](auto ep) {
509 try {
510 std::rethrow_exception(ep);
511 } catch (tls::verification_error&) {
512 // ok.
513 } catch (...) {
514 BOOST_FAIL("Unexpected exception");
515 }
516 });
517 }
518
519 SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail_name) {
520 // Use trust store with our signer, but wrong host name
521 return run_echo_test(message, 1, certfile("tls-ca-bundle.pem"), "nils.holgersson.gov").then([] {
522 BOOST_FAIL("Should have gotten validation error");
523 }).handle_exception([](auto ep) {
524 try {
525 std::rethrow_exception(ep);
526 } catch (tls::verification_error&) {
527 // ok.
528 } catch (...) {
529 BOOST_FAIL("Unexpected exception");
530 }
531 });
532 }
533
534 SEASTAR_TEST_CASE(test_large_message_x509_client_server) {
535 // Make sure we load our own auth trust pem file, otherwise our certs
536 // will not validate
537 // Must match expected name with cert CA or give empty name to ignore
538 // server name
539 sstring msg = uninitialized_string(512 * 1024);
540 for (size_t i = 0; i < msg.size(); ++i) {
541 msg[i] = '0' + char(i % 30);
542 }
543 return run_echo_test(std::move(msg), 20, certfile("catest.pem"), "test.scylladb.org");
544 }
545
546 SEASTAR_TEST_CASE(test_simple_x509_client_server_fail_client_auth) {
547 // Make sure we load our own auth trust pem file, otherwise our certs
548 // will not validate
549 // Must match expected name with cert CA or give empty name to ignore
550 // server name
551 // Server will require certificate auth. We supply none, so should fail connection
552 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE).then([] {
553 BOOST_FAIL("Expected exception");
554 }).handle_exception([](auto ep) {
555 // ok.
556 });
557 }
558
559 SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth) {
560 // Make sure we load our own auth trust pem file, otherwise our certs
561 // will not validate
562 // Must match expected name with cert CA or give empty name to ignore
563 // server name
564 // Server will require certificate auth. We supply one, so should succeed with connection
565 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"));
566 }
567
568 SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_with_dn_callback) {
569 // In addition to the above test, the certificate's subject and issuer
570 // Distinguished Names (DNs) will be checked for the occurrence of a specific
571 // substring (in this case, the test.scylladb.org url)
572 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type t, sstring subject, sstring issuer) {
573 BOOST_REQUIRE(t == tls::session_type::CLIENT);
574 BOOST_REQUIRE(subject.find("test.scylladb.org") != sstring::npos);
575 BOOST_REQUIRE(issuer.find("test.scylladb.org") != sstring::npos);
576 });
577 }
578
579 SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth_dn_callback_fails) {
580 // Test throwing an exception from within the Distinguished Names callback
581 return run_echo_test(message, 20, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::REQUIRE, certfile("test.crt"), certfile("test.key"), true, true, [](tls::session_type, sstring, sstring) {
582 throw tls::verification_error("to test throwing from within the callback");
583 }).then([] {
584 BOOST_FAIL("Should have gotten a verification_error exception");
585 }).handle_exception([](auto) {
586 // ok.
587 });
588 }
589
590 SEASTAR_TEST_CASE(test_many_large_message_x509_client_server) {
591 // Make sure we load our own auth trust pem file, otherwise our certs
592 // will not validate
593 // Must match expected name with cert CA or give empty name to ignore
594 // server name
595 sstring msg = uninitialized_string(4 * 1024 * 1024);
596 for (size_t i = 0; i < msg.size(); ++i) {
597 msg[i] = '0' + char(i % 30);
598 }
599 // Sending a huge-ish message a and immediately closing the session (see params)
600 // provokes case where tls::vec_push entered race and asserted on broken IO state
601 // machine.
602 auto range = boost::irange(0, 20);
603 return do_for_each(range, [msg = std::move(msg)](auto) {
604 return run_echo_test(std::move(msg), 1, certfile("catest.pem"), "test.scylladb.org", certfile("test.crt"), certfile("test.key"), tls::client_auth::NONE, {}, {}, false);
605 });
606 }
607
608 SEASTAR_THREAD_TEST_CASE(test_close_timout) {
609 tls::credentials_builder b;
610
611 b.set_x509_key_file(certfile("test.crt"), certfile("test.key"), tls::x509_crt_format::PEM).get();
612 b.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
613 b.set_dh_level();
614 b.set_system_trust().get();
615
616 auto creds = b.build_certificate_credentials();
617 auto serv = b.build_server_credentials();
618
619 semaphore sem(0);
620
621 class my_loopback_connected_socket_impl : public loopback_connected_socket_impl {
622 public:
623 semaphore& _sem;
624 bool _close = false;
625
626 my_loopback_connected_socket_impl(semaphore& s, lw_shared_ptr<loopback_buffer> tx, lw_shared_ptr<loopback_buffer> rx)
627 : loopback_connected_socket_impl(tx, rx)
628 , _sem(s)
629 {}
630 ~my_loopback_connected_socket_impl() {
631 _sem.signal();
632 }
633 class my_sink_impl : public data_sink_impl {
634 public:
635 data_sink _sink;
636 my_loopback_connected_socket_impl& _impl;
637 promise<> _p;
638 my_sink_impl(data_sink sink, my_loopback_connected_socket_impl& impl)
639 : _sink(std::move(sink))
640 , _impl(impl)
641 {}
642 future<> flush() override {
643 return _sink.flush();
644 }
645 using data_sink_impl::put;
646 future<> put(net::packet p) override {
647 if (std::exchange(_impl._close, false)) {
648 return _p.get_future().then([this, p = std::move(p)]() mutable {
649 return put(std::move(p));
650 });
651 }
652 return _sink.put(std::move(p));
653 }
654 future<> close() override {
655 _p.set_value();
656 return make_ready_future<>();
657 }
658 };
659 data_sink sink() override {
660 return data_sink(std::make_unique<my_sink_impl>(loopback_connected_socket_impl::sink(), *this));
661 }
662 };
663
664 auto constexpr iterations = 500;
665
666 for (int i = 0; i < iterations; ++i) {
667 auto b1 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::SERVER_TX);
668 auto b2 = ::make_lw_shared<loopback_buffer>(nullptr, loopback_buffer::type::CLIENT_TX);
669 auto ssi = std::make_unique<my_loopback_connected_socket_impl>(sem, b1, b2);
670 auto csi = std::make_unique<my_loopback_connected_socket_impl>(sem, b2, b1);
671
672 auto& ssir = *ssi;
673 auto& csir = *csi;
674
675 auto ss = tls::wrap_server(serv, connected_socket(std::move(ssi))).get0();
676 auto cs = tls::wrap_client(creds, connected_socket(std::move(csi))).get0();
677
678 auto os = cs.output().detach();
679 auto is = ss.input();
680
681 auto f1 = os.put(temporary_buffer<char>(10));
682 auto f2 = is.read();
683 f1.get();
684 f2.get();
685
686 // block further writes
687 ssir._close = true;
688 csir._close = true;
689 }
690
691 sem.wait(2 * iterations).get();
692 }
693
694 SEASTAR_THREAD_TEST_CASE(test_reload_certificates) {
695 tmpdir tmp;
696
697 namespace fs = std::filesystem;
698
699 // copy the wrong certs. We don't trust these
700 // blocking calls, but this is a test and seastar does not have a copy
701 // util and I am lazy...
702 fs::copy_file(certfile("other.crt"), tmp.path() / "test.crt");
703 fs::copy_file(certfile("other.key"), tmp.path() / "test.key");
704
705 auto cert = (tmp.path() / "test.crt").native();
706 auto key = (tmp.path() / "test.key").native();
707 std::unordered_set<sstring> changed;
708 promise<> p;
709
710 tls::credentials_builder b;
711 b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
712 b.set_dh_level();
713
714 auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
715 if (ep) {
716 return;
717 }
718 changed.insert(files.begin(), files.end());
719 if (changed.count(cert) && changed.count(key)) {
720 p.set_value();
721 }
722 }).get0();
723
724 ::listen_options opts;
725 opts.reuse_address = true;
726 auto addr = ::make_ipv4_address( {0x7f000001, 4712});
727 auto server = tls::listen(certs, addr, opts);
728
729 tls::credentials_builder b2;
730 b2.set_x509_trust_file(certfile("catest.pem"), tls::x509_crt_format::PEM).get();
731
732 {
733 auto sa = server.accept();
734 auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
735 auto s = sa.get0();
736 auto in = s.connection.input();
737
738 output_stream<char> out(c.output().detach(), 4096);
739
740 try {
741 out.write("apa").get();
742 auto f = out.flush();
743 auto f2 = in.read();
744
745 try {
746 f.get();
747 BOOST_FAIL("should not reach");
748 } catch (tls::verification_error&) {
749 // ok
750 }
751 try {
752 out.close().get();
753 } catch (...) {
754 }
755
756 try {
757 f2.get();
758 BOOST_FAIL("should not reach");
759 } catch (...) {
760 // ok
761 }
762 try {
763 in.close().get();
764 } catch (...) {
765 }
766 } catch (tls::verification_error&) {
767 // ok
768 }
769 }
770
771 // copy the right (trusted) certs over the old ones.
772 fs::copy_file(certfile("test.crt"), tmp.path() / "test0.crt");
773 fs::copy_file(certfile("test.key"), tmp.path() / "test0.key");
774
775 rename_file((tmp.path() / "test0.crt").native(), (tmp.path() / "test.crt").native()).get();
776 rename_file((tmp.path() / "test0.key").native(), (tmp.path() / "test.key").native()).get();
777
778 p.get_future().get();
779
780 // now it should work
781 {
782 auto sa = server.accept();
783 auto c = tls::connect(b2.build_certificate_credentials(), addr).get0();
784 auto s = sa.get0();
785 auto in = s.connection.input();
786
787 output_stream<char> out(c.output().detach(), 4096);
788
789 out.write("apa").get();
790 auto f = out.flush();
791 auto buf = in.read().get0();
792 f.get();
793 out.close().get();
794 in.read().get(); // ignore - just want eof
795 in.close().get();
796
797 BOOST_CHECK_EQUAL(sstring(buf.begin(), buf.end()), "apa");
798 }
799 }
800
801 SEASTAR_THREAD_TEST_CASE(test_reload_broken_certificates) {
802 tmpdir tmp;
803
804 namespace fs = std::filesystem;
805
806 fs::copy_file(certfile("test.crt"), tmp.path() / "test.crt");
807 fs::copy_file(certfile("test.key"), tmp.path() / "test.key");
808
809 auto cert = (tmp.path() / "test.crt").native();
810 auto key = (tmp.path() / "test.key").native();
811 std::unordered_set<sstring> changed;
812 promise<> p;
813
814 tls::credentials_builder b;
815 b.set_x509_key_file(cert, key, tls::x509_crt_format::PEM).get();
816 b.set_dh_level();
817
818 queue<std::exception_ptr> q(10);
819
820 auto certs = b.build_reloadable_server_credentials([&](const std::unordered_set<sstring>& files, std::exception_ptr ep) {
821 if (ep) {
822 q.push(std::move(ep));
823 return;
824 }
825 changed.insert(files.begin(), files.end());
826 if (changed.count(cert) && changed.count(key)) {
827 p.set_value();
828 }
829 }).get0();
830
831 // very intentionally use blocking calls. We want all our modifications to happen
832 // before any other continuation is allowed to process.
833
834 fs::remove(cert);
835 fs::remove(key);
836
837 // should get one or two exceptions
838 q.pop_eventually().get();
839
840 fs::copy_file(certfile("test.crt"), cert);
841 fs::copy_file(certfile("test.key"), key);
842
843 // now it should reload
844 p.get_future().get();
845 }