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

randombit / botan / 6564375290

18 Oct 2023 12:18PM UTC coverage: 91.715% (+0.01%) from 91.704%
6564375290

push

github

web-flow
Merge pull request #3764 from Rohde-Schwarz/refactor/modernize_asio_stream

Refactor: Modernize asio TLS Stream

80132 of 87371 relevant lines covered (91.71%)

8636754.93 hits per line

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

93.59
/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
   #include <boost/version.hpp>
15
   #if BOOST_VERSION >= 107300
16

17
      #include <functional>
18
      #include <memory>
19
      #include <optional>
20
      #include <thread>
21
      #include <utility>
22

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

27
      #include <boost/asio.hpp>
28
      #include <boost/asio/yield.hpp>
29
      #include <boost/exception/exception.hpp>
30

31
      #include "../cli/tls_helpers.h"  // for Basic_Credentials_Manager
32

33
namespace {
34

35
namespace net = boost::asio;
36

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

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

46
enum { max_msg_length = 512 };
47

48
std::string server_cert() {
36✔
49
   return Botan_Tests::Test::data_dir() + "/x509/certstor/cert1.crt";
36✔
50
}
51

52
std::string server_key() {
36✔
53
   return Botan_Tests::Test::data_dir() + "/x509/certstor/key01.pem";
36✔
54
}
55

56
class Timeout_Exception : public std::runtime_error {
57
      using std::runtime_error::runtime_error;
×
58
};
59

60
class Peer {
61
   public:
62
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) :
36✔
63
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
64
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(true, "")),
36✔
65
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
66
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
67
            m_timeout_timer(ioc) {}
108✔
68

69
      Peer(const std::shared_ptr<const Botan::TLS::Policy>& policy,
36✔
70
           net::io_context& ioc,
71
           const std::string& server_cert,
72
           const std::string& server_key) :
36✔
73
            m_rng(std::make_shared<Botan::AutoSeeded_RNG>()),
36✔
74
            m_credentials_manager(std::make_shared<Basic_Credentials_Manager>(server_cert, server_key)),
36✔
75
            m_session_mgr(std::make_shared<Botan::TLS::Session_Manager_Noop>()),
76
            m_ctx(std::make_shared<Botan::TLS::Context>(m_credentials_manager, m_rng, m_session_mgr, policy)),
36✔
77
            m_timeout_timer(ioc) {}
108✔
78

79
      Peer(const Peer& other) = delete;
80
      Peer(Peer&& other) = delete;
81
      Peer& operator=(const Peer& other) = delete;
82
      Peer& operator=(Peer&& other) = delete;
83

84
      // NOLINTNEXTLINE(*-exception-escape)
85
      virtual ~Peer() { cancel_timeout(); }
432✔
86

87
      net::mutable_buffer buffer() { return net::buffer(m_data, max_msg_length); }
36✔
88

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

91
      std::string message() const { return std::string(m_data); }
12✔
92

93
      // This is a CompletionCondition for net::async_read().
94
      // Our toy protocol always expects a single \0-terminated string.
95
      std::size_t received_zero_byte(const boost::system::error_code& error, std::size_t bytes_transferred) {
152✔
96
         if(error) {
152✔
97
            return 0;
98
         }
99

100
         if(bytes_transferred > 0 && m_data[bytes_transferred - 1] == '\0') {
116✔
101
            return 0;
102
         }
103

104
         return max_msg_length - bytes_transferred;
76✔
105
      }
106

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

109
      void reset_timeout(const std::string& message) {
296✔
110
         m_timeout_timer.expires_after(k_timeout);
296✔
111
         m_timeout_timer.async_wait([=, this](const error_code& ec) {
1,512✔
112
            if(ec != net::error::operation_aborted)  // timer cancelled
296✔
113
            {
114
               if(m_on_timeout) {
×
115
                  m_on_timeout(message);
×
116
               }
117

118
               // hard-close the underlying transport to maximize the
119
               // probability for all participating peers to error out
120
               // of their pending I/O operations
121
               if(m_stream) {
×
122
                  m_stream->lowest_layer().close();
×
123
               }
124

125
               throw Timeout_Exception("timeout occured: " + message);
×
126
            }
127
         });
296✔
128
      }
