From: Francis Dupont Date: Mon, 15 Mar 2021 09:28:48 +0000 (+0100) Subject: [#1748] Checkpoint: rewrote TLS UTs X-Git-Tag: Kea-1.9.6~143 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fa41491a989827163cde4b6c525944fb63002ad1;p=thirdparty%2Fkea.git [#1748] Checkpoint: rewrote TLS UTs --- diff --git a/src/lib/asiolink/tests/tls_unittest.cc b/src/lib/asiolink/tests/tls_unittest.cc index 0ac9e61eb3..884b23b754 100644 --- a/src/lib/asiolink/tests/tls_unittest.cc +++ b/src/lib/asiolink/tests/tls_unittest.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include +#include #include #include @@ -31,6 +33,12 @@ 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 Test TLS context class exposing protected methods. class TestTlsContext : public TlsContext { public: @@ -50,25 +58,338 @@ public: using TlsContext::loadKeyFile; }; -} // end of anonymous namespace. +/// @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()) { + } + + /// @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; + } + + /// @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; + } + + /// @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_; +}; + +/// @brief The type of a test to be run. +typedef function Test; + +/// @brief Class of an expected behavior. +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 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 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 exception& ex) { + thrown = true; + errmsg_ = ex.what(); + ADD_FAILURE() << "expect only LibraryError 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 expected error '" << errmsg_ << "'"; + } + +}; // Test if we can get a client context. TEST(TLSTest, clientContext) { - TlsContextPtr ctx; - ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT))); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + TlsContextPtr ctx(new TlsContext(TlsRole::CLIENT)); + }); } // Test if we can get a server context. TEST(TLSTest, serverContext) { - TlsContextPtr ctx; - ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::SERVER))); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + TlsContextPtr ctx(new TlsContext(TlsRole::SERVER)); + }); } // Test if the cert required flag is handled as expected. TEST(TLSTest, certRequired) { auto check = [] (TlsContext& ctx) -> bool { #ifdef WITH_BOTAN - // Implement it. + // Implement it? return (ctx.getCertRequired()); #else // WITH_OPENSSL ::SSL_CTX* ssl_ctx = ctx.getNativeContext(); @@ -102,233 +423,225 @@ TEST(TLSTest, certRequired) { // Test if the certificate authority can be loaded. TEST(TLSTest, loadCAFile) { - string ca(string(TEST_CA_DIR) + "/kea-ca.crt"); - TestTlsContext ctx(TlsRole::CLIENT); - ASSERT_NO_THROW(ctx.loadCaFile(ca)); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + string ca(string(TEST_CA_DIR) + "/kea-ca.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); } // Test that no certificate authority gives an error. TEST(TLSTest, loadNoCAFile) { - string ca("/no-such-file"); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW_MSG(ctx.loadCaFile(ca), LibraryError, - "No such file or directory"); + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL error. + exps.addThrow("No such file or directory"); + exps.runCanThrow([] { + string ca("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); + std::cout << exps.getErrMsg() << "\n"; } -#ifdef WITH_BOTAN -// Test that a directory can't be loaded with Botan. -TEST(TLSTest, loadCAPath) { - string ca(TEST_CA_DIR); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW(ctx.loadCaPath(ca), NotImplemented); -} -#else // WITH_OPENSSL // Test that a directory can be loaded. TEST(TLSTest, loadCAPath) { - string ca(TEST_CA_DIR); - TestTlsContext ctx(TlsRole::CLIENT); - ASSERT_NO_THROW(ctx.loadCaPath(ca)); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + string ca(TEST_CA_DIR); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaPath(ca); + }); } -#endif // Test that a certificate is wanted. TEST(TLSTest, loadKeyCA) { - string ca(string(TEST_CA_DIR) + "/kea-ca.key"); - TestTlsContext ctx(TlsRole::CLIENT); -#ifdef WITH_OPENSSL -#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L) - EXPECT_THROW_MSG(ctx.loadCaFile(ca), LibraryError, - "no certificate or crl found"); -#endif -#endif + Expecteds exps; + exps.addNoError(); + // Botan error. + exps.addThrow("Flatfile_Certificate_Store::Flatfile_Certificate_Store cert file is empty"); + // LibreSSL or old OpenSSL error. + exps.addNoError(); + // Recent OpenSSL error. + exps.addThrow("no certificate or crl found"); + exps.runCanThrow([] { + string ca(string(TEST_CA_DIR) + "/kea-ca.key"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCaFile(ca); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test if the end entity certificate can be loaded. TEST(TLSTest, loadCertFile) { - string cert(string(TEST_CA_DIR) + "/kea-client.crt"); - TestTlsContext ctx(TlsRole::CLIENT); - ASSERT_NO_THROW(ctx.loadCertFile(cert)); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + string cert(string(TEST_CA_DIR) + "/kea-client.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCertFile(cert); + }); } // Test that no end entity certificate gives an error. TEST(TLSTest, loadNoCertFile) { - string cert("/no-such-file"); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW_MSG(ctx.loadCertFile(cert), LibraryError, - "No such file or directory"); + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL error. + exps.addThrow("No such file or directory"); + exps.runCanThrow([] { + string cert("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCertFile(cert); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test that a certificate is wanted. TEST(TLSTest, loadCsrCertFile) { - string cert(string(TEST_CA_DIR) + "/kea-client.csr"); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW_MSG(ctx.loadCertFile(cert), LibraryError, - "no start line"); + Expecteds exps; + // Botan error. + exps.addThrow("Expected a certificate, got 'CERTIFICATE REQUEST'"); + // OpenSSL error. + exps.addThrow("no start line"); + exps.runCanThrow([] { + string cert(string(TEST_CA_DIR) + "/kea-client.csr"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadCertFile(cert); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test if the private key can be loaded. TEST(TLSTest, loadKeyFile) { - string key(string(TEST_CA_DIR) + "/kea-client.key"); - TestTlsContext ctx(TlsRole::CLIENT); - ASSERT_NO_THROW(ctx.loadKeyFile(key)); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + string key(string(TEST_CA_DIR) + "/kea-client.key"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadKeyFile(key); + }); } // Test that no private key gives an error. TEST(TLSTest, loadNoKeyFile) { - string key("/no-such-file"); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError, - "No such file or directory"); + Expecteds exps; + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL error. + exps.addThrow("No such file or directory"); + exps.runCanThrow([] { + string key("/no-such-file"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadKeyFile(key); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test that a private key is wanted. TEST(TLSTest, loadCertKeyFile) { - string key(string(TEST_CA_DIR) + "/kea-client.crt"); - TestTlsContext ctx(TlsRole::CLIENT); - EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError, - "no start line"); + 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 error. + exps.addThrow("no start line"); + exps.runCanThrow([] { + string key(string(TEST_CA_DIR) + "/kea-client.crt"); + TestTlsContext ctx(TlsRole::CLIENT); + ctx.loadKeyFile(key); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test that the certificate and private key must match. TEST(TLSTest, loadMismatch) { - string cert(string(TEST_CA_DIR) + "/kea-server.crt"); - TestTlsContext ctx(TlsRole::SERVER); - EXPECT_NO_THROW(ctx.loadCertFile(cert)); - string key(string(TEST_CA_DIR) + "/kea-client.key"); - // In fact OpenSSL checks only RSA key values... - // The explicit check function is SSL_CTX_check_private_key. - EXPECT_THROW_MSG(ctx.loadKeyFile(key), LibraryError, - "key values mismatch"); + 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); + }); + std::cout << exps.getErrMsg() << "\n"; } // Test the configure class method. TEST(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()); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + 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"); + TlsContext::configure(ctx, TlsRole::CLIENT, + ca, cert, key, true); + ASSERT_TRUE(ctx); + EXPECT_EQ(TlsRole::CLIENT, ctx->getRole()); + EXPECT_TRUE(ctx->getCertRequired()); + }); -#ifdef WITH_OPENSSL // Retry using the directory and the server. - 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()); - -#endif + exps.runCanThrow([] { + TlsContextPtr ctx; + string ca = TEST_CA_DIR; + string cert = string(TEST_CA_DIR) + "/kea-server.crt"; + string key = string(TEST_CA_DIR) + "/kea-server.key"; + TlsContext::configure(ctx, TlsRole::SERVER, + ca, cert, key, false); + ASSERT_TRUE(ctx); + EXPECT_EQ(TlsRole::SERVER, ctx->getRole()); + EXPECT_FALSE(ctx->getCertRequired()); + }); // The error case. - cert = "/no-such-file"; - key = string(TEST_CA_DIR) + "/kea-client.key"; - EXPECT_THROW_MSG(TlsContext::configure(ctx, TlsRole::CLIENT, - ca, cert, key, true), - LibraryError, - "No such file or directory"); - // The context is reseted on errors. - EXPECT_FALSE(ctx); + exps.clear(); + // Botan error. + exps.addThrow("I/O error: DataSource: Failure opening file /no-such-file"); + // OpenSSL error. + exps.addThrow("No such file or directory"); + exps.runCanThrow([] { + TlsContextPtr ctx; + 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(ctx, TlsRole::CLIENT, + ca, cert, key, true); + // The context is reseted on errors. + EXPECT_FALSE(ctx); + }); + std::cout << exps.getErrMsg() << "\n"; } -// Disabled tests for obsolete OpenSSL or Botan -#ifdef WITH_OPENSSL -#if defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L) - -// Define a callback class. -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 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()) { - } - - /// @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; - } - - /// @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; - } - - /// @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_; -}; - -} // end of anonymous namespace. - // Test if we can get a stream. TEST(TLSTest, stream) { - IOService service; - TlsContextPtr ctx; - ASSERT_NO_THROW(ctx.reset(new TlsContext(TlsRole::CLIENT))); - boost::scoped_ptr > st; - ASSERT_NO_THROW(st.reset(new TlsStream(service, ctx))); + Expecteds exps; + exps.addNoError(); + exps.runCanThrow([] { + IOService service; + TlsContextPtr ctx(new TlsContext(TlsRole::CLIENT)); + boost::scoped_ptr > st; + st.reset(new TlsStream(service, ctx)); + }); } -namespace { // anonymous namespace. -} // end of anonymous namespace. - // Test what happens when handshake is forgotten. TEST(TLSTest, noHandshake) { IOService service; @@ -373,25 +686,53 @@ TEST(TLSTest, noHandshake) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer1(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 (!send_cb.getCalled()) { + while (!timeout && !send_cb.getCalled()) { service.run_one(); } - EXPECT_TRUE(send_cb.getCode()); - EXPECT_EQ("uninitialized", send_cb.getCode().message()); + timer1.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("InvalidObjectState"); + // OpenSSL error. + exps.addError("uninitialized"); + exps.checkAsync("send", send_cb); + std::cout << "send: " << exps.getErrMsg() << "\n"; + + // Setup a second timeout. + IntervalTimer timer2(service); + timeout = false; + timer2.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); // Receive on the server. vector receive_buf(64); TestCallback receive_cb; server.async_read_some(boost::asio::buffer(receive_buf), receive_cb); - while (!receive_cb.getCalled()) { + while (!timeout && !receive_cb.getCalled()) { service.run_one(); } - EXPECT_TRUE(receive_cb.getCode()); - EXPECT_EQ("uninitialized", receive_cb.getCode().message()); + timer2.cancel(); + + exps.clear(); + // On Botan and some OpenSSL the receive party hangs. + exps.addTimeout(); + // OpenSSL error, + exps.addError("uninitialized"); + exps.checkAsync("receive", receive_cb); + 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()); @@ -442,23 +783,42 @@ TEST(TLSTest, serverNotConfigured) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(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 (!server_cb.getCalled() || !client_cb.getCalled()) { + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); -#ifndef LIBRESSL_VERSION_NUMBER - string server_expected("no shared cipher"); -#else - string server_expected("sslv3 alert handshake failure"); -#endif - EXPECT_EQ(server_expected, server_cb.getCode().message()); - EXPECT_TRUE(client_cb.getCode()); - EXPECT_EQ("sslv3 alert handshake failure", client_cb.getCode().message()); + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("handshake_failure"); + // LibreSSL error. + exps.addError("no shared cipher"); + // OpenSSL error. + exps.addError("sslv3 alert handshake failure"); + exps.checkAsync("server", server_cb); + std::cout << "server: " << exps.getErrMsg() << "\n"; + + exps.clear(); + // On Botan and some OpenSSL the client hangs. + exps.addTimeout(); + // OpenSSL error. + exps.addError("sslv3 alert handshake failure"); + exps.checkAsync("client", client_cb); + 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()); @@ -509,23 +869,43 @@ TEST(TLSTest, clientNotConfigured) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(service); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + // Perform TLS handshakes. TestCallback server_cb; - server.async_handshake(ssl::stream_base::server, server_cb); + server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb); TestCallback client_cb; - client.async_handshake(ssl::stream_base::client, client_cb); - while (!server_cb.getCalled() || !client_cb.getCalled()) { + client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); - EXPECT_EQ("tlsv1 alert unknown ca", server_cb.getCode().message()); - EXPECT_TRUE(client_cb.getCode()); -#ifndef LIBRESSL_VERSION_NUMBER - string client_expected("certificate verify failed"); -#else - string client_expected("tlsv1 alert unknown ca"); -#endif - EXPECT_EQ(client_expected, client_cb.getCode().message()); + timer.cancel(); + + Expecteds exps; + // On Botan and some OpenSSL the server hangs. + exps.addTimeout(); + // OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + exps.checkAsync("server", server_cb); + 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"); + // LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL error. + exps.addError("certificate verify failed"); + // The client should not hang. + exps.checkAsync("client", client_cb); + std::cout << "client: " << exps.getErrMsg() << "\n"; // Close client and server. EXPECT_NO_THROW(client.lowest_layer().close()); @@ -574,26 +954,43 @@ TEST(TLSTest, clientHTTPnoS) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(service); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + // Perform server TLS handshake. TestCallback server_cb; - server.async_handshake(ssl::stream_base::server, 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 (!server_cb.getCalled() || !client_cb.getCalled()) { + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); -#ifndef LIBRESSL_VERSION_NUMBER - string server_expected("http request"); -#else - string server_expected("tlsv1 alert protocol version"); -#endif - EXPECT_EQ(server_expected, server_cb.getCode().message()); - EXPECT_FALSE(client_cb.getCode()); + timer.cancel(); + + Expecteds exps; + // Botan server hangs. + exps.addTimeout(); + // LibreSSL error. + exps.addError("tlsv1 alert protocol version"); + // OpenSSL error (OpenSSL recognizes HTTP). + exps.addError("http request"); + exps.checkAsync("server", server_cb); + if (timeout) { + std::cout << "server timeout\n"; + } else { + std::cout << "server: " << exps.getErrMsg() << "\n"; + } + + exps.clear(); + // No error at the client. + exps.addNoError(); + exps.checkAsync("client", client_cb); // Close client and server. EXPECT_NO_THROW(client.lowest_layer().close()); @@ -642,26 +1039,41 @@ TEST(TLSTest, unknownClient) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(service); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + // Perform server TLS handshake. TestCallback server_cb; - server.async_handshake(ssl::stream_base::server, 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 (!server_cb.getCalled() || !client_cb.getCalled()) { + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); -#ifndef LIBRESSL_VERSION_NUMBER - string server_expected("wrong version number"); -#else - string server_expected("tlsv1 alert protocol version"); -#endif - EXPECT_EQ(server_expected, server_cb.getCode().message()); - EXPECT_FALSE(client_cb.getCode()); + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("record_overflow"); + // LibreSSL error. + exps.addError("tlsv1 alert protocol version"); + // Old OpenSSL error. + exps.addError("unknown protocol"); + // Recent OpenSSL error. + exps.addError("wrong version number"); + exps.checkAsync("server", server_cb); + std::cout << "server: " << exps.getErrMsg() << "\n"; + + exps.clear(); + // No error on the client side. + exps.addNoError(); + exps.checkAsync("client", client_cb); // Close client and server. EXPECT_NO_THROW(client.lowest_layer().close()); @@ -712,24 +1124,46 @@ TEST(TLSTest, anotherClient) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(service); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + // Perform TLS handshakes. TestCallback server_cb; - server.async_handshake(ssl::stream_base::server, server_cb); + server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb); TestCallback client_cb; - client.async_handshake(ssl::stream_base::client, client_cb); - while (!server_cb.getCalled() || !client_cb.getCalled()) { + client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL error. // Full error is: // error 20 at 0 depth lookup:unable to get local issuer certificate -#ifndef LIBRESSL_VERSION_NUMBER - string server_expected("certificate verify failed"); -#else - string server_expected("tlsv1 alert unknown ca"); -#endif - EXPECT_EQ(server_expected, server_cb.getCode().message()); - EXPECT_FALSE(client_cb.getCode()); + exps.addError("certificate verify failed"); + exps.checkAsync("server", server_cb); + std::cout << "server: " << exps.getErrMsg() << "\n"; + + exps.clear(); + // Botan client hangs. + exps.addTimeout(); + // LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + exps.checkAsync("client", client_cb); + 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()); @@ -780,29 +1214,50 @@ TEST(TLSTest, selfSigned) { << " '" << connect_cb.getCode().message() << "'"; } + // Setup a timeout. + IntervalTimer timer(service); + bool timeout = false; + timer.setup([&timeout] { timeout = true; }, 100, IntervalTimer::ONE_SHOT); + // Perform TLS handshakes. TestCallback server_cb; - server.async_handshake(ssl::stream_base::server, server_cb); + server.async_handshake(roleToImpl(TlsRole::SERVER), server_cb); TestCallback client_cb; - client.async_handshake(ssl::stream_base::client, client_cb); - while (!server_cb.getCalled() || !client_cb.getCalled()) { + client.async_handshake(roleToImpl(TlsRole::CLIENT), client_cb); + while (!timeout && (!server_cb.getCalled() || !client_cb.getCalled())) { service.run_one(); } - EXPECT_TRUE(server_cb.getCode()); + timer.cancel(); + + Expecteds exps; + // Botan error. + exps.addError("bad_certificate"); + // LibreSSL error. + exps.addError("tlsv1 alert unknown ca"); + // OpenSSL error. // Full error is: // error 18 at 0 depth lookup:self signed certificate -#ifndef LIBRESSL_VERSION_NUMBER - string server_expected("certificate verify failed"); -#else - string server_expected("tlsv1 alert unknown ca"); -#endif - EXPECT_EQ(server_expected, server_cb.getCode().message()); - EXPECT_FALSE(client_cb.getCode()); + exps.addError("certificate verify failed"); + exps.checkAsync("server", server_cb); + std::cout << "server: " << exps.getErrMsg() << "\n"; + + exps.clear(); + // Botan client hangs. + exps.addTimeout(); + // LibreSSL and recent OpenSSL do not fail. + exps.addNoError(); + // Old OpenSSL error. + exps.addError("tlsv1 alert unknown ca"); + exps.checkAsync("client", client_cb); + 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()); } -#endif // defined(LIBRESSL_VERSION_NUMBER) || (OPENSSL_VERSION_NUMBER >= 0x10100000L) -#endif // WITH_OPENSSL +} // end of anonymous namespace.