summaryrefslogtreecommitdiffstats
path: root/src/seastar/tests/unit/tls_test.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
commit483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch)
treee5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/seastar/tests/unit/tls_test.cc
parentInitial commit. (diff)
downloadceph-upstream.tar.xz
ceph-upstream.zip
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/seastar/tests/unit/tls_test.cc532
1 files changed, 532 insertions, 0 deletions
diff --git a/src/seastar/tests/unit/tls_test.cc b/src/seastar/tests/unit/tls_test.cc
new file mode 100644
index 00000000..929d2361
--- /dev/null
+++ b/src/seastar/tests/unit/tls_test.cc
@@ -0,0 +1,532 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Copyright (C) 2015 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/core/do_with.hh>
+#include "test_case.hh"
+#include <seastar/core/sstring.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/do_with.hh>
+#include <seastar/core/future-util.hh>
+#include <seastar/core/sharded.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/core/gate.hh>
+#include <seastar/net/tls.hh>
+
+#if 0
+#include <gnutls/gnutls.h>
+
+static void enable_gnutls_logging() {
+ gnutls_global_set_log_level(99);
+ gnutls_global_set_log_function([](int lv, const char * msg) {
+ std::cerr << "GNUTLS (" << lv << ") " << msg << std::endl;
+ });
+}
+#endif
+
+using namespace seastar;
+
+static future<> connect_to_ssl_addr(::shared_ptr<tls::certificate_credentials> certs, ipv4_addr addr) {
+ return tls::connect(certs, addr, "www.google.com").then([](connected_socket s) {
+ return do_with(std::move(s), [](connected_socket& s) {
+ return do_with(s.output(), [&s](auto& os) {
+ static const sstring msg("GET / HTTP/1.0\r\n\r\n");
+ auto f = os.write(msg);
+ return f.then([&s, &os]() mutable {
+ auto f = os.flush();
+ return f.then([&s]() mutable {
+ return do_with(s.input(), [](auto& in) {
+ auto f = in.read();
+ return f.then([](temporary_buffer<char> buf) {
+ // std::cout << buf.get() << std::endl;
+
+ // Avoid passing a nullptr as an argument of strncmp().
+ // If the temporary_buffer is empty (e.g. due to the underlying TCP connection
+ // being reset) passing the buf.get() (which would be a nullptr) to strncmp()
+ // causes a runtime error which masks the actual issue.
+ if (buf) {
+ BOOST_CHECK(strncmp(buf.get(), "HTTP/", 5) == 0);
+ }
+ BOOST_CHECK(buf.size() > 8);
+ });
+ });
+ });
+ }).finally([&os] {
+ return os.close();
+ });
+ });
+ });
+ });
+}
+
+static future<> connect_to_ssl_google(::shared_ptr<tls::certificate_credentials> certs) {
+ auto addr = make_ipv4_address(ipv4_addr("216.58.209.132:443"));
+ return connect_to_ssl_addr(std::move(certs), addr);
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_x509_trust_file("tests/unit/tls-ca-bundle.pem", tls::x509_crt_format::PEM).then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_system_trust) {
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ return certs->set_system_trust().then([certs]() {
+ return connect_to_ssl_google(certs);
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust) {
+ tls::credentials_builder b;
+ b.set_system_trust();
+ return connect_to_ssl_google(b.build_certificate_credentials());
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_builder_system_trust_multiple) {
+ tls::credentials_builder b;
+ b.set_system_trust();
+ auto creds = b.build_certificate_credentials();
+
+ return parallel_for_each(boost::irange(0, 20), [creds](auto i) { return connect_to_ssl_google(creds); });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings) {
+ static std::vector<sstring> prios( { "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128", // means normal ciphers plus ARCFOUR-128.
+ "SECURE128:-VERS-SSL3.0:+COMP-DEFLATE", // means that only secure ciphers are enabled, SSL3.0 is disabled, and libz compression enabled.
+ "NONE:+VERS-TLS-ALL:+AES-128-CBC:+RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1",
+ "NONE:+VERS-TLS-ALL:+AES-128-CBC:+ECDHE-RSA:+SHA1:+COMP-NULL:+SIGN-RSA-SHA1:+CURVE-SECP256R1",
+ "SECURE256:+SECURE128",
+ "NORMAL:%COMPAT",
+ "NORMAL:-MD5",
+ "NONE:+VERS-TLS-ALL:+MAC-ALL:+RSA:+AES-128-CBC:+SIGN-ALL:+COMP-NULL",
+ "NORMAL:+ARCFOUR-128",
+ "SECURE128:-VERS-TLS1.0:+COMP-DEFLATE",
+ "SECURE128:+SECURE192:-VERS-TLS-ALL:+VERS-TLS1.2"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ b.set_system_trust();
+ b.set_priority_string(prio);
+ return connect_to_ssl_google(b.build_certificate_credentials());
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_with_priority_strings_fail) {
+ static std::vector<sstring> prios( { "NONE",
+ "NONE:+CURVE-SECP256R1"
+ });
+ return do_for_each(prios, [](const sstring & prio) {
+ tls::credentials_builder b;
+ b.set_system_trust();
+ b.set_priority_string(prio);
+ return connect_to_ssl_google(b.build_certificate_credentials()).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+ });
+}
+
+SEASTAR_TEST_CASE(test_failed_connect) {
+ tls::credentials_builder b;
+ b.set_system_trust();
+ return connect_to_ssl_addr(b.build_certificate_credentials(), ipv4_addr()).handle_exception([](auto) {});
+}
+
+SEASTAR_TEST_CASE(test_non_tls) {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = engine().listen(addr, opts);
+
+ auto c = server.accept();
+
+ tls::credentials_builder b;
+ b.set_system_trust();
+
+ auto f = connect_to_ssl_addr(b.build_certificate_credentials(), addr);
+
+
+ return c.then([this, f = std::move(f)](::connected_socket s, socket_address) mutable {
+ std::cerr << "Established connection" << std::endl;
+ auto sp = std::make_unique<::connected_socket>(std::move(s));
+ timer<> t([s = std::ref(*sp)] {
+ std::cerr << "Killing server side" << std::endl;
+ s.get() = ::connected_socket();
+ });
+ t.arm(timer<>::clock::now() + std::chrono::seconds(5));
+ return std::move(f).finally([t = std::move(t), sp = std::move(sp)] {});
+ }).handle_exception([server = std::move(server)](auto ep) {
+ std::cerr << "Got expected exception" << std::endl;
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_before_handshake) {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ return certs->set_x509_key_file("tests/unit/test.crt", "tests/unit/test.key", tls::x509_crt_format::PEM).then([certs] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+ auto c = server.accept();
+ BOOST_CHECK(!c.available()); // should not be finished
+
+ server.abort_accept();
+
+ return c.then([](auto, auto) { BOOST_FAIL("Should not reach"); }).handle_exception([](auto) {
+ // ok
+ }).finally([server = std::move(server)] {});
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_after_handshake) {
+ return async([] {
+ auto certs = ::make_shared<tls::server_credentials>(::make_shared<tls::dh_params>());
+ certs->set_x509_key_file("tests/unit/test.crt", "tests/unit/test.key", tls::x509_crt_format::PEM).get();
+
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = tls::listen(certs, addr, opts);
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file("tests/unit/catest.pem", tls::x509_crt_format::PEM).get();
+
+ auto c = tls::connect(b.build_certificate_credentials(), addr).get0();
+ server.abort_accept(); // should not affect the socket we got.
+
+ auto s = sa.get0();
+ auto out = c.output();
+ auto in = s.input();
+
+ out.write("apa").get();
+ auto f = out.flush();
+ auto buf = in.read().get0();
+ f.get();
+ BOOST_CHECK(sstring(buf.begin(), buf.end()) == "apa");
+
+ out.close().get();
+ in.close().get();
+ });
+}
+
+SEASTAR_TEST_CASE(test_abort_accept_on_server_before_handshake) {
+ return async([] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+ auto addr = ::make_ipv4_address( {0x7f000001, 4712});
+ auto server = engine().listen(addr, opts);
+ auto sa = server.accept();
+
+ tls::credentials_builder b;
+ b.set_x509_trust_file("tests/unit/catest.pem", tls::x509_crt_format::PEM).get();
+
+ auto creds = b.build_certificate_credentials();
+ auto f = tls::connect(creds, addr);
+
+ server.abort_accept();
+ try {
+ sa.get();
+ } catch (...) {
+ }
+ server = {};
+
+ try {
+ // the connect as such should succeed, but the handshare following it
+ // should not.
+ auto c = f.get0();
+ auto out = c.output();
+ out.write("apa").get();
+ out.flush().get();
+ out.close().get();
+
+ BOOST_FAIL("Expected exception");
+ } catch (...) {
+ // ok
+ }
+ });
+}
+
+
+struct streams {
+ ::connected_socket s;
+ input_stream<char> in;
+ output_stream<char> out;
+
+ streams(::connected_socket cs) : s(std::move(cs)), in(s.input()), out(s.output())
+ {}
+};
+
+static const sstring message = "hej lilla fisk du kan dansa fint";
+
+class echoserver {
+ ::server_socket _socket;
+ ::shared_ptr<tls::server_credentials> _certs;
+ seastar::gate _gate;
+ bool _stopped = false;
+ size_t _size;
+public:
+ echoserver(size_t message_size)
+ : _certs(
+ ::make_shared<tls::server_credentials>(
+ ::make_shared<tls::dh_params>()))
+ , _size(message_size)
+ {}
+
+ future<> listen(socket_address addr, sstring crtfile, sstring keyfile, tls::client_auth ca = tls::client_auth::NONE, sstring trust = {}) {
+ _certs->set_client_auth(ca);
+ auto f = _certs->set_x509_key_file(crtfile, keyfile, tls::x509_crt_format::PEM);
+ if (!trust.empty()) {
+ f = f.then([this, trust = std::move(trust)] {
+ return _certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ });
+ }
+ return f.then([this, addr] {
+ ::listen_options opts;
+ opts.reuse_address = true;
+
+ _socket = tls::listen(_certs, addr, opts);
+
+ with_gate(_gate, [this] {
+ return _socket.accept().then([this](::connected_socket s, socket_address) {
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ return repeat([strms, this]() {
+ return strms->in.read_exactly(_size).then([strms](temporary_buffer<char> buf) {
+ if (buf.empty()) {
+ return make_ready_future<stop_iteration>(stop_iteration::yes);
+ }
+ sstring tmp(buf.begin(), buf.end());
+ return strms->out.write(tmp).then([strms]() {
+ return strms->out.flush();
+ }).then([] {
+ return make_ready_future<stop_iteration>(stop_iteration::no);
+ });
+ });
+ }).finally([strms]{
+ return strms->out.close();
+ }).finally([strms]{});
+ }).handle_exception([this](auto ep) {
+ if (_stopped) {
+ return make_ready_future<>();
+ }
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error &) {
+ // assume ok
+ return make_ready_future<>();
+ }
+ return make_exception_future(std::move(ep));
+ });
+ });
+ return make_ready_future<>();
+ });
+ }
+
+ future<> stop() {
+ _stopped = true;
+ _socket.abort_accept();
+ return _gate.close().handle_exception([] (std::exception_ptr ignored) { });
+ }
+};
+
+static future<> run_echo_test(sstring message,
+ int loops,
+ sstring trust,
+ sstring name,
+ sstring crt = "tests/unit/test.crt",
+ sstring key = "tests/unit/test.key",
+ tls::client_auth ca = tls::client_auth::NONE,
+ sstring client_crt = {},
+ sstring client_key = {},
+ bool do_read = true
+)
+{
+ static const auto port = 4711;
+
+ auto msg = ::make_shared<sstring>(std::move(message));
+ auto certs = ::make_shared<tls::certificate_credentials>();
+ auto server = ::make_shared<seastar::sharded<echoserver>>();
+ auto addr = ::make_ipv4_address( {0x7f000001, port});
+
+ assert(do_read || loops == 1);
+
+ future<> f = make_ready_future();
+
+ if (!client_crt.empty() && !client_key.empty()) {
+ f = certs->set_x509_key_file(client_crt, client_key, tls::x509_crt_format::PEM);
+ }
+
+ return f.then([=] {
+ return certs->set_x509_trust_file(trust, tls::x509_crt_format::PEM);
+ }).then([=] {
+ return server->start(msg->size()).then([=]() {
+ sstring server_trust;
+ if (ca != tls::client_auth::NONE) {
+ server_trust = trust;
+ }
+ return server->invoke_on_all(&echoserver::listen, addr, crt, key, ca, server_trust);
+ }).then([=] {
+ return tls::connect(certs, addr, name).then([loops, msg, do_read](::connected_socket s) {
+ auto strms = ::make_lw_shared<streams>(std::move(s));
+ auto range = boost::irange(0, loops);
+ return do_for_each(range, [strms, msg, do_read](auto) {
+ auto f = strms->out.write(*msg);
+ return f.then([strms, msg]() {
+ return strms->out.flush().then([strms, msg] {
+ return strms->in.read_exactly(msg->size()).then([msg](temporary_buffer<char> buf) {
+ sstring tmp(buf.begin(), buf.end());
+ BOOST_CHECK(*msg == tmp);
+ });
+ });
+ });
+ }).then_wrapped([strms, do_read] (future<> f1) {
+ // Always call close()
+ return (do_read ? strms->out.close() : make_ready_future<>()).then_wrapped([strms, f1 = std::move(f1)] (future<> f2) mutable {
+ // Verification errors will be reported by the call to output_stream::close(),
+ // which waits for the flush to actually happen. They can also be reported by the
+ // input_stream::read_exactly() call. We want to keep only one and avoid nested exception mess.
+ if (f1.failed()) {
+ f2.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(f1);
+ }
+ f1.handle_exception([] (std::exception_ptr ignored) { });
+ return std::move(f2);
+ }).finally([strms] { });
+ });
+ });
+ }).finally([server] {
+ return server->stop().finally([server]{});
+ });
+ });
+}
+
+/*
+ * Certificates:
+ *
+ * make -f tests/unit/mkcert.gmk domain=scylladb.org server=test
+ *
+ * -> test.crt
+ * test.csr
+ * catest.pem
+ * catest.key
+ *
+ * catest == snakeoil root authority for these self-signed certs
+ *
+ */
+SEASTAR_TEST_CASE(test_simple_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ return run_echo_test(message, 20, "tests/unit/catest.pem", "test.scylladb.org");
+}
+
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_again) {
+ return run_echo_test(message, 20, "tests/unit/catest.pem", "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail) {
+ // Load a real trust authority here, which out certs are _not_ signed with.
+ return run_echo_test(message, 1, "tests/unit/tls-ca-bundle.pem", {}).then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_x509_client_server_cert_validation_fail_name) {
+ // Use trust store with our signer, but wrong host name
+ return run_echo_test(message, 1, "tests/unit/tls-ca-bundle.pem", "nils.holgersson.gov").then([] {
+ BOOST_FAIL("Should have gotten validation error");
+ }).handle_exception([](auto ep) {
+ try {
+ std::rethrow_exception(ep);
+ } catch (tls::verification_error&) {
+ // ok.
+ } catch (...) {
+ BOOST_FAIL("Unexpected exception");
+ }
+ });
+}
+
+SEASTAR_TEST_CASE(test_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg(sstring::initialized_later(), 512 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ return run_echo_test(std::move(msg), 20, "tests/unit/catest.pem", "test.scylladb.org");
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_fail_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply none, so should fail connection
+ return run_echo_test(message, 20, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/test.key", tls::client_auth::REQUIRE).then([] {
+ BOOST_FAIL("Expected exception");
+ }).handle_exception([](auto ep) {
+ // ok.
+ });
+}
+
+SEASTAR_TEST_CASE(test_simple_x509_client_server_client_auth) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ // Server will require certificate auth. We supply one, so should succeed with connection
+ return run_echo_test(message, 20, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/test.key", tls::client_auth::REQUIRE, "tests/unit/test.crt", "tests/unit/test.key");
+}
+
+SEASTAR_TEST_CASE(test_many_large_message_x509_client_server) {
+ // Make sure we load our own auth trust pem file, otherwise our certs
+ // will not validate
+ // Must match expected name with cert CA or give empty name to ignore
+ // server name
+ sstring msg(sstring::initialized_later(), 4 * 1024 * 1024);
+ for (size_t i = 0; i < msg.size(); ++i) {
+ msg[i] = '0' + char(i % 30);
+ }
+ // Sending a huge-ish message a and immediately closing the session (see params)
+ // provokes case where tls::vec_push entered race and asserted on broken IO state
+ // machine.
+ auto range = boost::irange(0, 20);
+ return do_for_each(range, [msg = std::move(msg)](auto) {
+ return run_echo_test(std::move(msg), 1, "tests/unit/catest.pem", "test.scylladb.org", "tests/unit/test.crt", "tests/unit/test.key", tls::client_auth::NONE, {}, {}, false);
+ });
+}
+