296✔
129

130
      void cancel_timeout() { m_timeout_timer.cancel(); }
108✔
131

132
      ssl_stream& stream() { return *m_stream; }
188✔
133

134
      void create_stream(std::unique_ptr<ssl_stream> stream) {
72✔
135
         BOTAN_ASSERT(!m_stream, "Stream is only assigned once");
72✔
136
         m_stream = std::move(stream);
72✔
137
      }
72✔
138

139
      std::shared_ptr<Botan::TLS::Context> ctx() { return m_ctx; }
216✔
140

141
   private:
142
      std::shared_ptr<Botan::AutoSeeded_RNG> m_rng;
143
      std::shared_ptr<Basic_Credentials_Manager> m_credentials_manager;
144
      std::shared_ptr<Botan::TLS::Session_Manager_Noop> m_session_mgr;
145
      std::shared_ptr<Botan::TLS::Context> m_ctx;
146
      std::unique_ptr<ssl_stream> m_stream;
147
      net::system_timer m_timeout_timer;
148
      std::function<void(const std::string&)> m_on_timeout;
149

150
      char m_data[max_msg_length];
151
};
152

153
class Result_Wrapper {
36✔
154
   public:
155
      Result_Wrapper(std::string name) : m_result(std::move(name)) {}
144✔
156

157
      Result& result() { return m_result; }
72✔
158

159
      void expect_success(const std::string& msg, const error_code& ec) {
352✔
160
         error_code success;
352✔
161
         expect_ec(msg, success, ec);
352✔
162
      }
163

164
      void expect_ec(const std::string& msg, const error_code& expected, const error_code& ec) {
408✔
165
         if(ec != expected) {
408✔
166
            m_result.test_failure(msg, "Unexpected error code: " + ec.message());
×
167
         } else {
168
            m_result.test_success(msg);
408✔
169
         }
170
      }
408✔
171

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

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

176
   private:
177
      Result m_result;
178
};
179

180
// Control messages
181
// The messages below can be used by the test clients in order to configure the server's behavior during a test
182
// case.
183
//
184
// Tell the server that the next read should result in a StreamTruncated error
185
const std::string EXPECT_SHORT_READ_MESSAGE = "SHORT_READ";
186
// Prepare the server for the test case "Shutdown No Response"
187
const std::string PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE = "SHUTDOWN_NOW";
188

189
class Server : public Peer,
190
               public std::enable_shared_from_this<Server> {
191
   public:
192
      Server(const std::shared_ptr<const Botan::TLS::Policy>& policy,
36✔
193
             net::io_context& ioc,
194
             const std::string& test_name) :
36✔
195
            Peer(policy, ioc, server_cert(), server_key()),
36✔
196
            m_acceptor(ioc),
36✔
197
            m_result("Server (" + test_name + ")"),
36✔
198
            m_short_read_expected(false),
36✔
199
            m_move_before_accept(false) {
108✔
200
         reset_timeout("startup");
36✔
201
      }
36✔
202

203
      void listen() {
36✔
204
         error_code ec;
36✔
205
         const auto& endpoint = k_endpoints.back();
36✔
206

207
         m_acceptor.open(endpoint.protocol(), ec);
36✔
208
         m_acceptor.set_option(net::socket_base::reuse_address(true), ec);
36✔
209
         m_acceptor.bind(endpoint, ec);
36✔
210
         m_acceptor.listen(net::socket_base::max_listen_connections, ec);
36✔
211

212
         m_result.expect_success("listen", ec);
72✔
213

214
         reset_timeout("accept");
36✔
215
         m_acceptor.async_accept(std::bind(&Server::start_session, shared_from_this(), _1, _2));
72✔
216
      }
36✔
217

218
      void expect_short_read() { m_short_read_expected = true; }
4✔
219

220
      void move_before_accept() { m_move_before_accept = true; }
4✔
221

222
      Result_Wrapper result() { return m_result; }
36✔
223

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

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

232
         if(m_move_before_accept) {
36✔
233
            // regression test for #2635
234
            ssl_stream s(std::move(socket), ctx());
8✔
235
            create_stream(std::make_unique<ssl_stream>(std::move(s)));
8✔
236
         } else {
4✔
237
            create_stream(std::make_unique<ssl_stream>(std::move(socket), ctx()));
64✔
238
         }
239

240
         reset_timeout("handshake");
36✔
241
         stream().async_handshake(Botan::TLS::Connection_Side::Server,
72✔
242
                                  std::bind(&Server::handle_handshake, shared_from_this(), _1));
72✔
243
      }
36✔
244

245
      void shutdown() {
8✔
246
         error_code shutdown_ec;
8✔
247
         stream().shutdown(shutdown_ec);
8✔
248
         m_result.expect_success("shutdown", shutdown_ec);
16✔
249
         handle_write(error_code{});
8✔
250
      }
8✔
251

252
      void handle_handshake(const error_code& ec) {
36✔
253
         m_result.expect_success("handshake", ec);
72✔
254
         handle_write(error_code{});
36✔
255
      }
36✔
256

257
      void handle_write(const error_code& ec) {
60✔
258
         m_result.expect_success("send_response", ec);
120✔
259
         reset_timeout("read_message");
60✔
260
         net::async_read(stream(),
60✔
261
                         buffer(),
60✔
262
                         std::bind(&Server::received_zero_byte, shared_from_this(), _1, _2),
120✔
263
                         std::bind(&Server::handle_read, shared_from_this(), _1, _2));
120✔
264
      }
60✔
265

266
      void handle_read(const error_code& ec, size_t bytes_transferred = 0) {
60✔
267
         if(m_short_read_expected) {
60✔
268
            m_result.expect_ec("received stream truncated error", Botan::TLS::StreamTruncated, ec);
16✔
269
            cancel_timeout();
16✔
270
            return;
16✔
271
         }
272

273
         if(ec) {
44✔
274
            if(stream().shutdown_received()) {
20✔
275
               m_result.expect_ec("received EOF after close_notify", net::error::eof, ec);
20✔
276
               reset_timeout("shutdown");
20✔
277
               stream().async_shutdown(std::bind(&Server::handle_shutdown, shared_from_this(), _1));
20✔
278
            } else {
279
               m_result.test_failure("Unexpected error code: " + ec.message());
×
280
               cancel_timeout();
×
281
            }
282
            return;
20✔
283
         }
284

285
         m_result.expect_success("read_message", ec);
48✔
286

287
         if(message() == PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE) {
36✔
288
            m_short_read_expected = true;
8✔
289
            shutdown();
8✔
290
            return;
8✔
291
         }
292

293
         if(message() == EXPECT_SHORT_READ_MESSAGE) {
28✔
294
            m_short_read_expected = true;
4✔
295
         }
296

297
         reset_timeout("send_response");
16✔
298
         net::async_write(
32✔
299
            stream(), buffer(bytes_transferred), std::bind(&Server::handle_write, shared_from_this(), _1));
16✔
300
      }
301

302
      void handle_shutdown(const error_code& ec) {
20✔
303
         m_result.expect_success("shutdown", ec);
40✔
304
         cancel_timeout();
20✔
305
      }
20✔
306

307
   private:
308
      tcp::acceptor m_acceptor;
309
      Result_Wrapper m_result;
310
      bool m_short_read_expected;
311

312
      // regression test for #2635
313
      bool m_move_before_accept;
314
};
315

316
class Client : public Peer {
36✔
317
      static void accept_all(const std::vector<Botan::X509_Certificate>&,
36✔
318
                             const std::vector<std::optional<Botan::OCSP::Response>>&,
319
                             const std::vector<Botan::Certificate_Store*>&,
320
                             Botan::Usage_Type,
321
                             std::string_view,
322
                             const Botan::TLS::Policy&) {}
36✔
323

324
   public:
325
      Client(const std::shared_ptr<const Botan::TLS::Policy>& policy, net::io_context& ioc) : Peer(policy, ioc) {
36✔
326
         ctx()->set_verify_callback(accept_all);
36✔
327
         create_stream(std::make_unique<ssl_stream>(ioc, ctx()));
72✔
328
      }
36✔
329

330
      void close_socket() {
24✔
331
         // Shutdown on TCP level before closing the socket for portable behavior. Otherwise the peer will see a
332
         // connection_reset error rather than EOF on Windows.
333
         // See the remark in
334
         // https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/basic_stream_socket/close/overload1.html
335
         stream().lowest_layer().shutdown(tcp::socket::shutdown_both);
20✔
336
         stream().lowest_layer().close();
24✔
337
      }
12✔
338
};
339

340
class TestBase {
341
   public:
342
      TestBase(net::io_context& ioc,
36✔
343
               const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
344
               const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
345
               const std::string& name,
346
               const std::string& config_name) :
36✔
347
            m_name(name + " (" + config_name + ")"),
72✔
348
            m_client(std::make_shared<Client>(client_policy, ioc)),
36✔
349
            m_server(std::make_shared<Server>(server_policy, ioc, m_name)),
36✔
350
            m_result(m_name) {
108✔
351
         m_client->on_timeout(
36✔
352
            [=, this](const std::string& msg) { m_result.test_failure("timeout in client during: " + msg); });
×
353
         m_server->on_timeout(
36✔
354
            [=, this](const std::string& msg) { m_result.test_failure("timeout in server during: " + msg); });
×
355

356
         m_server->listen();
36✔
357
      }
36✔
358

359
      virtual ~TestBase() = default;
108✔
360

361
      TestBase(const TestBase& other) = delete;
362
      TestBase(TestBase&& other) = delete;
363
      TestBase& operator=(const TestBase& other) = delete;
364
      TestBase& operator=(TestBase&& other) = delete;
365

366
      virtual void finishAsynchronousWork() {}
20✔
367

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

370
      void extend_results(std::vector<Result>& results) {
36✔
371
         results.push_back(m_result.result());
36✔
372
         results.push_back(m_server->result().result());
36✔
373
      }
36✔
374

375
   protected:
376
      //! retire client and server instances after a job well done
377
      void teardown() { m_client->cancel_timeout(); }
24✔
378

379
      std::shared_ptr<Client>& client() { return m_client; }
44✔
380

381
      std::shared_ptr<Server>& server() { return m_server; }
8✔
382

383
      Result_Wrapper& result() { return m_result; }
156✔
384

385
   private:
386
      std::string m_name;
387

388
      std::shared_ptr<Client> m_client;
389
      std::shared_ptr<Server> m_server;
390

391
      Result_Wrapper m_result;
392
};
393

394
class Synchronous_Test : public TestBase {
16✔
395
   public:
396
      using TestBase::TestBase;
16✔
397

398
      void finishAsynchronousWork() override { m_client_thread.join(); }
×
399

400
      void run(const error_code&) {
16✔
401
         m_client_thread = std::thread([this] {
16✔
402
            try {
16✔
403
               this->run_synchronous_client();
16✔
404
            } catch(const std::exception& ex) {
×
405
               result().test_failure(std::string("sync client failed with: ") + ex.what());
×
406
            } catch(const boost::exception&) {
×
407
               result().test_failure("sync client failed with boost exception");
×
408
            } catch(...) {
×
409
               result().test_failure("sync client failed with unknown error");
×
410
            }
×
411
         });
32✔
412
      }
16✔
413

414
      virtual void run_synchronous_client() = 0;
415

416
   private:
417
      std::thread m_client_thread;
418
};
419

420
/* In this test case both parties perform the handshake, exchange a message, and do a full shutdown.
421
 *
422
 * The client expects the server to echo the same message it sent. The client then initiates the shutdown. The server is
423
 * expected to receive a close_notify and complete its shutdown with an error_code Success, the client is expected to
424
 * receive a close_notify and complete its shutdown with an error_code EOF.
425
 */
426
class Test_Conversation : public TestBase,
427
                          public net::coroutine,
428
                          public std::enable_shared_from_this<Test_Conversation> {
429
   public:
430
      Test_Conversation(net::io_context& ioc,
8✔
431
                        const std::string& config_name,
432
                        const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
433
                        const std::shared_ptr<const Botan::TLS::Policy>& server_policy,
434
                        const std::string& test_name = "Test Conversation") :
8✔
435
            TestBase(ioc, client_policy, server_policy, test_name, config_name) {}
8✔
436

437
      void run(const error_code& ec) {
56✔
438
         static auto test_case = &Test_Conversation::run;
56✔
439
         const std::string message("Time is an illusion. Lunchtime doubly so.");
56✔
440

441
         reenter(*this) {
56✔
442
            client()->reset_timeout("connect");
8✔
443
            yield net::async_connect(
16✔
444
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
16✔
445
            result().expect_success("connect", ec);
16✔
446

447
            client()->reset_timeout("handshake");
8✔
448
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
16✔
449
                                                     std::bind(test_case, shared_from_this(), _1));
16✔
450
            result().expect_success("handshake", ec);
16✔
451

452
            client()->reset_timeout("send_message");
8✔
453
            yield net::async_write(client()->stream(),
24✔
454
                                   net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
8✔
455
                                   std::bind(test_case, shared_from_this(), _1));
16✔
456
            result().expect_success("send_message", ec);
16✔
457

458
            client()->reset_timeout("receive_response");
8✔
459
            yield net::async_read(client()->stream(),
24✔
460
                                  client()->buffer(),
8✔
461
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
8✔
462
                                  std::bind(test_case, shared_from_this(), _1));
16✔
463
            result().expect_success("receive_response", ec);
16✔
464
            result().confirm("correct message", client()->message() == message);
16✔
465

466
            client()->reset_timeout("shutdown");
8✔
467
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
8✔
468
            result().expect_success("shutdown", ec);
16✔
469

470
            client()->reset_timeout("await close_notify");
8✔
471
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
8✔
472
            result().confirm("received close_notify", client()->stream().shutdown_received());
16✔
473
            result().expect_ec("closed with EOF", net::error::eof, ec);
8✔
474

475
            teardown();
64✔
476
         }
56✔
477
      }
56✔
478
};
479

