diff options
author | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2022-03-17 16:10:32 +0300 |
---|---|---|
committer | Niall Douglas (s [underscore] sourceforge {at} nedprod [dot] com) <spamtrap@nedprod.com> | 2022-04-06 18:43:45 +0300 |
commit | 6fa9b70d22823c49e7885af092ba86c6761b70a4 (patch) | |
tree | 25db34313f0d319505436ce3de34a856ad6a5806 | |
parent | c5b52a6436878e04958d37fe36c93825bd455038 (diff) |
networking: Test authenticated and non-blocking TLS connections.
-rw-r--r-- | include/llfio/revision.hpp | 6 | ||||
-rw-r--r-- | include/llfio/v2.0/byte_socket_handle.hpp | 11 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/tls_socket_sources/openssl.ipp | 109 | ||||
-rw-r--r-- | include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp | 32 | ||||
-rw-r--r-- | include/llfio/v2.0/tls_socket_handle.hpp | 2 | ||||
-rw-r--r-- | test/tests/tls_socket_handle.cpp | 185 |
6 files changed, 250 insertions, 95 deletions
diff --git a/include/llfio/revision.hpp b/include/llfio/revision.hpp index 996e3062..91700c56 100644 --- a/include/llfio/revision.hpp +++ b/include/llfio/revision.hpp @@ -1,4 +1,4 @@ // Note the second line of this file must ALWAYS be the git SHA, third line ALWAYS the git SHA update time -#define LLFIO_PREVIOUS_COMMIT_REF 6fb38a70ae35a3599d2b5de86663bb2f1d47e5d2 -#define LLFIO_PREVIOUS_COMMIT_DATE "2022-02-23 11:09:43 +00:00" -#define LLFIO_PREVIOUS_COMMIT_UNIQUE 6fb38a70 +#define LLFIO_PREVIOUS_COMMIT_REF 5720755175521bdd85f7361210de2038419b5628 +#define LLFIO_PREVIOUS_COMMIT_DATE "2022-03-17 13:10:37 +00:00" +#define LLFIO_PREVIOUS_COMMIT_UNIQUE 57207551 diff --git a/include/llfio/v2.0/byte_socket_handle.hpp b/include/llfio/v2.0/byte_socket_handle.hpp index c4862e89..d0d3d31d 100644 --- a/include/llfio/v2.0/byte_socket_handle.hpp +++ b/include/llfio/v2.0/byte_socket_handle.hpp @@ -599,8 +599,11 @@ public: flag flags = flag::none) noexcept; //! \brief Convenience function defaulting `flag::multiplexable` set. LLFIO_MAKE_FREE_FUNCTION - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<byte_socket_handle> - multiplexable_byte_socket(ip::family family, mode _mode = mode::write, caching _caching = caching::all, flag flags = flag::multiplexable) noexcept; + static result<byte_socket_handle> multiplexable_byte_socket(ip::family family, mode _mode = mode::write, caching _caching = caching::all, + flag flags = flag::multiplexable) noexcept + { + return byte_socket(family, _mode, _caching, flags); + } LLFIO_HEADERS_ONLY_VIRTUAL_SPEC ~byte_socket_handle() override { @@ -1102,8 +1105,8 @@ public: caching _caching = caching::all, flag flags = flag::none) noexcept; //! \brief Convenience function defaulting `flag::multiplexable` set. LLFIO_MAKE_FREE_FUNCTION - static LLFIO_HEADERS_ONLY_MEMFUNC_SPEC result<listening_socket_handle> - multiplexable_listening_socket(ip::family _family, mode _mode = mode::write, caching _caching = caching::all, flag flags = flag::multiplexable) noexcept + static result<listening_socket_handle> multiplexable_listening_socket(ip::family _family, mode _mode = mode::write, caching _caching = caching::all, + flag flags = flag::multiplexable) noexcept { return listening_socket(_family, _mode, _caching, flags); } diff --git a/include/llfio/v2.0/detail/impl/tls_socket_sources/openssl.ipp b/include/llfio/v2.0/detail/impl/tls_socket_sources/openssl.ipp index a868650e..e7d3e017 100644 --- a/include/llfio/v2.0/detail/impl/tls_socket_sources/openssl.ipp +++ b/include/llfio/v2.0/detail/impl/tls_socket_sources/openssl.ipp @@ -24,16 +24,22 @@ Distributed under the Boost Software License, Version 1.0. #include "../../../tls_socket_handle.hpp" -#define LLFIO_OPENSSL_ENABLE_DEBUG_PRINTING 0 +#define LLFIO_OPENSSL_ENABLE_DEBUG_PRINTING 1 #include <openssl/crypto.h> #include <openssl/err.h> #include <openssl/ssl.h> +#include <openssl/x509.h> #if LLFIO_OPENSSL_ENABLE_DEBUG_PRINTING #include <iostream> #endif +#ifdef _WIN32 +#include <cryptuiapi.h> +#pragma comment(lib, "cryptui.lib") +#endif + LLFIO_V2_NAMESPACE_BEGIN namespace detail @@ -396,8 +402,14 @@ namespace detail static struct openssl_default_ctxs_t { SSL_CTX *unverified{nullptr}, *verified{nullptr}; + X509_STORE *certstore{nullptr}; ~openssl_default_ctxs_t() { + if(certstore != nullptr) + { + X509_STORE_free(certstore); + certstore = nullptr; + } if(verified != nullptr) { SSL_CTX_free(verified); @@ -417,8 +429,42 @@ namespace detail QUICKCPPLIB_NAMESPACE::configurable_spinlock::lock_guard<QUICKCPPLIB_NAMESPACE::configurable_spinlock::spinlock<unsigned>> g(lock); if(verified == nullptr) { - auto make_ctx = [](bool verify_peer) -> result<SSL_CTX *> +#ifdef _WIN32 + // Create an OpenSSL certificate store made up of the certs from the Windows certificate store + HCERTSTORE winstore = CertOpenSystemStoreW(NULL, L"ROOT"); + if(!winstore) + { + return win32_error(); + } + auto unwinstore = make_scope_exit([&]() noexcept { CertCloseStore(winstore, 0); }); + certstore = X509_STORE_new(); + if(!certstore) { + return openssl_error(nullptr); + } + PCCERT_CONTEXT context = nullptr; + auto uncontext = make_scope_exit([&]() noexcept { + if(context != nullptr) + { + CertFreeCertificateContext(context); + } + }); + while((context = CertEnumCertificatesInStore(winstore, context)) != nullptr) + { + const unsigned char *in = (const unsigned char *) context->pbCertEncoded; + X509 *x509 = d2i_X509(nullptr, &in, context->cbCertEncoded); + if(!x509) + { + return openssl_error(nullptr); + } + auto unx509 = make_scope_exit([&]() noexcept { X509_free(x509); }); + if(X509_STORE_add_cert(certstore, x509) <= 0) + { + return openssl_error(nullptr); + } + } +#endif + auto make_ctx = [certstore = certstore](bool verify_peer) -> result<SSL_CTX *> { SSL_CTX *_ctx = SSL_CTX_new(TLS_method()); if(_ctx == nullptr) { @@ -434,13 +480,17 @@ namespace detail } else { + if(certstore != nullptr) + { + SSL_CTX_set1_cert_store(_ctx, certstore); + } if(!SSL_CTX_set_cipher_list(_ctx, openssl_verified_cipher_list)) { return openssl_error(nullptr).as_failure(); } SSL_CTX_set_verify(_ctx, SSL_VERIFY_PEER, nullptr); SSL_CTX_set_verify_depth(_ctx, 4); - if(!SSL_CTX_set_default_verify_paths(_ctx)) + if(SSL_CTX_set_default_verify_paths(_ctx) <= 0) { return openssl_error(nullptr).as_failure(); } @@ -670,7 +720,7 @@ protected: LLFIO_DEADLINE_TO_SLEEP_INIT(d); if(_ssl_bio == nullptr) { - OUTCOME_TRY(_init(true, _authentication_certificates_path && !_authentication_certificates_path->empty())); + OUTCOME_TRY(_init(true, _authentication_certificates_path)); } if(!(_v.behaviour & native_handle_type::disposition::_is_connected)) { @@ -747,9 +797,10 @@ public: this->_v.behaviour = (sock->native_handle().behaviour & ~(native_handle_type::disposition::kernel_handle)) | native_handle_type::disposition::is_pointer; } - result<void> _init(bool is_client, bool verify_peer) noexcept + result<void> _init(bool is_client, const optional<filesystem::path> &certpath) noexcept { LLFIO_LOG_FUNCTION_CALL(this); + const bool verify_peer = (is_client && (!certpath.has_value() || !certpath->empty())) || (!is_client && (certpath.has_value() || !certpath->empty())); OUTCOME_TRY(detail::openssl_default_ctxs.init()); assert(_ssl_bio == nullptr); _ssl_bio = BIO_new_ssl(verify_peer ? detail::openssl_default_ctxs.verified : detail::openssl_default_ctxs.unverified, is_client); @@ -757,6 +808,23 @@ public: { return openssl_error(this).as_failure(); } + if(certpath.has_value() && !certpath->empty()) + { + SSL *ssl{nullptr}; + BIO_get_ssl(_ssl_bio, &ssl); + if(ssl == nullptr) + { + return openssl_error(this).as_failure(); + } + if(SSL_use_certificate_file(ssl, certpath->string().c_str(), SSL_FILETYPE_PEM) <= 0) + { + return openssl_error(this).as_failure(); + } + if(SSL_use_PrivateKey_file(ssl, certpath->string().c_str(), SSL_FILETYPE_PEM) <= 0) + { + return openssl_error(this).as_failure(); + } + } _self_bio = BIO_new(detail::openssl_custom_bio.method); if(_self_bio == nullptr) { @@ -962,28 +1030,28 @@ public: _connect_hostname_port.assign(host.data(), host.size()); _connect_hostname_port.push_back(':'); _connect_hostname_port.append(std::to_string(port)); - auto res = BIO_set_conn_hostname(_ssl_bio, _connect_hostname_port.c_str()); - if(res != 1) - { - return openssl_error(this).as_failure(); - } if(_ctx == nullptr) { - OUTCOME_TRY(_init(true, true)); + OUTCOME_TRY(_init(true, _authentication_certificates_path)); } + auto res = BIO_set_conn_hostname(_ssl_bio, _connect_hostname_port.c_str()); + /* if(res != 1) + { + return openssl_error(this).as_failure(); + }*/ SSL *ssl{nullptr}; BIO_get_ssl(_ssl_bio, &ssl); if(ssl == nullptr) { return openssl_error(this).as_failure(); } - auto hostname = _connect_hostname_port.substr(0, _connect_hostname_port.rfind(':')); + std::string hostname(host); res = SSL_set_tlsext_host_name(ssl, hostname.c_str()); if(res != 1) { return openssl_error(this).as_failure(); } - return _connect_hostname_port; + return string_view(_connect_hostname_port).substr(host.size() + 1); } catch(...) { @@ -995,13 +1063,11 @@ public: int _bread(BIO *bio, char *buffer, size_t bytes, size_t *read) { LLFIO_LOG_FUNCTION_CALL(this); - auto ret = [=]() mutable - { + auto ret = [=]() mutable { assert(_lock_holder.owns_lock()); *read = 0; BIO_clear_retry_flags(bio); - auto copy_out = [&] - { + auto copy_out = [&] { while(!_toread_source_empty() && bytes > 0) { auto s = _toread_source(); @@ -1036,7 +1102,7 @@ public: } return 1; } - auto remaining = (size_t) (((*s.first)->data() + (*s.first)->size()) - (s.second->data() + s.second->size())); + auto remaining = (size_t)(((*s.first)->data() + (*s.first)->size()) - (s.second->data() + s.second->size())); byte_socket_handle::buffer_type b{s.second->data() + s.second->size(), remaining}; auto &began_steady = _read_deadline_began_steady; deadline nd; @@ -1096,8 +1162,7 @@ public: int _bwrite(BIO *bio, const char *buffer, size_t bytes, size_t *written) { LLFIO_LOG_FUNCTION_CALL(this); - auto ret = [=]() mutable - { + auto ret = [=]() mutable { assert(_lock_holder.owns_lock()); *written = 0; BIO_clear_retry_flags(bio); @@ -1205,7 +1270,7 @@ protected: } req.buffers.connected_socket() = {tls_socket_handle_ptr(p), read.connected_socket().second}; OUTCOME_TRY(p->set_registered_buffer_chunk_size(_registered_buffer_chunk_size)); - OUTCOME_TRY(p->_init(false, !_authentication_certificates_path || !_authentication_certificates_path->empty())); + OUTCOME_TRY(p->_init(false, _authentication_certificates_path)); return {std::move(req.buffers)}; } @@ -1225,7 +1290,7 @@ protected: } req.buffers.connected_socket() = {tls_socket_handle_ptr(p), read.connected_socket().second}; OUTCOME_TRY(p->set_registered_buffer_chunk_size(_registered_buffer_chunk_size)); - OUTCOME_TRY(p->_init(false, !_authentication_certificates_path || !_authentication_certificates_path->empty())); + OUTCOME_TRY(p->_init(false, _authentication_certificates_path)); return {std::move(req.buffers)}; } diff --git a/include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp b/include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp index 620d9aa6..a7f57e66 100644 --- a/include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp +++ b/include/llfio/v2.0/detail/impl/windows/byte_socket_handle.ipp @@ -88,6 +88,7 @@ namespace ip OVERLAPPED ol; ::ADDRINFOEXW *res{nullptr}; HANDLE ophandle{nullptr}; + bool done{false}; resolver_impl() { clear(); } @@ -107,12 +108,13 @@ namespace ip res = nullptr; } ophandle = nullptr; + done = false; } // Returns 0 for not ready yet, -1 for already processed, +1 for just processed int check(DWORD millis) { - if(0 != WaitForSingleObject(ol.hEvent, millis)) + if(!done && 0 != WaitForSingleObject(ol.hEvent, millis)) { return 0; } @@ -120,6 +122,7 @@ namespace ip { return -1; } + done = true; auto unaddrinfo = make_scope_exit( [&]() noexcept { @@ -188,7 +191,7 @@ namespace ip void resolver_deleter::operator()(resolver *_p) const { auto *p = static_cast<resolver_impl *>(_p); - if(0 != WaitForSingleObject(p->ol.hEvent, 0)) + if(!p->done && 0 != WaitForSingleObject(p->ol.hEvent, 0)) { GetAddrInfoExCancel(&p->ophandle); WaitForSingleObject(p->ol.hEvent, INFINITE); @@ -220,7 +223,7 @@ namespace ip bool resolver::incomplete() const noexcept { auto *self = static_cast<const detail::resolver_impl *>(this); - return 0 != WaitForSingleObject(self->ol.hEvent, 0); + return !self->done && 0 != WaitForSingleObject(self->ol.hEvent, 0); } result<span<address>> resolver::get() noexcept { @@ -326,9 +329,15 @@ namespace ip _timeout.tv_usec = (long) (diff % 1000000); } timeout = &_timeout; + // Can't combine blocking and timeouts + flags &= ~resolve_flag::blocking; } p->hints.ai_socktype = SOCK_STREAM; - p->hints.ai_flags = AI_ADDRCONFIG | AI_V4MAPPED; + p->hints.ai_flags = AI_ADDRCONFIG; + if(_family != family::v4) + { + p->hints.ai_flags |= AI_V4MAPPED; + } if(flags & resolve_flag::passive) { p->hints.ai_flags |= AI_PASSIVE; @@ -343,24 +352,29 @@ namespace ip return ntkernel_error(ntstat); } std::wstring ret; - ret.resize(written); + ret.resize(written / sizeof(wchar_t)); written = 0; // Do the conversion UTF-8 to UTF-16 - ntstat = RtlUTF8ToUnicodeN(const_cast<wchar_t *>(ret.data()), static_cast<ULONG>(ret.size()), &written, str.c_str(), static_cast<ULONG>(str.size())); + ntstat = RtlUTF8ToUnicodeN(const_cast<wchar_t *>(ret.data()), static_cast<ULONG>(ret.size() * sizeof(wchar_t)), &written, str.c_str(), + static_cast<ULONG>(str.size())); if(ntstat < 0) { return ntkernel_error(ntstat); } - ret.resize(written); + ret.resize(written / sizeof(wchar_t)); return ret; }; OUTCOME_TRY(auto &&_name, to_wstring(p->name)); OUTCOME_TRY(auto &&_service, to_wstring(p->service)); auto errcode = GetAddrInfoExW(_name.c_str(), _service.c_str(), NS_ALL, nullptr, &p->hints, &p->res, timeout, (flags & resolve_flag::blocking) ? nullptr : &p->ol, nullptr, (flags & resolve_flag::blocking) ? nullptr : &p->ophandle); - if(NO_ERROR != errcode && WSA_IO_PENDING != errcode) + if(NO_ERROR == errcode) + { + p->done = true; + } + else if(WSA_IO_PENDING != errcode) { - SetEvent(p->ol.hEvent); + p->done = true; return win32_error(errcode); } p->check(0); diff --git a/include/llfio/v2.0/tls_socket_handle.hpp b/include/llfio/v2.0/tls_socket_handle.hpp index 418ea068..3e87705a 100644 --- a/include/llfio/v2.0/tls_socket_handle.hpp +++ b/include/llfio/v2.0/tls_socket_handle.hpp @@ -147,7 +147,7 @@ public: { deadline nd; LLFIO_DEADLINE_TO_PARTIAL_DEADLINE(nd, d); - lasterror = byte_socket_handle::connect(address, nd); + lasterror = this->connect(address, nd); if(lasterror) { return lasterror; diff --git a/test/tests/tls_socket_handle.cpp b/test/tests/tls_socket_handle.cpp index ee78320c..591525da 100644 --- a/test/tests/tls_socket_handle.cpp +++ b/test/tests/tls_socket_handle.cpp @@ -89,7 +89,7 @@ static inline void TestBlockingTLSSocketHandles() g.unlock(); serversocket->close().value(); llfio::byte buffer[64]; - auto read = s.first->read(0, {{buffer, 64}}).value(); + auto read = s.first->read({{buffer, 64}}).value(); g.lock(); std::cout << "\nThe inbound server socket negotiated the cipher " << s.first->algorithms_description() << std::endl; BOOST_REQUIRE(read == 5); @@ -123,7 +123,7 @@ static inline void TestBlockingTLSSocketHandles() BOOST_CHECK(writer->is_writable()); std::cout << "\nThe connecting socket negotiated the cipher " << writer->algorithms_description() << std::endl; g.unlock(); - auto written = writer->write(0, {{(const llfio::byte *) "hello", 5}}).value(); + auto written = writer->write({{(const llfio::byte *) "hello", 5}}).value(); BOOST_REQUIRE(written == 5); writer->shutdown_and_close().value(); readerthread.get(); @@ -137,74 +137,144 @@ static inline void TestBlockingTLSSocketHandles() runtest(tls_socket_source->wrap(&rawserversocket).value(), tls_socket_source->wrap(&rawwriter).value()); } -#if 0 static inline void TestNonBlockingTLSSocketHandles() { namespace llfio = LLFIO_V2_NAMESPACE; - auto serversocket = llfio::listening_socket_handle::listening_socket(llfio::ip::family::v4, llfio::listening_socket_handle::mode::read, - llfio::byte_socket_handle::caching::all, llfio::byte_socket_handle::flag::multiplexable) - .value(); - BOOST_REQUIRE(serversocket.is_valid()); - BOOST_CHECK(serversocket.is_socket()); - BOOST_CHECK(serversocket.is_readable()); - BOOST_CHECK(!serversocket.is_writable()); - serversocket.bind(llfio::ip::address_v4::loopback()).value(); - auto endpoint = serversocket.local_endpoint().value(); - std::cout << "Server socket is listening on " << endpoint << std::endl; - if(endpoint.family() == llfio::ip::family::unknown && getenv("CI") != nullptr) + if(llfio::tls_socket_source_registry::empty()) { - std::cout << "\nNOTE: Currently on CI and couldn't bind a listening socket to loopback, assuming it is CI host restrictions and skipping this test." - << std::endl; + std::cout << "\nNOTE: This platform has no TLS socket sources in its registry, skipping this test." << std::endl; return; } + auto tls_socket_source = llfio::tls_socket_source_registry::default_source().instantiate().value(); + auto runtest = [](llfio::listening_tls_socket_handle_ptr serversocket, auto &&make_writer) + { + BOOST_REQUIRE(serversocket->is_valid()); + BOOST_CHECK(serversocket->is_socket()); + BOOST_CHECK(serversocket->is_readable()); + BOOST_CHECK(serversocket->is_writable()); + // Disable server authentication + serversocket->set_authentication_certificates_path({}).value(); + serversocket->bind(llfio::ip::address_v4::loopback()).value(); + auto endpoint = serversocket->local_endpoint().value(); + std::cout << "Server socket is listening on " << endpoint << std::endl; + if(endpoint.family() == llfio::ip::family::unknown && getenv("CI") != nullptr) + { + std::cout << "\nNOTE: Currently on CI and couldn't bind a listening socket to loopback, assuming it is CI host restrictions and skipping this test." + << std::endl; + return; + } - std::pair<llfio::byte_socket_handle, llfio::ip::address> reader; - { // no incoming, so non-blocking read should time out - auto read = serversocket.read({reader}, std::chrono::milliseconds(0)); - BOOST_REQUIRE(read.has_error()); - BOOST_REQUIRE(read.error() == llfio::errc::timed_out); - } - { // no incoming, so blocking read should time out - auto read = serversocket.read({reader}, std::chrono::seconds(1)); - BOOST_REQUIRE(read.has_error()); - BOOST_REQUIRE(read.error() == llfio::errc::timed_out); - } + std::pair<llfio::tls_socket_handle_ptr, llfio::ip::address> reader; + { // no incoming, so non-blocking read should time out + auto read = serversocket->read({reader}, std::chrono::milliseconds(0)); + BOOST_REQUIRE(read.has_error()); + BOOST_REQUIRE(read.error() == llfio::errc::timed_out); + } + { // no incoming, so blocking read should time out + auto read = serversocket->read({reader}, std::chrono::seconds(1)); + BOOST_REQUIRE(read.has_error()); + BOOST_REQUIRE(read.error() == llfio::errc::timed_out); + } + + // Form the connection. + llfio::tls_socket_handle_ptr writer = make_writer(); + writer->connect(endpoint).value(); + serversocket->read({reader}, std::chrono::seconds(1)).value(); + std::cout << "Server socket sees incoming connection from " << reader.second << std::endl; + + llfio::byte buffer[64]; + { // no data, so non-blocking read should time out + auto read = reader.first->read(0, {{buffer, 64}}, std::chrono::milliseconds(0)); + BOOST_REQUIRE(read.has_error()); + BOOST_REQUIRE(read.error() == llfio::errc::timed_out); + } + { // no data, so blocking read should time out + auto read = reader.first->read(0, {{buffer, 64}}, std::chrono::seconds(1)); + if(!read.has_error()) + { + std::cout << "Blocking read did not return error, instead returned " << read.value() << std::endl; + } + BOOST_REQUIRE(read.has_error()); + BOOST_REQUIRE(read.error() == llfio::errc::timed_out); + } + auto written = writer->write(0, {{(const llfio::byte *) "hello", 5}}).value(); + BOOST_REQUIRE(written == 5); + // writer.shutdown_and_close().value(); // would block until socket drained by reader + // writer.close().value(); // would cause all further reads to fail due to socket broken + auto read = reader.first->read(0, {{buffer, 64}}, std::chrono::milliseconds(1)); + BOOST_REQUIRE(read.value() == 5); + BOOST_CHECK(0 == memcmp(buffer, "hello", 5)); + writer->shutdown_and_close().value(); // must not block nor fail + writer->close().value(); + reader.first->close().value(); + }; + std::cout << "\nUnwrapped TLS socket:\n" << std::endl; + runtest(tls_socket_source->multiplexable_listening_socket(llfio::ip::family::v4).value(), + [&] { return tls_socket_source->multiplexable_connecting_socket(llfio::ip::family::v4).value(); }); - // Form the connection. - auto writer = llfio::byte_socket_handle::byte_socket(llfio::ip::family::v4, llfio::byte_socket_handle::mode::append, - llfio::byte_socket_handle::caching::reads, llfio::byte_socket_handle::flag::multiplexable) - .value(); - writer.connect(endpoint).value(); - serversocket.read({reader}, std::chrono::seconds(1)).value(); - std::cout << "Server socket sees incoming connection from " << reader.second << std::endl; + std::cout << "\nWrapped TLS socket:\n" << std::endl; + auto rawserversocket = llfio::listening_socket_handle::multiplexable_listening_socket(llfio::ip::family::v4).value(); + auto rawwriter = llfio::byte_socket_handle::multiplexable_byte_socket(llfio::ip::family::v4).value(); + runtest(tls_socket_source->wrap(&rawserversocket).value(), [&] { return tls_socket_source->wrap(&rawwriter).value(); }); +} - llfio::byte buffer[64]; - { // no data, so non-blocking read should time out - auto read = reader.first.read(0, {{buffer, 64}}, std::chrono::milliseconds(0)); - BOOST_REQUIRE(read.has_error()); - BOOST_REQUIRE(read.error() == llfio::errc::timed_out); +/* This test makes the assumption that the host OS is able to validate github.com's +TLS certificate. +*/ +static inline void TestAuthenticatingTLSSocketHandles() +{ + static constexpr const char *test_host = "github.com"; + static constexpr const char *get_request = R"(GET / HTTP/1.0 +Host: github.com + +)"; + namespace llfio = LLFIO_V2_NAMESPACE; + if(llfio::tls_socket_source_registry::empty()) + { + std::cout << "\nNOTE: This platform has no TLS socket sources in its registry, skipping this test." << std::endl; + return; + } + auto test_host_ip = llfio::ip::resolve(test_host, "https", llfio::ip::family::any, {}, llfio::ip::resolve_flag::blocking).value()->get().value(); + std::cout << "The IP address of " << test_host << " is " << test_host_ip.front() << std::endl; + auto tls_socket_source = llfio::tls_socket_source_registry::default_source().instantiate().value(); + auto sock = tls_socket_source->multiplexable_connecting_socket(llfio::ip::family::any).value(); + { + auto r = sock->connect(test_host, 443, std::chrono::seconds(5)); + if(!r) + { + if(r.error() == llfio::errc::timed_out || r.error() == llfio::errc::host_unreachable || r.error() == llfio::errc::network_unreachable) + { + std::cout << "\nNOTE: Failed to connect to " << test_host + << " within five seconds, assuming there is no internet connection and skipping this test. Error was: " << r.error().message() << std::endl; + return; + } + r.value(); + } } - { // no data, so blocking read should time out - auto read = reader.first.read(0, {{buffer, 64}}, std::chrono::seconds(1)); - if(!read.has_error()) + // Get the front page + std::cout << "\nThe socket which connected to " << test_host << " negotiated the cipher " << sock->algorithms_description() << std::endl; + auto written = sock->write({{(const llfio::byte *) get_request, strlen(get_request)}}).value(); + BOOST_REQUIRE(written == strlen(get_request)); + // Fetch the front page. The connection will close once all data is sent. + std::vector<char> buffer(4096); + size_t offset = 0; + for(size_t readed = 0; (readed = sock->read({{(llfio::byte *) buffer.data() + offset, buffer.size() - offset}}, std::chrono::seconds(3)).value()) > 0;) + { + offset += readed; + if(buffer.size() - offset < 1024) { - std::cout << "Blocking read did not return error, instead returned " << read.value() << std::endl; + buffer.resize(buffer.size() + 4096); } - BOOST_REQUIRE(read.has_error()); - BOOST_REQUIRE(read.error() == llfio::errc::timed_out); } - auto written = writer.write(0, {{(const llfio::byte *) "hello", 5}}).value(); - BOOST_REQUIRE(written == 5); - // writer.shutdown_and_close().value(); // would block until socket drained by reader - // writer.close().value(); // would cause all further reads to fail due to socket broken - auto read = reader.first.read(0, {{buffer, 64}}, std::chrono::milliseconds(1)); - BOOST_REQUIRE(read.value() == 5); - BOOST_CHECK(0 == memcmp(buffer, "hello", 5)); - writer.shutdown_and_close().value(); // must not block nor fail - writer.close().value(); - reader.first.close().value(); + buffer.resize(offset); + std::cout << "\nRead from " << test_host << " " << offset << " bytes. The first 1024 bytes are:\n\n" + << llfio::string_view(buffer.data(), offset).substr(0, 1024) << "\n" + << std::endl; + // Make sure this doesn't hang because the socket is closed + sock->shutdown_and_close().value(); } +#if 0 #if LLFIO_ENABLE_TEST_IO_MULTIPLEXERS static inline void TestMultiplexedTLSSocketHandles() { @@ -675,9 +745,12 @@ static inline void TestPollingTLSSocketHandles() KERNELTEST_TEST_KERNEL(integration, llfio, tls_socket_handle, blocking, "Tests that blocking llfio::tls_byte_socket_handle works as expected", TestBlockingTLSSocketHandles()) -#if 0 KERNELTEST_TEST_KERNEL(integration, llfio, tls_socket_handle, nonblocking, "Tests that nonblocking llfio::tls_byte_socket_handle works as expected", TestNonBlockingTLSSocketHandles()) +KERNELTEST_TEST_KERNEL(integration, llfio, tls_socket_handle, authenticating, + "Tests that connecting to an authenticating server using llfio::tls_byte_socket_handle works as expected", + TestAuthenticatingTLSSocketHandles()) +#if 0 #if LLFIO_ENABLE_TEST_IO_MULTIPLEXERS KERNELTEST_TEST_KERNEL(integration, llfio, tls_socket_handle, multiplexed, "Tests that multiplexed llfio::tls_byte_socket_handle works as expected", TestMultiplexedTLSSocketHandles()) |