• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 5134090420

31 May 2023 03:12PM UTC coverage: 91.721% (-0.3%) from 91.995%
5134090420

push

github

randombit
Merge GH #3565 Disable noisy/pointless pylint warnings

76048 of 82912 relevant lines covered (91.72%)

11755290.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.43
/src/tests/test_tls_stream_integration.cpp
1
/*
2
* TLS ASIO Stream Client-Server Interaction Test
3
* (C) 2018-2020 Jack Lloyd
4
*     2018-2020 Hannes Rantzsch, René Meusel
5
*     2022      René Meusel, Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#include "tests.h"
11

12
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_ASIO_STREAM) && defined(BOTAN_TARGET_OS_HAS_THREADS)
13

14
   // first version to be compatible with Networking TS (N4656) and boost::beast
15
   #include <boost/version.hpp>
16
   #if BOOST_VERSION >= 106600
17

18
      #include <functional>
19
      #include <optional>
20
      #include <thread>
21

22
      #include <botan/asio_stream.h>
23
      #include <botan/auto_rng.h>
24
      #include <botan/tls_session_manager_noop.h>
25

26
      #include <boost/asio.hpp>
27
      #include <boost/exception/exception.hpp>
28

29
      #include "../cli/tls_helpers.h"  // for Basic_Credentials_Manager
30

31
namespace {
32

33
namespace net = boost::asio;
34

35
using tcp = net::ip::tcp;
36
using error_code = boost::system::error_code;
37
using ssl_stream = Botan::TLS::Stream<net::ip::tcp::socket>;
38
using namespace std::placeholders;
39
using Result = Botan_Tests::Test::Result;
40

41
static const auto k_timeout = std::chrono::seconds(30);
42
static const auto k_endpoints = std::vector<tcp::endpoint>{tcp::endpoint{net::ip::make_address("127.0.0.1"), 8082}};
43

44
enum { max_msg_length = 512 };
45

46
static std::string server_cert() { return Botan_Tests::Test::data_dir() + "/x509/certstor/cert1.crt"; }
36✔
47

48
static std::string server_key() { return Botan_Tests::Test::data_dir() + "/x509/certstor/key01.pem"; }
36✔
49

50
class Timeout_Exception : public std::runtime_error {
51
      using std::runtime_error::runtime_error;
×
52
};
53

54
class Peer {
55
   public:
56
      Peer(std::shared_ptr<const Botan::TLS::Policy> policy, net::io_context& ioc) :
36✔
57
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
58
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(true, "")),
36✔
59
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
60
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
61
            m_timeout_timer(ioc) {}
108✔
62

63
      Peer(std::shared_ptr<const Botan::TLS::Policy> policy,
36✔
64
           net::io_context& ioc,
65
           const std::string& server_cert,
66
           const std::string& server_key) :
36✔
67
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
36✔
68
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(server_cert, server_key)),
36✔
69
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
70
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
71
            m_timeout_timer(ioc) {}
108✔
72

73
      virtual ~Peer() { cancel_timeout(); }
432✔
74

75
      net::mutable_buffer buffer() { return net::buffer(m_data, max_msg_length); }
36✔
76

77
      net::mutable_buffer buffer(size_t size) { return net::buffer(m_data, size); }
16✔
78

79
      std::string message() const { return std::string(m_data); }
12✔
80

81
      // This is a CompletionCondition for net::async_read().
82
      // Our toy protocol always expects a single \0-terminated string.
83
      std::size_t received_zero_byte(const boost::system::error_code& error, std::size_t bytes_transferred) {
152✔
84
         if(error) {
152✔
85
            return 0;
86
         }
87

88
         if(bytes_transferred > 0 && m_data[bytes_transferred - 1] == '\0') {
116✔
89
            return 0;
90
         }
91

92
         return max_msg_length - bytes_transferred;
76✔
93
      }
94

95
      void on_timeout(std::function<void(const std::string&)> cb) { m_on_timeout = std::move(cb); }
72✔
96

97
      void reset_timeout(std::string message) {
296✔
98
         m_timeout_timer.expires_after(k_timeout);
296✔
99
         m_timeout_timer.async_wait([=, this](const error_code& ec) {
2,104✔
100
            if(ec != net::error::operation_aborted)  // timer cancelled
296✔
101
            {
102
               if(m_on_timeout) {
×
103
                  m_on_timeout(message);
×
104
               }
105

106
               // hard-close the underlying transport to maximize the
107
               // probability for all participating peers to error out
108
               // of their pending I/O operations
109
               if(m_stream) {
×
110
                  m_stream->lowest_layer().close();
×
111
               }
112

113
               throw Timeout_Exception("timeout occured: " + message);
×
114
            }
115
         });
296✔
116
      }
296✔
117

118
      void cancel_timeout() { m_timeout_timer.cancel(); }
108✔
119

120
   protected:
121
      std::shared_ptr<Botan::AutoSeeded_RNG> m_rng;
122
      std::shared_ptr<Basic_Credentials_Manager> m_credentials_manager;
123
      std::shared_ptr<Botan::TLS::Session_Manager_Noop> m_session_mgr;
124
      std::shared_ptr<Botan::TLS::Context> m_ctx;
125
      std::unique_ptr<ssl_stream> m_stream;
126
      net::system_timer m_timeout_timer;
127
      std::function<void(const std::string&)> m_on_timeout;
128

129
      char m_data[max_msg_length];
130
};
131

132
class Result_Wrapper {
36✔
133
   public:
134
      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
144✔
135

136
      Result& result() { return m_result; }
72✔
137

138
      void expect_success(const std::string& msg, const error_code& ec) {
352✔
139
         error_code success;
352✔
140
         expect_ec(msg, success, ec);
352✔
141
      }
142

143
      void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec) {
408✔
144
         if(ec != expected) {
408✔
145
            m_result.test_failure(msg, "Unexpected error code: " + ec.message());
×
146
         } else {
147
            m_result.test_success(msg);
408✔
148
         }
149
      }
408✔
150

151
      void confirm(const std::string& msg, bool condition) { m_result.confirm(msg, condition); }
48✔
152

153
      void test_failure(const std::string& msg) { m_result.test_failure(msg); }
×
154

155
   private:
156
      Result m_result;
157
};
158

159
class Server : public Peer,
160
               public std::enable_shared_from_this<Server> {
161
   public:
162
      Server(std::shared_ptr<const Botan::TLS::Policy> policy, net::io_context& ioc, std::string test_name) :
36✔
163
            Peer(policy, ioc, server_cert(), server_key()),
36✔
164
            m_acceptor(ioc),
72✔
165
            m_result("Server (" + test_name + ")"),
36✔
166
            m_short_read_expected(false),
36✔
167
            m_move_before_accept(false) {
144✔
168
         reset_timeout("startup");
36✔
169
      }
36✔
170

171
      // Control messages
172
      // The messages below can be used by the test clients in order to configure the server's behavior during a test
173
      // case.
174
      //
175
      // Tell the server that the next read should result in a StreamTruncated error
176
      std::string expect_short_read_message = "SHORT_READ";
177
      // Prepare the server for the test case "Shutdown No Response"
178
      std::string prepare_shutdown_no_response_message = "SHUTDOWN_NOW";
179

180
      void listen() {
36✔
181
         error_code ec;
36✔
182
         const auto endpoint = k_endpoints.back();
36✔
183

184
         m_acceptor.open(endpoint.protocol(), ec);
36✔
185
         m_acceptor.set_option(net::socket_base::reuse_address(true), ec);
36✔
186
         m_acceptor.bind(endpoint, ec);
36✔
187
         m_acceptor.listen(net::socket_base::max_listen_connections, ec);
36✔
188

189
         m_result.expect_success("listen", ec);
72✔
190

191
         reset_timeout("accept");
36✔
192
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
72✔
193
      }
36✔
194

195
      void expect_short_read() { m_short_read_expected = true; }
4✔
196

197
      void move_before_accept() { m_move_before_accept = true; }
4✔
198

199
      Result_Wrapper result() { return m_result; }
36✔
200

201
   private:
202
      void start_session(const error_code& ec, tcp::socket socket) {
36✔
203
         // Note: If this fails with 'Operation canceled', it likely means the timer expired and the port is taken.
204
         m_result.expect_success("accept", ec);
72✔
205

206
         // Note: If this was a real server, we should create a new session (with its own stream) for each accepted
207
         // connection. In this test we only have one connection.
208

209
         if(m_move_before_accept) {
36✔
210
            // regression test for #2635
211
            ssl_stream s(std::move(socket), m_ctx);
4✔
212
            m_stream = std::unique_ptr<ssl_stream>(new ssl_stream(std::move(s)));
4✔
213
         } else {
4✔
214
            m_stream = std::unique_ptr<ssl_stream>(new ssl_stream(std::move(socket), m_ctx));
64✔
215
         }
216

217
         reset_timeout("handshake");
36✔
218
         m_stream->async_handshake(Botan::TLS::Connection_Side::Server,
72✔
219
                                   std::bind(&Server::handle_handshake, shared_from_this(), _1));
72✔
220
      }
36✔
221

222
      void shutdown() {
8✔
223
         error_code shutdown_ec;
8✔
224
         m_stream->shutdown(shutdown_ec);
8✔
225
         m_result.expect_success("shutdown", shutdown_ec);
16✔
226
         handle_write(error_code{});
8✔
227
      }
8✔
228

229
      void handle_handshake(const error_code& ec) {
36✔
230
         m_result.expect_success("handshake", ec);
72✔
231
         handle_write(error_code{});
36✔
232
      }
36✔
233

234
      void handle_write(const error_code& ec) {
60✔
235
         m_result.expect_success("send_response", ec);
120✔
236
         reset_timeout("read_message");
60✔
237
         net::async_read(*m_stream,
60✔
238
                         buffer(),
60✔
239
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
240
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
241
      }
60✔
242

243
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
244
         if(m_short_read_expected) {
60✔
245
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
246
            cancel_timeout();
16✔
247
            return;
16✔
248
         }
249

250
         if(ec) {
44✔
251
            if(m_stream->shutdown_received()) {
20✔
252
               m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
20✔
253
               reset_timeout("shutdown");
20✔
254
               m_stream->async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
20✔
255
            } else {
256
               m_result.test_failure("Unexpected error code: " + ec.message());
×
257
               cancel_timeout();
×
258
            }
259
            return;
20✔
260
         }
261

262
         m_result.expect_success("read_message", ec);
48✔
263

264
         if(message() == prepare_shutdown_no_response_message) {
36✔
265
            m_short_read_expected = true;
8✔
266
            shutdown();
8✔
267
            return;
8✔
268
         }
269

270
         if(message() == expect_short_read_message) {
28✔
271
            m_short_read_expected = true;
4✔
272
         }
273

274
         reset_timeout("send_response");
16✔
275
         net::async_write(
32✔
276
            *m_stream, buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
277
      }
278

279
      void handle_shutdown(const error_code& ec) {
20✔
280
         m_result.expect_success("shutdown", ec);
40✔
281
         cancel_timeout();
20✔
282
      }
20✔
283

284
   private:
285
      tcp::acceptor m_acceptor;
286
      Result_Wrapper m_result;
287
      bool m_short_read_expected;
288

289
      // regression test for #2635
290
      bool m_move_before_accept;
291
};
292

293
class Client : public Peer {
36✔
294
      static void accept_all(const std::vector<Botan::X509_Certificate>&,
36✔
295
                             const std::vector<std::optional<Botan::OCSP::Response>>&,
296
                             const std::vector<Botan::Certificate_Store*>&,
297
                             Botan::Usage_Type,
298
                             std::string_view,
299
                             const Botan::TLS::Policy&) {}
36✔
300

301
   public:
302
      Client(std::shared_ptr<const Botan::TLS::Policy> policy, net::io_context& ioc) : Peer(policy, ioc) {
36✔
303
         m_ctx->set_verify_callback(accept_all);
36✔
304
         m_stream = std::unique_ptr<ssl_stream>(new ssl_stream(ioc, m_ctx));
36✔
305
      }
36✔
306

307
      ssl_stream& stream() { return *m_stream; }
164✔
308

309
      void close_socket() {
24✔
310
         // Shutdown on TCP level before closing the socket for portable behavior. Otherwise the peer will see a
311
         // connection_reset error rather than EOF on Windows.
312
         // See the remark in
313
         // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html
314
         m_stream->lowest_layer().shutdown(tcp::socket::shutdown_both);
20✔
315
         m_stream->lowest_layer().close();
24✔
316
      }
12✔
317
};
318

319
      #include <boost/asio/yield.hpp>
320

321
class TestBase {
322
   public:
323
      TestBase(net::io_context& ioc,
36✔
324
               std::shared_ptr<const Botan::TLS::Policy> client_policy,
325
               std::shared_ptr<const Botan::TLS::Policy> server_policy,
326
               const std::string& name,
327
               const std::string& config_name) :
36✔
328
            m_name(name + " (" + config_name + ")"),
72✔
329
            m_client(std::make_shared<Client>(client_policy, ioc)),
36✔
330
            m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
36✔
331
            m_result(m_name) {
108✔
332
         m_client->on_timeout(
36✔
333
            [=, this](const std::string& msg) { m_result.test_failure("timeout in client during: " + msg); });
×
334
         m_server->on_timeout(
36✔
335
            [=, this](const std::string& msg) { m_result.test_failure("timeout in server during: " + msg); });
×
336

337
         m_server->listen();
36✔
338
      }
36✔
339

340
      virtual ~TestBase() = default;
108✔
341

342
      virtual void finishAsynchronousWork() {}
20✔
343

344
      void fail(const std::string& msg) { m_result.test_failure(msg); }
×
345

346
      void extend_results(std::vector<Result>& results) {
36✔
347
         results.push_back(m_result.result());
36✔
348
         results.push_back(m_server->result().result());
36✔
349
      }
36✔
350

351
   protected:
352
      //! retire client and server instances after a job well done
353
      void teardown() { m_client->cancel_timeout(); }
24✔
354

355
   protected:
356
      std::string m_name;
357

358
      std::shared_ptr<Client> m_client;
359
      std::shared_ptr<Server> m_server;
360

361
      Result_Wrapper m_result;
362
};
363

364
class Synchronous_Test : public TestBase {
16✔
365
   public:
366
      using TestBase::TestBase;
16✔
367

368
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
369

370
      void run(const error_code&) {
16✔
371
         m_client_thread = std::thread([this] {
16✔
372
            try {
16✔
373
               this->run_synchronous_client();
16✔
374
            } catch(const std::exception& ex) {
×
375
               m_result.test_failure(std::string("sync client failed with: ") + ex.what());
×
376
            } catch(const boost::exception&) {
×
377
               m_result.test_failure("sync client failed with boost exception");
×
378
            } catch(...) {
×
379
               m_result.test_failure("sync client failed with unknown error");
×
380
            }
×
381
         });
32✔
382
      }
16✔
383

384
      virtual void run_synchronous_client() = 0;
385

386
   private:
387
      std::thread m_client_thread;
388
};
389

390
/* In this test case both parties perform the handshake, exchange a message, and do a full shutdown.
391
 *
392
 * The client expects the server to echo the same message it sent. The client then initiates the shutdown. The server is
393
 * expected to receive a close_notify and complete its shutdown with an error_code Success, the client is expected to
394
 * receive a close_notify and complete its shutdown with an error_code EOF.
395
 */