480
class Test_Conversation_Sync : public Synchronous_Test {
4✔
481
   public:
482
      Test_Conversation_Sync(net::io_context& ioc,
4✔
483
                             const std::string& config_name,
484
                             const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
485
                             const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
486
            Synchronous_Test(ioc, client_policy, server_policy, "Test Conversation Sync", config_name) {}
12✔
487

488
      void run_synchronous_client() override {
4✔
489
         const std::string message("Time is an illusion. Lunchtime doubly so.");
4✔
490
         error_code ec;
4✔
491

492
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
493
         result().expect_success("connect", ec);
8✔
494

495
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
496
         result().expect_success("handshake", ec);
8✔
497

498
         net::write(client()->stream(),
4✔
499
                    net::buffer(message.c_str(), message.size() + 1),  // including \0 termination
4✔
500
                    ec);
501
         result().expect_success("send_message", ec);
8✔
502

503
         net::read(
8✔
504
            client()->stream(), client()->buffer(), std::bind(&Client::received_zero_byte, client().get(), _1, _2), ec);
4✔
505
         result().expect_success("receive_response", ec);
8✔
506
         result().confirm("correct message", client()->message() == message);
8✔
507

508
         client()->stream().shutdown(ec);
4✔
509
         result().expect_success("shutdown", ec);
8✔
510

511
         net::read(client()->stream(), client()->buffer(), ec);
4✔
512
         result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
513
         result().expect_ec("closed with EOF", net::error::eof, ec);
4✔
514

515
         teardown();
8✔
516
      }
4✔
517
};
518

519
/* In this test case the client shuts down the SSL connection, but does not wait for the server's response before
520
 * closing the socket. Accordingly, it will not receive the server's close_notify alert. Instead, the async_read
521
 * operation will be aborted. The server should be able to successfully shutdown nonetheless.
522
 */
523
class Test_Eager_Close : public TestBase,
524
                         public net::coroutine,
525
                         public std::enable_shared_from_this<Test_Eager_Close> {
526
   public:
527
      Test_Eager_Close(net::io_context& ioc,
4✔
528
                       const std::string& config_name,
529
                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
530
                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
531
            TestBase(ioc, client_policy, server_policy, "Test Eager Close", config_name) {}
8✔
532

533
      void run(const error_code& ec) {
16✔
534
         static auto test_case = &Test_Eager_Close::run;
16✔
535
         reenter(*this) {
16✔
536
            client()->reset_timeout("connect");
4✔
537
            yield net::async_connect(
8✔
538
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
539
            result().expect_success("connect", ec);
8✔
540

541
            client()->reset_timeout("handshake");
4✔
542
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
543
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
544
            result().expect_success("handshake", ec);
8✔
545

546
            client()->reset_timeout("shutdown");
4✔
547
            yield client()->stream().async_shutdown(std::bind(test_case, shared_from_this(), _1));
4✔
548
            result().expect_success("shutdown", ec);
8✔
549

550
            client()->close_socket();
4✔
551
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
552

553
            teardown();
20✔
554
         }
16✔
555
      }
16✔
556
};
557

558
class Test_Eager_Close_Sync : public Synchronous_Test {
4✔
559
   public:
560
      Test_Eager_Close_Sync(net::io_context& ioc,
4✔
561
                            const std::string& config_name,
562
                            const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
563
                            const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
564
            Synchronous_Test(ioc, client_policy, server_policy, "Test Eager Close Sync", config_name) {}
12✔
565

566
      void run_synchronous_client() override {
4✔
567
         error_code ec;
4✔
568

569
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
570
         result().expect_success("connect", ec);
8✔
571

572
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
573
         result().expect_success("handshake", ec);
8✔
574

575
         client()->stream().shutdown(ec);
4✔
576
         result().expect_success("shutdown", ec);
8✔
577

578
         client()->close_socket();
4✔
579
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
580

581
         teardown();
4✔
582
      }
4✔
583
};
584

585
/* In this test case the client closes the socket without properly shutting down the connection.
586
 * The server should see a StreamTruncated error.
587
 */
588
class Test_Close_Without_Shutdown : public TestBase,
589
                                    public net::coroutine,
590
                                    public std::enable_shared_from_this<Test_Close_Without_Shutdown> {
591
   public:
592
      Test_Close_Without_Shutdown(net::io_context& ioc,
4✔
593
                                  const std::string& config_name,
594
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
595
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
596
            TestBase(ioc, client_policy, server_policy, "Test Close Without Shutdown", config_name) {}
8✔
597

598
      void run(const error_code& ec) {
20✔
599
         static auto test_case = &Test_Close_Without_Shutdown::run;
20✔
600
         reenter(*this) {
20✔
601
            client()->reset_timeout("connect");
4✔
602
            yield net::async_connect(
8✔
603
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
604
            result().expect_success("connect", ec);
8✔
605

606
            client()->reset_timeout("handshake");
4✔
607
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
608
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
609
            result().expect_success("handshake", ec);
8✔
610

611
            // send the control message to configure the server to expect a short-read
612
            client()->reset_timeout("send expect_short_read message");
4✔
613
            yield net::async_write(client()->stream(),
12✔
614
                                   net::buffer(EXPECT_SHORT_READ_MESSAGE.c_str(),
4✔
615
                                               EXPECT_SHORT_READ_MESSAGE.size() + 1),  // including \0 termination
4✔
616
                                   std::bind(test_case, shared_from_this(), _1));
8✔
617
            result().expect_success("send expect_short_read message", ec);
8✔
618

619
            // read the confirmation of the control message above
620
            client()->reset_timeout("receive_response");
4✔
621
            yield net::async_read(client()->stream(),
12✔
622
                                  client()->buffer(),
4✔
623
                                  std::bind(&Client::received_zero_byte, client().get(), _1, _2),
4✔
624
                                  std::bind(test_case, shared_from_this(), _1));
8✔
625
            result().expect_success("receive_response", ec);
8✔
626

627
            client()->close_socket();
4✔
628
            result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
629

630
            teardown();
24✔
631
         }
20✔
632
      }
20✔
633
};
634

635
class Test_Close_Without_Shutdown_Sync : public Synchronous_Test {
4✔
636
   public:
637
      Test_Close_Without_Shutdown_Sync(net::io_context& ioc,
4✔
638
                                       const std::string& config_name,
639
                                       const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
640
                                       const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
641
            Synchronous_Test(ioc, client_policy, server_policy, "Test Close Without Shutdown Sync", config_name) {}
12✔
642

643
      void run_synchronous_client() override {
4✔
644
         error_code ec;
4✔
645
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
646
         result().expect_success("connect", ec);
8✔
647

648
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
649
         result().expect_success("handshake", ec);
8✔
650

651
         server()->expect_short_read();
4✔
652

653
         client()->close_socket();
4✔
654
         result().confirm("did not receive close_notify", !client()->stream().shutdown_received());
8✔
655

656
         teardown();
4✔
657
      }
4✔
658
};
659

660
/* In this test case the server shuts down the connection but the client doesn't send the corresponding close_notify
661
 * response. Instead, it closes the socket immediately.
662
 * The server should see a short-read error.
663
 */
664
class Test_No_Shutdown_Response : public TestBase,
665
                                  public net::coroutine,
666
                                  public std::enable_shared_from_this<Test_No_Shutdown_Response> {
667
   public:
668
      Test_No_Shutdown_Response(net::io_context& ioc,
4✔
669
                                const std::string& config_name,
670
                                const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
671
                                const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
672
            TestBase(ioc, client_policy, server_policy, "Test No Shutdown Response", config_name) {}
8✔
673

674
      void run(const error_code& ec) {
20✔
675
         static auto test_case = &Test_No_Shutdown_Response::run;
20✔
676
         reenter(*this) {
20✔
677
            client()->reset_timeout("connect");
4✔
678
            yield net::async_connect(
8✔
679
               client()->stream().lowest_layer(), k_endpoints, std::bind(test_case, shared_from_this(), _1));
8✔
680
            result().expect_success("connect", ec);
8✔
681

682
            client()->reset_timeout("handshake");
4✔
683
            yield client()->stream().async_handshake(Botan::TLS::Connection_Side::Client,
8✔
684
                                                     std::bind(test_case, shared_from_this(), _1));
8✔
685
            result().expect_success("handshake", ec);
8✔
686

687
            // send a control message to make the server shut down
688
            client()->reset_timeout("send shutdown message");
4✔
689
            yield net::async_write(
8✔
690
               client()->stream(),
4✔
691
               net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
4✔
692
                           PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
693
               std::bind(test_case, shared_from_this(), _1));
8✔
694
            result().expect_success("send shutdown message", ec);
8✔
695

696
            // read the server's close-notify message
697
            client()->reset_timeout("read close_notify");
4✔
698
            yield net::async_read(client()->stream(), client()->buffer(), std::bind(test_case, shared_from_this(), _1));
4✔
699
            result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
700
            result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
701

702
            // close the socket rather than shutting down
703
            client()->close_socket();
4✔
704

705
            teardown();
24✔
706
         }
20✔
707
      }
20✔
708
};
709

710
class Test_No_Shutdown_Response_Sync : public Synchronous_Test {
4✔
711
   public:
712
      Test_No_Shutdown_Response_Sync(net::io_context& ioc,
4✔
713
                                     const std::string& config_name,
714
                                     const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
715
                                     const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
716
            Synchronous_Test(ioc, client_policy, server_policy, "Test No Shutdown Response Sync", config_name) {}
12✔
717

718
      void run_synchronous_client() override {
4✔
719
         error_code ec;
4✔
720
         net::connect(client()->stream().lowest_layer(), k_endpoints, ec);
4✔
721
         result().expect_success("connect", ec);
8✔
722

723
         client()->stream().handshake(Botan::TLS::Connection_Side::Client, ec);
4✔
724
         result().expect_success("handshake", ec);
8✔
725

726
         net::write(client()->stream(),
4✔
727
                    net::buffer(PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.c_str(),
8✔
728
                                PREPARE_SHUTDOWN_NO_RESPONSE_MESSAGE.size() + 1),  // including \0 termination
4✔
729
                    ec);
730
         result().expect_success("send expect_short_read message", ec);
8✔
731

732
         net::read(client()->stream(), client()->buffer(), ec);
4✔
733
         result().expect_ec("read gives EOF", net::error::eof, ec);
4✔
734
         result().confirm("received close_notify", client()->stream().shutdown_received());
8✔
735

736
         // close the socket rather than shutting down
737
         client()->close_socket();
4✔
738

739
         teardown();
4✔
740
      }
4✔
741
};
742

743
class Test_Conversation_With_Move : public Test_Conversation {
4✔
744
   public:
745
      Test_Conversation_With_Move(net::io_context& ioc,
4✔
746
                                  const std::string& config_name,
747
                                  const std::shared_ptr<const Botan::TLS::Policy>& client_policy,
748
                                  const std::shared_ptr<const Botan::TLS::Policy>& server_policy) :
4✔
749
            Test_Conversation(ioc, config_name, client_policy, server_policy, "Test Conversation With Move") {
8✔
750
         server()->move_before_accept();
4✔
751
      }
4✔
752
};
753

754
class SystemConfiguration {
755
   public:
756
      SystemConfiguration(std::string n, const std::string& cp, const std::string& sp) :
4✔
757
            m_name(std::move(n)),
8✔
758
            m_client_policy(std::make_shared<Botan::TLS::Text_Policy>(cp)),
4✔
759
            m_server_policy(std::make_shared<Botan::TLS::Text_Policy>(sp)) {}
4✔
760

761
      template <typename TestT>
762
      void run(std::vector<Result>& results) {
36✔
763
         net::io_context ioc;
36✔
764

765
         auto t = std::make_shared<TestT>(ioc, m_name, m_server_policy, m_client_policy);
36✔
766

767
         t->run(error_code{});
36✔
768

769
         while(true) {
770
            try {
771
               ioc.run();
36✔
772
               break;
773
            } catch(const Timeout_Exception&) { /* timeout is reported via Test::Result object */
×
774
            } catch(const boost::exception&) {
×
775
               t->fail("boost exception");
×
776
            } catch(const std::exception& ex) {
×
777
               t->fail(ex.what());
×
778
            }
779
         }
780

781
         t->finishAsynchronousWork();
36✔
782
         t->extend_results(results);
36✔
783
      }
36✔
784

785
   private:
786
      std::string m_name;
787

788
      std::shared_ptr<Botan::TLS::Text_Policy> m_client_policy;
789
      std::shared_ptr<Botan::TLS::Text_Policy> m_server_policy;
790
};
791

792
std::vector<SystemConfiguration> get_configurations() {
1✔
793
   return {
1✔
794
      SystemConfiguration("TLS 1.2 only", "allow_tls12=true\nallow_tls13=false", "allow_tls12=true\nallow_tls13=false"),
795
      #if defined(BOTAN_HAS_TLS_13)
796
         SystemConfiguration(
797
            "TLS 1.3 only", "allow_tls12=false\nallow_tls13=true", "allow_tls12=false\nallow_tls13=true"),
798
         SystemConfiguration("TLS 1.x server, TLS 1.2 client",
799
                             "allow_tls12=true\nallow_tls13=false",
800
                             "allow_tls12=true\nallow_tls13=true"),
801
         SystemConfiguration("TLS 1.2 server, TLS 1.x client",
802
                             "allow_tls12=true\nallow_tls13=true",
803
                             "allow_tls12=true\nallow_tls13=false"),
804
      #endif
805
   };
13✔
806
}
807

808
}  // namespace
809

810
namespace Botan_Tests {
811

812
class Tls_Stream_Integration_Tests final : public Test {
×
813
   public:
814
      std::vector<Test::Result> run() override {
1✔
815
         std::vector<Test::Result> results;
1✔
816

817
         auto configs = get_configurations();
1✔
818
         for(auto& config : configs) {
5✔
819
            config.run<Test_Conversation>(results);
4✔
820
            config.run<Test_Eager_Close>(results);
4✔
821
            config.run<Test_Close_Without_Shutdown>(results);
4✔
822
            config.run<Test_No_Shutdown_Response>(results);
4✔
823
            config.run<Test_Conversation_Sync>(results);
4✔
824
            config.run<Test_Eager_Close_Sync>(results);
4✔
825
            config.run<Test_Close_Without_Shutdown_Sync>(results);
4✔
826
            config.run<Test_No_Shutdown_Response_Sync>(results);
4✔
827
            config.run<Test_Conversation_With_Move>(results);
4✔
828
         }
829

830
         return results;
1✔
831
      }
1✔
832
};
833

834
BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Tests);
835

836
}  // namespace Botan_Tests
837

838
   #endif  // BOOST_VERSION
839
#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