diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 14:53:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 14:53:22 +0000 |
commit | 52c021ee0b0c6ad2128ed550c694aad0d11d4c3f (patch) | |
tree | 83cf8627b94336cf4bee7479b9749263bbfd3a06 /src/lib/asiolink/tests/tls_unittest.cc | |
parent | Initial commit. (diff) | |
download | isc-kea-52c021ee0b0c6ad2128ed550c694aad0d11d4c3f.tar.xz isc-kea-52c021ee0b0c6ad2128ed550c694aad0d11d4c3f.zip |
Adding upstream version 2.5.7.upstream/2.5.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/asiolink/tests/tls_unittest.cc')
-rw-r--r-- | src/lib/asiolink/tests/tls_unittest.cc | 2687 |
1 files changed, 2687 insertions, 0 deletions
diff --git a/src/lib/asiolink/tests/tls_unittest.cc b/src/lib/asiolink/tests/tls_unittest.cc new file mode 100644 index 0000000..effbe19 --- /dev/null +++ b/src/lib/asiolink/tests/tls_unittest.cc @@ -0,0 +1,2687 @@ +// Copyright (C) 2021-2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <asiolink/asio_wrapper.h> +#include <asiolink/io_service.h> +#include <asiolink/interval_timer.h> +#include <asiolink/crypto_tls.h> +#include <asiolink/tcp_endpoint.h> +#include <asiolink/testutils/test_tls.h> + +#include <boost/scoped_ptr.hpp> +#include <gtest/gtest.h> + +#include <cstdlib> +#include <list> +#include <string> +#include <vector> + +#ifdef WITH_OPENSSL +#include <openssl/opensslv.h> +#endif + +using namespace boost::asio; +using namespace boost::asio::ip; +using namespace isc::asiolink; +using namespace isc::cryptolink; +using namespace std; + +namespace { // anonymous namespace. + +/// @brief Local server address used for testing. +const char SERVER_ADDRESS[] = "127.0.0.1"; + +/// @brief Local server port used for testing. +const unsigned short SERVER_PORT = 18123; + +/// @brief Name of the environment variable controlling the display +/// (default off) of TLS error messages. +const char KEA_TLS_CHECK_VERBOSE[] = "KEA_TLS_CHECK_VERBOSE"; + +/// @brief Test TLS context class exposing protected methods. +class TestTlsContext : public TlsContext { +public: + /// @brief Constructor. + /// + /// @param role The TLS role client or server. + explicit TestTlsContext(TlsRole role) : TlsContext(role) { } + + /// @brief Destructor. + virtual ~TestTlsContext() { } + + /// @brief Make protected methods visible in tests. + using TlsContext::setCertRequired; + using TlsContext::loadCaFile; + using TlsContext::loadCaPath; + using TlsContext::loadCertFile; + using TlsContext::loadKeyFile; +}; + +/// @brief Class of test callbacks. +class TestCallback { +public: + /// @brief State part. + class State { + public: + /// @brief Constructor. + State() : called_(false), error_code_() { + } + + /// @brief Destructor. + virtual ~State() { + } + + /// @brief Called flag. + /// + /// Initialized to false, set to true when the callback is called. + bool called_; + + /// @brief Last error code. + boost::system::error_code error_code_; + }; + + /// @brief Constructor. + /// + /// Used to shared pointer to state to allow the callback object to + /// be copied keeping the state member values. + TestCallback() + : state_(new State()), tcpp_(0) { + } + + /// @brief Close on error constructor. + /// + /// An overload which takes the stream to close on error. + /// + /// @param tcpp Pointer to the stream to close on error. + TestCallback(TlsStream<TestCallback>::lowest_layer_type* tcpp) + : state_(new State()), tcpp_(tcpp) { + } + + /// @brief Destructor. + virtual ~TestCallback() { + } + + /// @brief Callback function (one argument). + /// + /// @parame ec Boost completion code. + void operator()(const boost::system::error_code& ec) { + state_->called_ = true; + state_->error_code_ = ec; + if (ec && tcpp_) { + tcpp_->close(); + } + } + + /// @brief Callback function (two arguments). + /// + /// @parame ec Boost completion code. + void operator()(const boost::system::error_code& ec, size_t) { + state_->called_ = true; + state_->error_code_ = ec; + if (ec && tcpp_) { + tcpp_->close(); + } + } + + /// @brief Get called value. + inline bool getCalled() const { + return (state_->called_); + } + + /// @brief Get error code. + inline const boost::system::error_code& getCode() const { + return (state_->error_code_); + } + +protected: + /// @brief Pointer to state. + boost::shared_ptr<State> state_; + + /// @brief Pointer to the stream to close on error. + TlsStream<TestCallback>::lowest_layer_type* tcpp_; +}; + +/// @brief The type of a test to be run. +typedef function<void()> Test; + +/// @brief Class of an expected behavior. +/// +/// Some TLS tests can not use the standard GTEST macros because they +/// show different behaviors depending on the crypto backend and the +/// boost library versions. Worse in some cases the behavior can not +/// be deduced from them so #ifdef's do not work... +/// +/// Until this is adopted / widespread the policy is to use these flexible +/// expected behavior tests ONLY when needed. +class Expected { +private: + /// Constructor. + /// + /// @param throwing True when an exception should be thrown. + /// @param timeout True when a timeout should occur. + /// @param no_error True when no error should be returned. + /// @param message Expected message. + Expected(bool throwing, bool timeout, bool no_error, + const string& message) + : throwing_(throwing), timeout_(timeout), no_error_(no_error), + message_(message) { + } + + /// @brief The throwing flag. + bool throwing_; + + /// @brief The timeout flag. + bool timeout_; + + /// @brief The no error flag. + bool no_error_; + + /// @brief The expected error message. + string message_; + +public: + /// @brief Create an expected throwing exception behavior. + /// + /// @param message Expected message. + static Expected createThrow(const string& message) { + return (Expected(true, false, false, message)); + } + + /// @brief Create an expected timeout behavior. + static Expected createTimeout() { + return (Expected(false, true, false, "")); + } + + /// @brief Create an expected no error behavior. + static Expected createNoError() { + return (Expected(false, false, true, "")); + } + + /// @brief Create an expected error message behavior. + /// + /// @param message Expected message. + static Expected createError(const string& message) { + return (Expected(false, false, false, message)); + } + + /// @brief Get the throwing flag. + /// + /// @return The throwing flag. + bool getThrow() const { + return (throwing_); + } + + /// @brief Get the timeout flag. + /// + /// @return The timeout flag. + bool getTimeout() const { + return (timeout_); + } + + /// @brief Get the no error flag. + /// + /// @return The no error flag. + bool getNoError() const { + return (no_error_); + } + + /// @brief Get the expected error message. + /// + /// @return The expected error message. + const string& getMessage() const { + return (message_); + } +}; + +/// @brief Class of expected behaviors. +class Expecteds { +private: + /// @brief List of expected behaviors. + list<Expected> list_; + + /// @brief The error message for the verbose mode. + string errmsg_; + +public: + /// Constructor. + /// + /// @return An empty expected behavior list. + Expecteds() : list_(), errmsg_("") { + } + + /// @brief Clear the list. + void clear() { + list_.clear(); + errmsg_.clear(); + } + + /// @brief Add an expected throwing exception behavior. + /// + /// @param message Expected message. + void addThrow(const string& message) { + list_.push_back(Expected::createThrow(message)); + } + + /// @brief Add an expected timeout behavior. + void addTimeout() { + list_.push_back(Expected::createTimeout()); + } + + /// @brief Add an expected no error behavior. + void addNoError() { + list_.push_back(Expected::createNoError()); + } + + /// @brief Add an expected error message behavior. + /// + /// @param message Expected message. + void addError(const string& message) { + list_.push_back(Expected::createError(message)); + } + + /// @brief Display error messages. + /// + /// @return True if error messages are displayed. + static bool displayErrMsg() { + return (getenv(KEA_TLS_CHECK_VERBOSE)); + } + + /// @brief Has an error message. + /// + /// @return True when there is a cached error message. + bool hasErrMsg() const { + return (!errmsg_.empty()); + } + + /// @brief Get error message. + /// + /// @return The cached error message. + const string& getErrMsg() const { + return (errmsg_); + } + + /// @brief Run a test which can throw. + /// + /// @param test The test to run. + void runCanThrow(const Test& test) { + // Check consistency. + for (auto const& exp : list_) { + if (!exp.getThrow() && !exp.getNoError()) { + ADD_FAILURE() << "inconsistent runCanThrow settings"; + } + } + + // Collect the test behavior. + bool thrown = false; + try { + test(); + } catch (const LibraryError& ex) { + thrown = true; + errmsg_ = ex.what(); + } catch (const isc::BadValue& ex) { + thrown = true; + errmsg_ = ex.what(); + } catch (const exception& ex) { + thrown = true; + errmsg_ = ex.what(); + ADD_FAILURE() << "expect only LibraryError or BadValue exception"; + } + + // Check the no error case. + if (!thrown) { + for (auto const& exp : list_) { + if (exp.getNoError()) { + // No error was expected: good. + return; + } + } + // No error was not expected: bad. + ADD_FAILURE() << "no exception?"; + return; + } + + // Check the thrown message. + for (auto const& exp : list_) { + if (!exp.getThrow()) { + continue; + } + if (errmsg_ == exp.getMessage()) { + // Got an expected message: good. + return; + } + } + // The message was not expected: bad. + ADD_FAILURE() << "exception with unknown '" << errmsg_ << "'"; + } + + /// @brief Check the result of an asynchronous operation. + /// + /// @param party The name of the party. + /// @param callback The test callback of the an asynchronous. + void checkAsync(const string& party, const TestCallback& callback) { + // Check timeout i.e. the callback was not called. + if (!callback.getCalled()) { + bool expected = false; + for (auto const& exp : list_) { + if (exp.getTimeout()) { + expected = true; + break; + } + } + if (!expected) { + ADD_FAILURE() << "unexpected timeout"; + } + } + + // Check the no error case. + const boost::system::error_code& ec = callback.getCode(); + if (!ec) { + for (auto const& exp : list_) { + if (exp.getTimeout() || exp.getNoError()) { + // Expected timeout or no error: good. + return; + } + } + // Should have failed but did not: bad. + ADD_FAILURE() << party << " did not failed as expected"; + return; + } + + // Got an error but was this one expected? + errmsg_ = ec.message(); + for (auto const& exp : list_) { + if (exp.getTimeout() || exp.getNoError()) { + continue; + } + if (errmsg_ == exp.getMessage()) { + // This error message was expected: good. + return; + } + } + ADD_FAILURE() << party << " got unexpected error '" << errmsg_ << "'"; + } + +}; + +/// @brief Test fixture class for TlsContext. +class TLSTest : public ::testing::Test { +public: + + /// @brief Constructor. + /// + /// Besides initializing class members it also sets the test timer to guard + /// against endlessly running IO service when TCP connections are + /// unsuccessful. + TLSTest() : io_service_(new IOService()) { + } + + /// @brief Destructor. + virtual ~TLSTest() { + io_service_->restart(); + try { + io_service_->poll(); + } catch (...) { + } + } + + /// @brief IO service. + IOServicePtr io_service_; + + /// @brief The shutdown callback. + TestCallback shutdown_cb_; + + /// @brief The TLS server. + boost::shared_ptr<TlsStream<TestCallback>> server_; + + /// @brief The TLS client. + boost::shared_ptr<TlsStream<TestCallback>> client_; +}; + +//////////////////////////////////////////////////////////////////////// +// TlsContext tests // +//////////////////////////////////////////////////////////////////////// + +// Test if we can get a client context. +TEST_F(TLSTest, clientContext) { + TlsContextPtr ctx; + EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT))); +} + +// Test if we can get a server context. +TEST_F(TLSTest, serverContext) { + TlsContextPtr ctx; + EXPECT_NO_THROW(ctx.reset(new TlsContext(TlsRole::SERVER))); +} + +// Test if the cert required flag is handled as expected. +TEST_F(TLSTest, certRequired) { + auto check = [] (TlsContext& ctx) -> bool { +#ifdef WITH_BOTAN + return (ctx.getCertRequired()); +#else // WITH_OPENSSL + ::SSL_CTX* ssl_ctx = ctx.getNativeContext(); + if (!ssl_ctx) { + ADD_FAILURE() << "null SSL_CTX"; + return (false); + } + int mode = SSL_CTX_get_verify_mode(ssl_ctx); + switch (mode) { + case SSL_VERIFY_NONE: + return (false); + case (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT): + return (true); + default: + ADD_FAILURE() << "unknown ssl_verify_mode: " << mode; + return (false); + } +#endif + }; + + TestTlsContext ctx(TlsRole::SERVER); + EXPECT_TRUE(ctx.getCertRequired()); + EXPECT_TRUE(check(ctx)); + ASSERT_NO_THROW(ctx.setCertRequired(false)); + EXPECT_FALSE(ctx.getCertRequired()); + EXPECT_FALSE(check(ctx)); + ASSERT_NO_THROW(ctx.setCertRequired(true)); + EXPECT_TRUE(ctx.getCertRequired()); + EXPECT_TRUE(check(ctx)); + + // Client role is different so test it too. + TestTlsContext cctx(TlsRole::CLIENT); + EXPECT_TRUE(cctx.getCertRequired()); + EXPECT_TRUE(check(cctx)); + ASSERT_NO_THROW(cctx.setCertRequired(true)); + EXPECT_TRUE(cctx.getCertRequired()); + EXPECT_TRUE(check(cctx)); + EXPECT_THROW(cctx.setCertRequired(false), isc::BadValue); +} + +// Test if the certificate authority can be loaded. +TEST_F(TLSTest, loadCAFile) { + string ca(string(TEST_CA_DIR) + "/kea-ca.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + EXPECT_NO_THROW(ctx.loadCaFile(ca)); +} + +// Test that no certificate authority gives an error. +TEST_F(TLSTest, loadNoCAFile) { + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL errors. + exps.addThrow("No such file or directory"); + exps.addThrow("No such file or directory (system library)"); + exps.addThrow("No such file or directory (system library, fopen)"); + exps.runCanThrow([] { + string ca("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +#ifdef WITH_BOTAN +// Test that Botan requires a real CA certificate so fails with +// trusted self-signed client. +/// @note: convert to GTEST when gtest_utils.h will be moved. +TEST_F(TLSTest, loadTrustedSelfCAFile) { + Expecteds exps; + // Botan error. + string botan_error = "Flatfile_Certificate_Store received non CA cert "; + botan_error += "C=\"US\",X520.State=\"Some-State\",O=\"ISC Inc.\","; + botan_error += "CN=\"kea-self\""; + exps.addThrow(botan_error); + exps.runCanThrow([] { + string ca(string(TEST_CA_DIR) + "/kea-self.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} +#endif // WITH_BOTAN + +// Test that a directory can be loaded. +TEST_F(TLSTest, loadCAPath) { + string ca(TEST_CA_DIR); + TestTlsContext ctx(TlsRole::CLIENT); + EXPECT_NO_THROW(ctx.loadCaPath(ca)); +} + +// Test that a certificate is wanted. +TEST_F(TLSTest, loadKeyCA) { + Expecteds exps; + // Botan error. + exps.addThrow("Flatfile_Certificate_Store::Flatfile_Certificate_Store cert file is empty"); + // LibreSSL or old OpenSSL does not check. + exps.addNoError(); + // Recent OpenSSL errors. + exps.addThrow("no certificate or crl found"); + exps.addThrow("no certificate or crl found (x509 certificate routines)"); + exps.addThrow("no certificate or crl found (x509 certificate routines, CRYPTO_internal)"); + exps.addThrow("no certificate or crl found (x509 certificate routines, X509_load_cert_crl_file)"); + exps.runCanThrow([] { + string ca(string(TEST_CA_DIR) + "/kea-ca.key"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test if the end entity certificate can be loaded. +TEST_F(TLSTest, loadCertFile) { + string cert(string(TEST_CA_DIR) + "/kea-client.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + EXPECT_NO_THROW(ctx.loadCertFile(cert)); +} + +// Test that no end entity certificate gives an error. +TEST_F(TLSTest, loadNoCertFile) { + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL errors. + exps.addThrow("No such file or directory"); + exps.addThrow("No such file or directory (system library)"); + exps.addThrow("No such file or directory (system library, fopen)"); + exps.runCanThrow([] { + string cert("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCertFile(cert); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test that a certificate is wanted. +TEST_F(TLSTest, loadCsrCertFile) { + Expecteds exps; + // Botan error. + exps.addThrow("Expected a certificate, got 'CERTIFICATE REQUEST'"); + // OpenSSL errors. + exps.addThrow("no start line"); + exps.addThrow("no start line (PEM routines)"); + exps.addThrow("no start line (PEM routines, get_name)"); + exps.addThrow("no start line (PEM routines, CRYPTO_internal)"); + exps.runCanThrow([] { + string cert(string(TEST_CA_DIR) + "/kea-client.csr"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCertFile(cert); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test if the private key can be loaded. +TEST_F(TLSTest, loadKeyFile) { + string key(string(TEST_CA_DIR) + "/kea-client.key"); + TestTlsContext ctx(TlsRole::CLIENT); + EXPECT_NO_THROW(ctx.loadKeyFile(key)); +} + +// Test that no private key gives an error. +TEST_F(TLSTest, loadNoKeyFile) { + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL errors. + exps.addThrow("No such file or directory"); + exps.addThrow("No such file or directory (system library)"); + exps.addThrow("No such file or directory (system library, fopen)"); + // Another possible error. + exps.addThrow("PEM lib"); + exps.runCanThrow([] { + string key("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadKeyFile(key); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test that a private key is wanted. +TEST_F(TLSTest, loadCertKeyFile) { + Expecteds exps; + // Botan error. + string botan_error = "PKCS #8 private key decoding failed with PKCS #8: "; + botan_error += "Unknown PEM label CERTIFICATE"; + exps.addThrow(botan_error); + // OpenSSL errors. + exps.addThrow("no start line"); + exps.addThrow("no start line (PEM routines)"); + exps.addThrow("no start line (PEM routines, get_name)"); + exps.addThrow("no start line (PEM routines, CRYPTO_internal)"); + exps.addThrow("PEM lib"); + exps.addThrow("PEM lib (SSL routines)"); + exps.addThrow("unsupported (DECODER routines)"); + exps.addThrow("unsupported"); + // Another possible error. + exps.addThrow("No such file or directory"); + exps.runCanThrow([] { + string key(string(TEST_CA_DIR) + "/kea-client.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadKeyFile(key); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test that the certificate and private key must match. +TEST_F(TLSTest, loadMismatch) { + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + string cert(string(TEST_CA_DIR) + "/kea-server.crt"); + TestTlsContext ctx(TlsRole::SERVER); + ctx.loadCertFile(cert); + }); + // Keep no error for at least Botan. + // OpenSSL error. + exps.addThrow("key values mismatch"); + exps.runCanThrow([] { + string key(string(TEST_CA_DIR) + "/kea-client.key"); + TestTlsContext ctx(TlsRole::SERVER); + // In fact OpenSSL checks only RSA key values... + // The explicit check function is SSL_CTX_check_private_key. + ctx.loadKeyFile(key); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +// Test the configure class method. +TEST_F(TLSTest, configure) { + TlsContextPtr ctx; + string ca(string(TEST_CA_DIR) + "/kea-ca.crt"); + string cert(string(TEST_CA_DIR) + "/kea-client.crt"); + string key(string(TEST_CA_DIR) + "/kea-client.key"); + EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::CLIENT, + ca, cert, key, true)); + ASSERT_TRUE(ctx); + EXPECT_EQ(TlsRole::CLIENT, ctx->getRole()); + EXPECT_TRUE(ctx->getCertRequired()); + + // Retry using the directory and the server. + ctx.reset(); + ca = TEST_CA_DIR; + cert = string(TEST_CA_DIR) + "/kea-server.crt"; + key = string(TEST_CA_DIR) + "/kea-server.key"; + EXPECT_NO_THROW(TlsContext::configure(ctx, TlsRole::SERVER, + ca, cert, key, false)); + ASSERT_TRUE(ctx); + EXPECT_EQ(TlsRole::SERVER, ctx->getRole()); + EXPECT_FALSE(ctx->getCertRequired()); +} + +// Test the configure class method error case. +TEST_F(TLSTest, configureError) { + // The error case. + Expecteds exps; + // Common part of the error message. + string common_error = "load of cert file '/no-such-file' failed: "; + // Botan error. + string botan_error = "I/O error: DataSource: Failure opening file /no-such-file"; + exps.addThrow(common_error + botan_error); + // OpenSSL errors. + string openssl_error = "No such file or directory"; + exps.addThrow(common_error + openssl_error); + exps.addThrow(common_error + "No such file or directory (system library)"); + exps.addThrow(common_error + "No such file or directory (system library, fopen)"); + exps.runCanThrow([] { + TlsContextPtr ctx1; + string ca(string(TEST_CA_DIR) + "/kea-ca.crt"); + string cert = "/no-such-file"; + string key = string(TEST_CA_DIR) + "/kea-client.key"; + TlsContext::configure(ctx1, TlsRole::CLIENT, + ca, cert, key, true); + // The context is reset on errors. + EXPECT_FALSE(ctx1); + }); + if (Expecteds::displayErrMsg()) { + std::cout << exps.getErrMsg() << "\n"; + } +} + +//////////////////////////////////////////////////////////////////////// +// Basic handshake failures // +//////////////////////////////////////////////////////////////////////// + +// Test if we can get a stream. +TEST_F(TLSTest, stream) { + TlsContextPtr ctx(new TlsContext(TlsRole::CLIENT)); + boost::scoped_ptr<TlsStream<TestCallback> > st; + EXPECT_NO_THROW(server_.reset(new TlsStream<TestCallback>(io_service_, ctx))); +} + +// Test what happens when handshake is forgotten. +TEST_F(TLSTest, noHandshake) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer1(io_service_); + bool timeout = false; + timer1.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Send on the client. + char send_buf[] = "some text..."; + TestCallback send_cb; + async_write(*client_, boost::asio::buffer(send_buf), send_cb); + while (!timeout && !send_cb.getCalled()) { + io_service_->runOne(); + } + timer1.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("InvalidObjectState"); + // OpenSSL errors. + exps.addError("uninitialized"); + exps.addError("uninitialized (SSL routines)"); + exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)"); + exps.addError("uninitialized (SSL routines, ssl_write_internal)"); + exps.checkAsync("send", send_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "send: " << exps.getErrMsg() << "\n"; + } + + // Setup a second timeout. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Receive on the server. + vector<char> receive_buf(64); + TestCallback receive_cb; + server_->async_read_some(boost::asio::buffer(receive_buf), receive_cb); + while (!timeout && !receive_cb.getCalled()) { + io_service_->runOne(); + } + timer2.cancel(); + + exps.clear(); + // On Botan and some OpenSSL the receive party hangs. + exps.addTimeout(); + // OpenSSL errors. + exps.addError("uninitialized"); + exps.addError("uninitialized (SSL routines)"); + exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)"); + exps.addError("uninitialized (SSL routines, ssl_read_internal)"); + exps.checkAsync("receive", receive_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "receive timeout\n"; + } else { + std::cout << "receive: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the server was not configured. +TEST_F(TLSTest, serverNotConfigured) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + // Skip config. + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("handshake_failure"); + // Old LibreSSL error. + exps.addError("no shared cipher"); + // OpenSSL errors. + exps.addError("sslv3 alert handshake failure"); + exps.addError("no shared cipher (SSL routines)"); + exps.addError("no shared cipher (SSL routines, ACCEPT_SR_CLNT_HELLO_C)"); + exps.addError("no shared cipher (SSL routines, tls_post_process_client_hello)"); + // Recent LibreSSL error. + exps.addError("missing rsa certificate (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // On Botan and some OpenSSL the client hangs. + exps.addTimeout(); + // OpenSSL errors. + exps.addError("sslv3 alert handshake failure"); + exps.addError("sslv3 alert handshake failure (SSL routines)"); + exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_SRVR_HELLO)"); + exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_CERT)"); + exps.addError("sslv3 alert handshake failure (SSL routines, ssl3_read_bytes)"); + exps.addError("ssl/tls alert handshake failure (SSL routines)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "client timeout\n"; + } else { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client was not configured. +TEST_F(TLSTest, clientNotConfigured) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT)); + // Skip config. + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // On Botan and some OpenSSL the server hangs. + exps.addTimeout(); + // OpenSSL errors. + exps.addError("tlsv1 alert unknown ca"); + exps.addError("tlsv1 alert unknown ca (SSL routines)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT_VRFY)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ssl3_read_bytes)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "server timeout\n"; + } else { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + } + + exps.clear(); + // Botan error (unfortunately a bit generic). + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, CONNECT_CR_CERT)"); + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + exps.addError("certificate verify failed (SSL routines, tls_process_server_certificate)"); + // The client should not hang. + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client is HTTP (vs HTTPS). +TEST_F(TLSTest, clientHTTPnoS) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + tcp::socket client(io_service_->getInternalIOService()); + + // Connect to. + client.open(tcp::v4()); + TestCallback connect_cb; + client.async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform server TLS handshake. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + + // Client sending a HTTP GET. + char send_buf[] = "GET / HTTP/1.1\r\n"; + TestCallback client_cb; + client.async_send(boost::asio::buffer(send_buf), client_cb); + + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan server hangs before 2.18. + exps.addTimeout(); + // Botan error. + exps.addError("protocol_version"); + // Old LibreSSL error. + exps.addError("tlsv1 alert protocol version"); + // OpenSSL errors (OpenSSL recognizes HTTP). + exps.addError("http request"); + exps.addError("http request (SSL routines)"); + exps.addError("http request (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.addError("http request (SSL routines, ssl3_get_record)"); + // Another OpenSSL error (not all OpenSSL recognizes HTTP). + exps.addError("wrong version number"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "server timeout\n"; + } else { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + } + + // No error at the client. + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client.lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client does not use HTTP nor HTTP. +TEST_F(TLSTest, unknownClient) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + tcp::socket client(io_service_->getInternalIOService()); + + // Connect to. + client.open(tcp::v4()); + TestCallback connect_cb; + client.async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform server TLS handshake. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + + // Client sending something which is not a TLS ClientHello. + char send_buf[] = "hello my server..."; + TestCallback client_cb; + client.async_send(boost::asio::buffer(send_buf), client_cb); + + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan errors. + exps.addError("record_overflow"); + exps.addError("protocol_version"); + // Old LibreSSL error. + exps.addError("tlsv1 alert protocol version"); + // Old OpenSSL error. + exps.addError("unknown protocol"); + // Recent OpenSSL errors. + exps.addError("wrong version number"); + exps.addError("wrong version number (SSL routines)"); + exps.addError("wrong version number (SSL routines, ssl3_get_record)"); + exps.addError("packet length too long (SSL routines)"); + // Recent LibreSSL error. + exps.addError("unknown protocol (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + // No error on the client side. + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client.lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client uses a certificate from another CA. +TEST_F(TLSTest, anotherClient) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a certificate signed by another CA. + TlsContextPtr client_ctx; + test::configOther(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + // Full error is: + // error 20 at 0 depth lookup:unable to get local issuer certificate + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)"); + // Recent LibreSSL errors. + exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan client hangs. + exps.addTimeout(); + // Old LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "client timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client uses a self-signed certificate. +TEST_F(TLSTest, selfSigned) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a self-signed certificate. + TlsContextPtr client_ctx; + test::configSelf(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + // Full error is: + // error 18 at 0 depth lookup:self signed certificate + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)"); + // Recent LibreSSL errors. + exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan client hangs. + exps.addTimeout(); + // Old LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "client timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +//////////////////////////////////////////////////////////////////////// +// Close on error handshake failures // +//////////////////////////////////////////////////////////////////////// + +// Investigate what happens when a peer closes its streams when the +// handshake callback returns an error. In particular does still +// the other peer timeout? + +// Test what happens when handshake is forgotten. +TEST_F(TLSTest, noHandshakeCloseonError) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer1(io_service_); + bool timeout = false; + timer1.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Send on the client. + char send_buf[] = "some text..."; + TestCallback send_cb(&client_->lowest_layer()); + async_write(*client_, boost::asio::buffer(send_buf), send_cb); + while (!timeout && !send_cb.getCalled()) { + io_service_->runOne(); + } + timer1.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("InvalidObjectState"); + // OpenSSL errors. + exps.addError("uninitialized"); + exps.addError("uninitialized (SSL routines)"); + exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)"); + exps.addError("uninitialized (SSL routines, ssl_write_internal)"); + exps.checkAsync("send", send_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "send: " << exps.getErrMsg() << "\n"; + } + + // Setup a second timeout. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Receive on the server. + vector<char> receive_buf(64); + TestCallback receive_cb; + server_->async_read_some(boost::asio::buffer(receive_buf), receive_cb); + while (!timeout && !receive_cb.getCalled()) { + io_service_->runOne(); + } + timer2.cancel(); + + exps.clear(); + // Botan and some OpenSSL. + exps.addError("stream truncated"); + // OpenSSL errors. + exps.addError("uninitialized"); + exps.addError("uninitialized (SSL routines)"); + exps.addError("uninitialized (SSL routines, ST_BEFORE_ACCEPT)"); + exps.addError("uninitialized (SSL routines, ssl_read_internal)"); + exps.checkAsync("receive", receive_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "receive: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the server was not configured. +TEST_F(TLSTest, serverNotConfiguredCloseonError) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + // Skip config. + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("handshake_failure"); + // Old LibreSSL error. + exps.addError("no shared cipher"); + // OpenSSL errors. + exps.addError("sslv3 alert handshake failure"); + exps.addError("no shared cipher (SSL routines)"); + exps.addError("no shared cipher (SSL routines, ACCEPT_SR_CLNT_HELLO_C)"); + exps.addError("no shared cipher (SSL routines, tls_post_process_client_hello)"); + // Recent LibreSSL error. + exps.addError("missing rsa certificate (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan and some OpenSSL. + exps.addError("stream truncated"); + // Alias on old OpenSSL. + exps.addError("short read"); + // OpenSSL errors. + exps.addError("sslv3 alert handshake failure"); + exps.addError("sslv3 alert handshake failure (SSL routines)"); + exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_SRVR_HELLO)"); + exps.addError("sslv3 alert handshake failure (SSL routines, ssl3_read_bytes)"); + exps.addError("ssl/tls alert handshake failure (SSL routines)"); + // Recent LibreSSL error. + exps.addError("sslv3 alert handshake failure (SSL routines, CONNECT_CR_CERT)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client was not configured. +TEST_F(TLSTest, clientNotConfiguredCloseonError) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx(new TlsContext(TlsRole::CLIENT)); + // Skip config. + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb(&client_->lowest_layer()); + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan and some OpenSSL. + exps.addError("stream truncated"); + // Alias on old OpenSSL. + exps.addError("short read"); + // OpenSSL errors. + exps.addError("tlsv1 alert unknown ca"); + exps.addError("tlsv1 alert unknown ca (SSL routines)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ACCEPT_SR_CERT_VRFY)"); + exps.addError("tlsv1 alert unknown ca (SSL routines, ssl3_read_bytes)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan error (unfortunately a bit generic). + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, CONNECT_CR_CERT)"); + exps.addError("certificate verify failed (SSL routines, tls_process_server_certificate)"); + // Recent LibreSSL error. + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + // The client should not hang. + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client is HTTP (vs HTTPS). +TEST_F(TLSTest, clientHTTPnoSCloseonError) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + tcp::socket client(io_service_->getInternalIOService()); + + // Connect to. + client.open(tcp::v4()); + TestCallback connect_cb; + client.async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform server TLS handshake. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + + // Client sending a HTTP GET. + char send_buf[] = "GET / HTTP/1.1\r\n"; + TestCallback client_cb; + client.async_send(boost::asio::buffer(send_buf), client_cb); + + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan server hangs before 2.18. + exps.addTimeout(); + // Botan behavior was reported and fixed. + exps.addError("protocol_version"); + // Old LibreSSL error. + exps.addError("tlsv1 alert protocol version"); + // OpenSSL errors when OpenSSL recognizes HTTP. + exps.addError("http request"); + exps.addError("http request (SSL routines)"); + exps.addError("http request (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.addError("http request (SSL routines, ssl3_get_record)"); + // Another OpenSSL error (not all OpenSSL recognizes HTTP). + exps.addError("wrong version number"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert protocol version (SSL routines, ACCEPT_SR_CLNT_HELLO)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "server timeout\n"; + } else { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + } + + // No error at the client. + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client.lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client uses a certificate from another CA. +TEST_F(TLSTest, anotherClientCloseonError) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a certificate signed by another CA. + TlsContextPtr client_ctx; + test::configOther(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + // Full error is: + // error 20 at 0 depth lookup:unable to get local issuer certificate + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)"); + // Recent LibreSSL errors. + exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan and some OpenSSL. + exps.addError("stream truncated"); + // Alias on old OpenSSL. + exps.addError("short read"); + // Old LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg() && exps.hasErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the client uses a self-signed certificate. +TEST_F(TLSTest, selfSignedCloseonError) { + // Server part. + TlsContextPtr server_ctx; + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a self-signed certificate. + TlsContextPtr client_ctx; + test::configSelf(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // Old LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL errors. + // Full error is: + // error 18 at 0 depth lookup:self signed certificate + exps.addError("certificate verify failed"); + exps.addError("certificate verify failed (SSL routines)"); + exps.addError("certificate verify failed (SSL routines, tls_process_client_certificate)"); + // Recent LibreSSL errors. + exps.addError("no certificate returned (SSL routines, ACCEPT_SR_CERT)"); + exps.addError("certificate verify failed (SSL routines, (UNKNOWN)SSL_internal)"); + exps.checkAsync("server", server_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // Botan and some OpenSSL. + exps.addError("stream truncated"); + // Alias on old OpenSSL. + exps.addError("short read"); + // Old LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + // Recent LibreSSL error. + exps.addError("tlsv1 alert unknown ca (SSL routines, CONNECT_CR_SESSION_TICKET)"); + exps.checkAsync("client", client_cb); + if (Expecteds::displayErrMsg() && exps.hasErrMsg()) { + std::cout << "client: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +//////////////////////////////////////////////////////////////////////// +// TLS handshake corner case // +//////////////////////////////////////////////////////////////////////// + +// Some special cases. + +// Test what happens when the client uses a certificate from another CA +// but the client certificate request and validation are disabled. +TEST_F(TLSTest, anotherClientNoReq) { + // Server part. + TlsContextPtr server_ctx; + test::configServerNoReq(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a certificate signed by another CA. + TlsContextPtr client_ctx; + test::configOther(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // Should not fail. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the server uses a certificate without subject +// alternative name (but still a version 3 certificate). +TEST_F(TLSTest, serverRaw) { + // Server part. + TlsContextPtr server_ctx; + test::configServerRaw(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // Should not fail. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +#ifdef WITH_OPENSSL +// Test what happens when the client uses a trusted self-signed certificate. +// Not really a failure case as it works... +TEST_F(TLSTest, trustedSelfSigned) { + // Server part. + TlsContextPtr server_ctx; + test::configTrustedSelf(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part using a self-signed certificate. + TlsContextPtr client_ctx; + test::configSelf(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb; + server_->async_handshake(roleToImpl(TlsRole::SERVER), server_cb); + TestCallback client_cb; + client_->async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // It should work for all OpenSSL. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} +#endif // WITH_OPENSSL + +//////////////////////////////////////////////////////////////////////// +// TLS shutdown // +//////////////////////////////////////////////////////////////////////// + +// Investigate the TLS shutdown processing. + +// Test what happens when the shutdown receiver is inactive. +TEST_F(TLSTest, shutdownInactive) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // No problem is expected. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Setup a timeout for the shutdown. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Shutdown on the client leaving the server inactive. + client_->shutdown(shutdown_cb_); + while (!timeout && !shutdown_cb_.getCalled()) { + io_service_->runOne(); + } + timer2.cancel(); + + Expecteds exps; + // Botan gets no error. + exps.addNoError(); + // OpenSSL hangs. + exps.addTimeout(); + exps.checkAsync("shutdown", shutdown_cb_); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "shutdown timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "shutdown: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the shutdown receiver is active. +TEST_F(TLSTest, shutdownActive) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // No problem is expected. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Setup a timeout for the shutdown and receive. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Receive on the server. + vector<char> receive_buf(64); + TestCallback receive_cb; + server_->async_read_some(boost::asio::buffer(receive_buf), receive_cb); + + // Shutdown on the client. + client_->shutdown(shutdown_cb_); + while (!timeout && (!shutdown_cb_.getCalled() || !receive_cb.getCalled())) { + io_service_->runOne(); + } + timer2.cancel(); + + Expecteds exps; + // Botan gets no error. + exps.addNoError(); + // OpenSSL hangs. + exps.addTimeout(); + exps.checkAsync("shutdown", shutdown_cb_); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "shutdown timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "shutdown: " << exps.getErrMsg() << "\n"; + } + } + + exps.clear(); + // End of file on the receive side. + exps.addError("End of file"); + exps.checkAsync("receive", receive_cb); + if (Expecteds::displayErrMsg()) { + std::cout << "receive: " << exps.getErrMsg() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the shutdown receiver is inactive on shutdown +// and immediate close. +TEST_F(TLSTest, shutdownCloseInactive) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // No problem is expected. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Setup a timeout for the shutdown. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Shutdown on the client leaving the server inactive. + client_->shutdown(shutdown_cb_); + + // Post a close which should be called after the shutdown. + io_service_->post([&] { client_->lowest_layer().close(); }); + while (!timeout && !shutdown_cb_.getCalled()) { + io_service_->runOne(); + } + timer2.cancel(); + + Expecteds exps; + // Botan gets no error. + exps.addNoError(); + // LibreSSL and some old OpenSSL gets Operation canceled. + exps.addError("Operation canceled"); + // OpenSSL gets Bad file descriptor. + exps.addError("Bad file descriptor"); + exps.checkAsync("shutdown", shutdown_cb_); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "shutdown timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "shutdown: " << exps.getErrMsg() << "\n"; + } + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Test what happens when the shutdown receiver is active with an +// immediate close. +TEST_F(TLSTest, shutdownCloseActive) { + // Server part. + TlsContextPtr server_ctx(new TlsContext(TlsRole::SERVER)); + test::configServer(server_ctx); + server_.reset(new TlsStream<TestCallback>(io_service_, server_ctx)); + + // Accept a client. + tcp::endpoint server_ep(tcp::endpoint(address::from_string(SERVER_ADDRESS), + SERVER_PORT)); + tcp::acceptor acceptor(io_service_->getInternalIOService(), server_ep); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + TestCallback accept_cb; + acceptor.async_accept(server_->lowest_layer(), accept_cb); + + // Client part. + TlsContextPtr client_ctx; + test::configClient(client_ctx); + client_.reset(new TlsStream<TestCallback>(io_service_, client_ctx)); + + // Connect to. + client_->lowest_layer().open(tcp::v4()); + TestCallback connect_cb; + client_->lowest_layer().async_connect(server_ep, connect_cb); + + // Run accept and connect. + while (!accept_cb.getCalled() || !connect_cb.getCalled()) { + io_service_->runOne(); + } + + // Verify the error codes. + if (accept_cb.getCode()) { + FAIL() << "accept error " << accept_cb.getCode().value() + << " '" << accept_cb.getCode().message() << "'"; + } + // Possible EINPROGRESS for the client. + if (connect_cb.getCode() && + (connect_cb.getCode().value() != EINPROGRESS)) { + FAIL() << "connect error " << connect_cb.getCode().value() + << " '" << connect_cb.getCode().message() << "'"; + } + + // Setup a timeout. + IntervalTimer timer(io_service_); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Perform TLS handshakes. + TestCallback server_cb(&server_->lowest_layer()); + server_->handshake(server_cb); + TestCallback client_cb; + client_->handshake(client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { + io_service_->runOne(); + } + timer.cancel(); + + // No problem is expected. + EXPECT_FALSE(timeout); + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_FALSE(server_cb.getCode()); + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_FALSE(client_cb.getCode()); + + // Setup a timeout for the shutdown and receive. + IntervalTimer timer2(io_service_); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + + // Receive on the server. + vector<char> receive_buf(64); + TestCallback receive_cb; + server_->async_read_some(boost::asio::buffer(receive_buf), receive_cb); + + // Shutdown on the client. + client_->shutdown(shutdown_cb_); + + // Post a close which should be called after the shutdown. + io_service_->post([&] { client_->lowest_layer().close(); }); + while (!timeout && (!shutdown_cb_.getCalled() || !receive_cb.getCalled())) { + io_service_->runOne(); + } + timer2.cancel(); + + Expecteds exps; + // Botan gets no error. + exps.addNoError(); + // LibreSSL and some old OpenSSL gets Operation canceled. + exps.addError("Operation canceled"); + // OpenSSL gets Bad file descriptor. + exps.addError("Bad file descriptor"); + exps.checkAsync("shutdown", shutdown_cb_); + if (Expecteds::displayErrMsg()) { + if (timeout) { + std::cout << "shutdown timeout\n"; + } else if (exps.hasErrMsg()) { + std::cout << "shutdown: " << exps.getErrMsg() << "\n"; + } + } + + // End of file on the receive side. + EXPECT_TRUE(receive_cb.getCalled()); + EXPECT_TRUE(receive_cb.getCode()); + EXPECT_EQ("End of file", receive_cb.getCode().message()); + if (Expecteds::displayErrMsg()) { + std::cout << "receive: " << receive_cb.getCode().message() << "\n"; + } + + // Close client and server. + EXPECT_NO_THROW(client_->lowest_layer().close()); + EXPECT_NO_THROW(server_->lowest_layer().close()); +} + +// Conclusion about the shutdown: do the close on completion (e.g. in the +// handler) or on timeout (i.e. simulate an asynchronous shutdown with +// timeout). + +} // end of anonymous namespace. |