396
class Test_Conversation : public TestBase,
397
                          public net::coroutine,
398
                          public std::enable_shared_from_this<Test_Conversation> {
399
   public:
400
      Test_Conversation(net::io_context& ioc,
8✔
401
                        std::string config_name,
402
                        std::shared_ptr<const Botan::TLS::Policy> client_policy,
403
                        std::shared_ptr<const Botan::TLS::Policy> server_policy,
404
                        std::string test_name = "Test Conversation") :
8✔
405
            TestBase(ioc, client_policy, server_policy, test_name, config_name) {}
24✔
406

407
      void run(const error_code& ec) {
56✔
408
         static auto test_case = &Test_Conversation::run;
56✔
409
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
410

411
         reenter(*this) {
56✔
412
            m_client->reset_timeout("connect");
8✔
413
            yield net::async_connect(
16✔
414
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
415
            m_result.expect_success("connect", ec);
16✔
416

417
            m_client->reset_timeout("handshake");
8✔
418
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
419
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
420
            m_result.expect_success("handshake", ec);
16✔
421

422
            m_client->reset_timeout("send_message");
8✔
423
            yield net::async_write(m_client->stream(),
24✔
424
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
425
                                   std::bind(test_case, shared_from_this(), _1));
16✔
426
            m_result.expect_success("send_message", ec);
16✔
427

428
            m_client->reset_timeout("receive_response");
8✔
429
            yield net::async_read(m_client->stream(),
24✔
430
                                  m_client->buffer(),
8✔
431
                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
8✔
432
                                  std::bind(test_case, shared_from_this(), _1));
16✔
433
            m_result.expect_success("receive_response", ec);
16✔
434
            m_result.confirm("correct message", m_client->message() == message);
16✔
435

436
            m_client->reset_timeout("shutdown");
8✔
437
            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
438
            m_result.expect_success("shutdown", ec);
16✔
439

440
            m_client->reset_timeout("await close_notify");
8✔
441
            yield net::async_read(m_client->stream(), m_client->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
442
            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
16✔
443
            m_result.expect_ec("closed with EOF", net::error::eof, ec);
8✔
444

445
            teardown();
64✔
446
         }
56✔
447
      }
56✔
448
};
449

450
class Test_Conversation_Sync : public Synchronous_Test {
4✔
451
   public:
452
      Test_Conversation_Sync(net::io_context& ioc,
4✔
453
                             std::string config_name,
454
                             std::shared_ptr<const Botan::TLS::Policy> client_policy,
455
                             std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
456
            Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
20✔
457

458
      void run_synchronous_client() override {
4✔
459
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
460
         error_code ec;
4✔
461

462
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
463
         m_result.expect_success("connect", ec);
8✔
464

465
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
466
         m_result.expect_success("handshake", ec);
8✔
467

468
         net::write(m_client->stream(),
4✔
469
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
470
                    ec);
471
         m_result.expect_success("send_message", ec);
8✔
472

473
         net::read(
8✔
474
            m_client->stream(), m_client->buffer(), std::bind(&Client::received_zero_byte, m_client.get(), _1, _2), ec);
4✔
475
         m_result.expect_success("receive_response", ec);
8✔
476
         m_result.confirm("correct message", m_client->message() == message);
8✔
477

478
         m_client->stream().shutdown(ec);
4✔
479
         m_result.expect_success("shutdown", ec);
8✔
480

481
         net::read(m_client->stream(), m_client->buffer(), ec);
4✔
482
         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
483
         m_result.expect_ec("closed with EOF", net::error::eof, ec);
4✔
484

485
         teardown();
8✔
486
      }
4✔
487
};
488

489
/* In this test case the client shuts down the SSL connection, but does not wait for the server's response before
490
 * closing the socket. Accordingly, it will not receive the server's close_notify alert. Instead, the async_read
491
 * operation will be aborted. The server should be able to successfully shutdown nonetheless.
492
 */
493
class Test_Eager_Close : public TestBase,
494
                         public net::coroutine,
495
                         public std::enable_shared_from_this<Test_Eager_Close> {
496
   public:
497
      Test_Eager_Close(net::io_context& ioc,
4✔
498
                       std::string config_name,
499
                       std::shared_ptr<const Botan::TLS::Policy> client_policy,
500
                       std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
501
            TestBase(ioc, client_policy, server_policy, "Test Eager Close", config_name) {}
20✔
502

503
      void run(const error_code& ec) {
16✔
504
         static auto test_case = &Test_Eager_Close::run;
16✔
505
         reenter(*this) {
16✔
506
            m_client->reset_timeout("connect");
4✔
507
            yield net::async_connect(
8✔
508
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
509
            m_result.expect_success("connect", ec);
8✔
510

511
            m_client->reset_timeout("handshake");
4✔
512
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
513
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
514
            m_result.expect_success("handshake", ec);
8✔
515

516
            m_client->reset_timeout("shutdown");
4✔
517
            yield m_client->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
518
            m_result.expect_success("shutdown", ec);
8✔
519

520
            m_client->close_socket();
4✔
521
            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
522

523
            teardown();
20✔
524
         }
16✔
525
      }
16✔
526
};
527

528
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
529
   public:
530
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
531
                            std::string config_name,
532
                            std::shared_ptr<const Botan::TLS::Policy> client_policy,
533
                            std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
534
            Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
20✔
535

536
      void run_synchronous_client() override {
4✔
537
         error_code ec;
4✔
538

539
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
540
         m_result.expect_success("connect", ec);
8✔
541

542
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
543
         m_result.expect_success("handshake", ec);
8✔
544

545
         m_client->stream().shutdown(ec);
4✔
546
         m_result.expect_success("shutdown", ec);
8✔
547

548
         m_client->close_socket();
4✔
549
         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
550

551
         teardown();
4✔
552
      }
4✔
553
};
554

555
/* In this test case the client closes the socket without properly shutting down the connection.
556
 * The server should see a StreamTruncated error.
557
 */
558
class Test_Close_Without_Shutdown : public TestBase,
559
                                    public net::coroutine,
560
                                    public std::enable_shared_from_this<Test_Close_Without_Shutdown> {
561
   public:
562
      Test_Close_Without_Shutdown(net::io_context& ioc,
4✔
563
                                  std::string config_name,
564
                                  std::shared_ptr<const Botan::TLS::Policy> client_policy,
565
                                  std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
566
            TestBase(ioc, client_policy, server_policy, "Test Close Without Shutdown", config_name) {}
20✔
567

568
      void run(const error_code& ec) {
20✔
569
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
570
         reenter(*this) {
20✔
571
            m_client->reset_timeout("connect");
4✔
572
            yield net::async_connect(
8✔
573
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
574
            m_result.expect_success("connect", ec);
8✔
575

576
            m_client->reset_timeout("handshake");
4✔
577
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
578
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
579
            m_result.expect_success("handshake", ec);
8✔
580

581
            // send the control message to configure the server to expect a short-read
582
            m_client->reset_timeout("send expect_short_read message");
4✔
583
            yield net::async_write(
8✔
584
               m_client->stream(),
4✔
585
               net::buffer(m_server->expect_short_read_message.c_str(),
4✔
586
                           m_server->expect_short_read_message.size() + 1),  // including \0 termination
4✔
587
               std::bind(test_case, shared_from_this(), _1));
8✔
588
            m_result.expect_success("send expect_short_read message", ec);
8✔
589

590
            // read the confirmation of the control message above
591
            m_client->reset_timeout("receive_response");
4✔
592
            yield net::async_read(m_client->stream(),
12✔
593
                                  m_client->buffer(),
4✔
594
                                  std::bind(&Client::received_zero_byte, m_client.get(), _1, _2),
4✔
595
                                  std::bind(test_case, shared_from_this(), _1));
8✔
596
            m_result.expect_success("receive_response", ec);
8✔
597

598
            m_client->close_socket();
4✔
599
            m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
600

601
            teardown();
24✔
602
         }
20✔
603
      }
20✔
604
};
605

606
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
607
   public:
608
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
609
                                       std::string config_name,
610
                                       std::shared_ptr<const Botan::TLS::Policy> client_policy,
611
                                       std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
612
            Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
20✔
613

614
      void run_synchronous_client() override {
4✔
615
         error_code ec;
4✔
616
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
617
         m_result.expect_success("connect", ec);
8✔
618

619
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
620
         m_result.expect_success("handshake", ec);
8✔
621

622
         m_server->expect_short_read();
4✔
623

624
         m_client->close_socket();
4✔
625
         m_result.confirm("did not receive close_notify", !m_client->stream().shutdown_received());
8✔
626

627
         teardown();
4✔
628
      }
4✔
629
};
630

631
/* In this test case the server shuts down the connection but the client doesn't send the corresponding close_notify
632
 * response. Instead, it closes the socket immediately.
633
 * The server should see a short-read error.
634
 */
635
class Test_No_Shutdown_Response : public TestBase,
636
                                  public net::coroutine,
637
                                  public std::enable_shared_from_this<Test_No_Shutdown_Response> {
638
   public:
639
      Test_No_Shutdown_Response(net::io_context& ioc,
4✔
640
                                std::string config_name,
641
                                std::shared_ptr<const Botan::TLS::Policy> client_policy,
642
                                std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
643
            TestBase(ioc, client_policy, server_policy, "Test No Shutdown Response", config_name) {}
20✔
644

645
      void run(const error_code& ec) {
20✔
646
         static auto test_case = &Test_No_Shutdown_Response::run;
20✔
647
         reenter(*this) {
20✔
648
            m_client->reset_timeout("connect");
4✔
649
            yield net::async_connect(
8✔
650
               m_client->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
651
            m_result.expect_success("connect", ec);
8✔
652

653
            m_client->reset_timeout("handshake");
4✔
654
            yield m_client->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
655
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
656
            m_result.expect_success("handshake", ec);
8✔
657

658
            // send a control message to make the server shut down
659
            m_client->reset_timeout("send shutdown message");
4✔
660
            yield net::async_write(
8✔
661
               m_client->stream(),
4✔
662
               net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
4✔
663
                           m_server->prepare_shutdown_no_response_message.size() + 1),  // including \0 termination
4✔
664
               std::bind(test_case, shared_from_this(), _1));
8✔
665
            m_result.expect_success("send shutdown message", ec);
8✔
666

667
            // read the server's close-notify message
668
            m_client->reset_timeout("read close_notify");
4✔
669
            yield net::async_read(m_client->stream(), m_client->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
670
            m_result.expect_ec("read gives EOF", net::error::eof, ec);
4✔
671
            m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
672

673
            // close the socket rather than shutting down
674
            m_client->close_socket();
4✔
675

676
            teardown();
24✔
677
         }
20✔
678
      }
20✔
679
};
680

681
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
682
   public:
683
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
684
                                     std::string config_name,
685
                                     std::shared_ptr<const Botan::TLS::Policy> client_policy,
686
                                     std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
687
            Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
20✔
688

689
      void run_synchronous_client() override {
4✔
690
         error_code ec;
4✔
691
         net::connect(m_client->stream().lowest_layer(), k_endpoints, ec);
4✔
692
         m_result.expect_success("connect", ec);
8✔
693

694
         m_client->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
695
         m_result.expect_success("handshake", ec);
8✔
696

697
         net::write(m_client->stream(),
8✔
698
                    net::buffer(m_server->prepare_shutdown_no_response_message.c_str(),
4✔
699
                                m_server->prepare_shutdown_no_response_message.size() + 1),  // including \0 termination
4✔
700
                    ec);
701
         m_result.expect_success("send expect_short_read message", ec);
8✔
702

703
         net::read(m_client->stream(), m_client->buffer(), ec);
4✔
704
         m_result.expect_ec("read gives EOF", net::error::eof, ec);
4✔
705
         m_result.confirm("received close_notify", m_client->stream().shutdown_received());
8✔
706

707
         // close the socket rather than shutting down
708
         m_client->close_socket();
4✔
709

710
         teardown();
4✔
711
      }
4✔
712
};
713

714
class Test_Conversation_With_Move : public Test_Conversation {
4✔
715
   public:
716
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
717
                                  std::string config_name,
718
                                  std::shared_ptr<const Botan::TLS::Policy> client_policy,
719
                                  std::shared_ptr<const Botan::TLS::Policy> server_policy) :
4✔
720
            Test_Conversation(
721
               ioc, std::move(config_name), client_policy, server_policy, "Test Conversation With Move") {
24✔
722
         m_server->move_before_accept();
4✔
723
      }
4✔
724
};
725

726
      #include <boost/asio/unyield.hpp>
727

728
struct SystemConfiguration {
729
      std::string name;
730

731
      std::shared_ptr<Botan::TLS::Text_Policy> client_policy;
732
      std::shared_ptr<Botan::TLS::Text_Policy> server_policy;
733

734
      SystemConfiguration(std::string n, std::string cp, std::string sp) :
4✔
735
            name(std::move(n)),
8✔
736
            client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
737
            server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
738

739
      template <typename TestT>
740
      void run(std::vector<Result>& results) {
36✔
741
         net::io_context ioc;
36✔
742

743
         auto t = std::make_shared<TestT>(ioc, name, server_policy, client_policy);
36✔
744

745
         t->run(error_code{});
36✔
746

747
         while(true) {
748
            try {
749
               ioc.run();
36✔
750
               break;
751
            } catch(const Timeout_Exception&) { /* timeout is reported via Test::Result object */
×
752
            } catch(const boost::exception&) {
×
753
               t->fail("boost exception");
×
754
            } catch(const std::exception& ex) {
×
755
               t->fail(ex.what());
×
756
            }
757
         }
758

759
         t->finishAsynchronousWork();
36✔
760
         t->extend_results(results);
36✔
761
      }
36✔
762
};
763

764
std::vector<SystemConfiguration> get_configurations() {
1✔
765
   return {
1✔
766
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
767
      #if defined(BOTAN_HAS_TLS_13)
768
         SystemConfiguration(
769
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
770
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
771
                             "allow_tls12=true\nallow_tls13=false",
772
                             "allow_tls12=true\nallow_tls13=true"),
773
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
774
                             "allow_tls12=true\nallow_tls13=true",
775
                             "allow_tls12=true\nallow_tls13=false"),
776
      #endif
777
   };
13✔
778
}
779

780
}  // namespace
781

782
namespace Botan_Tests {
783

784
class Tls_Stream_Integration_Tests final : public Test {
×
785
   public:
786
      std::vector<Test::Result> run() override {
1✔
787
         std::vector<Test::Result> results;
1✔
788

789
         auto configs = get_configurations();
1✔
790
         for(auto& config : configs) {
5✔
791
            config.run<Test_Conversation>(results);
4✔
792
            config.run<Test_Eager_Close>(results);
4✔
793
            config.run<Test_Close_Without_Shutdown>(results);
4✔
794
            config.run<Test_No_Shutdown_Response>(results);
4✔
795
            config.run<Test_Conversation_Sync>(results);
4✔
796
            config.run<Test_Eager_Close_Sync>(results);
4✔
797
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
798
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
799
            config.run<Test_Conversation_With_Move>(results);
4✔
800
         }
801

802
         return results;
1✔
803
      }
1✔
804
};
805

806
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
807

808
}  // namespace Botan_Tests
809

810
   #endif  // BOOST_VERSION
811
#endif     // BOTAN_HAS_TLS && BOTAN_HAS_BOOST_ASIO
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc