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

randombit / botan / 13215274653

08 Feb 2025 11:38AM UTC coverage: 91.655% (-0.009%) from 91.664%
13215274653

Pull #4650

github

web-flow
Merge 107f31833 into bc555cd3c
Pull Request #4650: Reorganize code and reduce header dependencies

94836 of 103471 relevant lines covered (91.65%)

11230958.94 hits per line

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

93.88
/src/lib/tls/asio/asio_stream.h
1
/*
2
* TLS ASIO Stream
3
* (C) 2018-2021 Jack Lloyd
4
*     2018-2021 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
5
*     2023      Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
6
*
7
* Botan is released under the Simplified BSD License (see license.txt)
8
*/
9

10
#ifndef BOTAN_ASIO_STREAM_H_
11
#define BOTAN_ASIO_STREAM_H_
12

13
#include <botan/asio_compat.h>
14
#if !defined(BOTAN_FOUND_COMPATIBLE_BOOST_ASIO_VERSION)
15
   #error Available boost headers are too old for the boost asio stream.
16
#else
17

18
   #include <botan/asio_async_ops.h>
19
   #include <botan/asio_context.h>
20
   #include <botan/asio_error.h>
21

22
   #include <botan/tls_callbacks.h>
23
   #include <botan/tls_channel.h>
24
   #include <botan/tls_client.h>
25
   #include <botan/tls_magic.h>
26
   #include <botan/tls_server.h>
27

28
   // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>,
29
   // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'.
30
   #define BOOST_ASIO_DISABLE_SERIAL_PORT
31
   #include <boost/asio.hpp>
32
   #include <boost/beast/core.hpp>
33

34
   #include <memory>
35
   #include <type_traits>
36

37
namespace Botan::TLS {
38

39
template <class SL, class C>
40
class Stream;
41

42
/**
43
 * @brief Specialization of TLS::Callbacks for the ASIO Stream
44
 *
45
 * Applications may decide to derive from this for fine-grained customizations
46
 * of the TLS::Stream's behaviour. Examples may be OCSP integration, custom
47
 * certificate validation or user-defined key exchange mechanisms.
48
 *
49
 * By default, this class provides all necessary customizations for the ASIO
50
 * integration. The methods required for that are `final` and cannot be
51
 * overridden.
52
 *
53
 * Each instance of TLS::Stream must have their own instance of this class. A
54
 * future major version of Botan will therefor consume instances of this class
55
 * as a std::unique_ptr. The current usage of std::shared_ptr is erratic.
56
 */
57
class StreamCallbacks : public Callbacks {
58
   public:
59
      StreamCallbacks() {}
119✔
60

61
      void tls_emit_data(std::span<const uint8_t> data) final {
488✔
62
         m_send_buffer.commit(boost::asio::buffer_copy(m_send_buffer.prepare(data.size()),
976✔
63
                                                       boost::asio::buffer(data.data(), data.size())));
488✔
64
      }
488✔
65

66
      void tls_record_received(uint64_t, std::span<const uint8_t> data) final {
50✔
67
         m_receive_buffer.commit(boost::asio::buffer_copy(m_receive_buffer.prepare(data.size()),
100✔
68
                                                          boost::asio::const_buffer(data.data(), data.size())));
50✔
69
      }
50✔
70

71
      bool tls_peer_closed_connection() final {
40✔
72
         // Instruct the TLS implementation to reply with our close_notify to
73
         // obtain the same behaviour for TLS 1.2 and TLS 1.3. Currently, this
74
         // prevents a downstream application from closing their write-end while
75
         // allowing the peer to continue writing.
76
         //
77
         // When lifting this limitation, please take good note of the "Future
78
         // work" remarks in https://github.com/randombit/botan/pull/3801.
79
         return true;
40✔
80
      }
81

82
      /**
83
       * @param alert  a TLS alert sent by the peer
84
       */
85
      void tls_alert(TLS::Alert alert) final {
50✔
86
         if(alert.is_fatal() || alert.type() == TLS::AlertType::CloseNotify) {
50✔
87
            // TLS alerts received from the peer are not passed to the
88
            // downstream application immediately. Instead, we retain them here
89
            // and the stream invokes `handle_tls_protocol_errors()` in due time
90
            // to handle them.
91
            m_alert_from_peer = alert;
50✔
92
         }
93
      }
50✔
94

95
      void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain,
36✔
96
                                 const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
97
                                 const std::vector<Certificate_Store*>& trusted_roots,
98
                                 Usage_Type usage,
99
                                 std::string_view hostname,
100
                                 const TLS::Policy& policy) override {
101
         auto ctx = m_context.lock();
36✔
102

103
         if(ctx && ctx->has_verify_callback()) {
36✔
104
            ctx->get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
36✔
105
         } else {
106
            Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
×
107
         }
108
      }
36✔
109

110
   private:
111
      // The members below are meant for the tightly-coupled Stream class only
112
      template <class SL, class C>
113
      friend class Stream;
114

115
      void set_context(std::weak_ptr<Botan::TLS::Context> context) { m_context = std::move(context); }
238✔
116

117
      void consume_send_buffer() { m_send_buffer.consume(m_send_buffer.size()); }
118

119
      boost::beast::flat_buffer& send_buffer() { return m_send_buffer; }
1,180✔
120

121
      const boost::beast::flat_buffer& send_buffer() const { return m_send_buffer; }
122

123
      boost::beast::flat_buffer& receive_buffer() { return m_receive_buffer; }
343✔
124

125
      const boost::beast::flat_buffer& receive_buffer() const { return m_receive_buffer; }
126

127
      bool shutdown_received() const {
72✔
128
         return m_alert_from_peer && m_alert_from_peer->type() == AlertType::CloseNotify;
56✔
129
      }
130

131
      std::optional<Alert> alert_from_peer() const { return m_alert_from_peer; }
77✔
132

133
   private:
134
      std::optional<Alert> m_alert_from_peer;
135
      boost::beast::flat_buffer m_receive_buffer;
136
      boost::beast::flat_buffer m_send_buffer;
137

138
      std::weak_ptr<TLS::Context> m_context;
139
};
140

141
namespace detail {
142

143
template <typename T>
144
concept basic_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code)>;
145

146
template <typename T>
147
concept byte_size_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code, size_t)>;
148

149
}  // namespace detail
150

151
/**
152
 * @brief boost::asio compatible SSL/TLS stream
153
 *
154
 * @tparam StreamLayer type of the next layer, usually a network socket
155
 * @tparam ChannelT type of the native_handle, defaults to TLS::Channel, only needed for testing purposes
156
 */
157
template <class StreamLayer, class ChannelT = Channel>
158
class Stream {
159
   private:
160
      using default_completion_token =
161
         boost::asio::default_completion_token_t<boost::beast::executor_type<StreamLayer>>;
162

163
   public:
164
      //! \name construction
165
      //! @{
166

167
      /**
168
       * @brief Construct a new Stream with a customizable instance of Callbacks
169
       *
170
       * @param context The context parameter is used to set up the underlying native handle.
171
       * @param callbacks The callbacks parameter may contain an instance of a derived TLS::Callbacks
172
       *                  class to allow for fine-grained customization of the TLS stream. Note that
173
       *                  applications need to ensure a 1-to-1 relationship between instances of
174
       *                  Callbacks and Streams. A future major version of Botan will use a unique_ptr
175
       *                  here.
176
       *
177
       * @param args Arguments to be forwarded to the construction of the next layer.
178
       */
179
      template <typename... Args>
180
      explicit Stream(std::shared_ptr<Context> context, std::shared_ptr<StreamCallbacks> callbacks, Args&&... args) :
119✔
181
            m_context(std::move(context)),
119✔
182
            m_nextLayer(std::forward<Args>(args)...),
119✔
183
            m_core(std::move(callbacks)),
119✔
184
            m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'),
119✔
185
            m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {
163✔
186
         m_core->set_context(m_context);
119✔
187
      }
119✔
188

189
      /**
190
       * @brief Construct a new Stream
191
       *
192
       * @param context The context parameter is used to set up the underlying native handle.
193
       * @param args Arguments to be forwarded to the construction of the next layer.
194
       */
195
      template <typename... Args>
196
      explicit Stream(std::shared_ptr<Context> context, Args&&... args) :
27✔
197
            Stream(std::move(context), std::make_shared<StreamCallbacks>(), std::forward<Args>(args)...) {}
54✔
198

199
      /**
200
       * @brief Construct a new Stream
201
       *
202
       * Convenience overload for boost::asio::ssl::stream compatibility.
203
       *
204
       * @param arg This argument is forwarded to the construction of the next layer.
205
       * @param context The context parameter is used to set up the underlying native handle.
206
       * @param callbacks The (optional) callbacks object that the stream should use. Note that
207
       *                  applications need to ensure a 1-to-1 relationship between instances of Callbacks
208
       *                  and Streams. A future major version of Botan will use a unique_ptr here.
209
       */
210
      template <typename Arg>
211
      explicit Stream(Arg&& arg,
92✔
212
                      std::shared_ptr<Context> context,
213
                      std::shared_ptr<StreamCallbacks> callbacks = std::make_shared<StreamCallbacks>()) :
214
            Stream(std::move(context), std::move(callbacks), std::forward<Arg>(arg)) {}
184✔
215

216
   #if defined(BOTAN_HAS_HAS_DEFAULT_TLS_CONTEXT)
217
      /**
218
       * @brief Conveniently construct a new Stream with default settings
219
       *
220
       * This is typically a good starting point for a basic TLS client. For
221
       * much more control and configuration options, consider creating a custom
222
       * Context object and pass it to the respective constructor.
223
       *
224
       * @param server_info  Basic information about the host to connect to (SNI)
225
       * @param args  Arguments to be forwarded to the construction of the next layer.
226
       */
227
      template <typename... Args>
228
      explicit Stream(Server_Information server_info, Args&&... args) :
229
            Stream(std::make_shared<Context>(std::move(server_info)), std::forward<Args>(args)...) {}
230
   #endif
231

232
      virtual ~Stream() = default;
567✔
233

234
      Stream(Stream&& other) = default;
4✔
235
      Stream& operator=(Stream&& other) = default;
236

237
      Stream(const Stream& other) = delete;
238
      Stream& operator=(const Stream& other) = delete;
239

240
      //! @}
241
      //! \name boost::asio accessor methods
242
      //! @{
243

244
      using next_layer_type = typename std::remove_reference<StreamLayer>::type;
245

246
      const next_layer_type& next_layer() const { return m_nextLayer; }
2✔
247

248
      next_layer_type& next_layer() { return m_nextLayer; }
3✔
249

250
      using lowest_layer_type = typename boost::beast::lowest_layer_type<StreamLayer>;
251

252
      lowest_layer_type& lowest_layer() { return boost::beast::get_lowest_layer(m_nextLayer); }
253

254
      const lowest_layer_type& lowest_layer() const { return boost::beast::get_lowest_layer(m_nextLayer); }
255

256
      using executor_type = typename next_layer_type::executor_type;
257

258
      executor_type get_executor() noexcept { return m_nextLayer.get_executor(); }
758✔
259

260
      using native_handle_type = typename std::add_pointer<ChannelT>::type;
261

262
      native_handle_type native_handle() {
675✔
263
         BOTAN_STATE_CHECK(m_native_handle != nullptr);
298✔
264
         return m_native_handle.get();
668✔
265
      }
266

267
      //! @}
268
      //! \name configuration and callback setters
269
      //! @{
270

271
      /**
272
       * @brief Override the tls_verify_cert_chain callback
273
       *
274
       * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback
275
       * used in the handshake.
276
       * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback
277
       *
278
       * @note This function should only be called before initiating the TLS handshake
279
       */
280
      void set_verify_callback(Context::Verify_Callback callback) {
281
         m_context->set_verify_callback(std::move(callback));
282
      }
283

284
      /**
285
       * @brief Compatibility overload of @ref set_verify_callback
286
       *
287
       * @param callback the callback implementation
288
       * @param ec This parameter is unused.
289
       */
290
      void set_verify_callback(Context::Verify_Callback callback, boost::system::error_code& ec) {
291
         BOTAN_UNUSED(ec);
292
         m_context->set_verify_callback(std::move(callback));
293
      }
294

295
      /**
296
       * Not Implemented.
297
       * @param depth the desired verification depth
298
       * @throws Not_Implemented todo
299
       */
300
      void set_verify_depth(int depth) {
301
         BOTAN_UNUSED(depth);
302
         throw Not_Implemented("set_verify_depth is not implemented");
303
      }
304

305
      /**
306
       * Not Implemented.
307
       * @param depth the desired verification depth
308
       * @param ec Will be set to `Botan::ErrorType::NotImplemented`
309
       */
310
      void set_verify_depth(int depth, boost::system::error_code& ec) {
311
         BOTAN_UNUSED(depth);
312
         ec = ErrorType::NotImplemented;
313
      }
314

315
      /**
316
       * Not Implemented.
317
       * @param v the desired verify mode
318
       * @throws Not_Implemented todo
319
       */
320
      template <typename verify_mode>
321
      void set_verify_mode(verify_mode v) {
322
         BOTAN_UNUSED(v);
323
         throw Not_Implemented("set_verify_mode is not implemented");
324
      }
325

326
      /**
327
       * Not Implemented.
328
       * @param v the desired verify mode
329
       * @param ec Will be set to `Botan::ErrorType::NotImplemented`
330
       */
331
      template <typename verify_mode>
332
      void set_verify_mode(verify_mode v, boost::system::error_code& ec) {
333
         BOTAN_UNUSED(v);
334
         ec = ErrorType::NotImplemented;
335
      }
336

337
      //! @}
338
      //! \name handshake methods
339
      //! @{
340

341
      /**
342
       * @brief Performs SSL handshaking.
343
       *
344
       * The function call will block until handshaking is complete or an error occurs.
345
       *
346
       * @param side The type of handshaking to be performed, i.e. as a client or as a server.
347
       * @throws boost::system::system_error if error occured
348
       */
349
      void handshake(Connection_Side side) {
1✔
350
         boost::system::error_code ec;
1✔
351
         handshake(side, ec);
1✔
352
         boost::asio::detail::throw_error(ec, "handshake");
1✔
353
      }
1✔
354

355
      /**
356
       * @brief Performs SSL handshaking.
357
       *
358
       * The function call will block until handshaking is complete or an error occurs.
359
       *
360
       * @param side The type of handshaking to be performed, i.e. as a client or as a server.
361
       * @param ec Set to indicate what error occurred, if any.
362
       */
363
      void handshake(Connection_Side side, boost::system::error_code& ec) {
24✔
364
         setup_native_handle(side, ec);
24✔
365

366
         // We write to the socket if we have data to send and read from it
367
         // otherwise, until either some error occured or we have successfully
368
         // performed the handshake.
369
         while(!ec) {
77✔
370
            // Send pending data to the peer and abort the handshake if that
371
            // fails with a network error. We do that first, to allow sending
372
            // any final message before reporting the handshake as "finished".
373
            if(has_data_to_send()) {
63✔
374
               send_pending_encrypted_data(ec);
43✔
375
            }
376

377
            // Once the underlying TLS implementation reports a complete and
378
            // successful handshake we're done.
379
            if(native_handle()->is_handshake_complete()) {
63✔
380
               return;
381
            }
382

383
            // Handle and report any TLS protocol errors that might have
384
            // surfaced in a previous iteration. By postponing their handling we
385
            // allow the stream to send a respective TLS alert to the peer before
386
            // aborting the handshake.
387
            handle_tls_protocol_errors(ec);
46✔
388

389
            // If we don't have any encrypted data to send we attempt to read
390
            // more data from the peer. This reports network errors immediately.
391
            // TLS protocol errors result in an internal state change which is
392
            // handled by `handle_tls_protocol_errors()` in the next iteration.
393
            read_and_process_encrypted_data_from_peer(ec);
46✔
394
         }
395

396
         BOTAN_ASSERT_NOMSG(ec.failed());
×
397
      }
398

399
      /**
400
       * @brief Starts an asynchronous SSL handshake.
401
       *
402
       * This function call always returns immediately.
403
       *
404
       * @param side The type of handshaking to be performed, i.e. as a client or as a server.
405
       * @param completion_token The completion handler to be called when the handshake operation completes.
406
       *                         The completion signature of the handler must be: void(boost::system::error_code).
407
       */
408
      template <detail::basic_completion_token CompletionToken = default_completion_token>
409
      auto async_handshake(Botan::TLS::Connection_Side side,
72✔
410
                           CompletionToken&& completion_token = default_completion_token{}) {
411
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
72✔
412
            [this](auto&& completion_handler, TLS::Connection_Side connection_side) {
224✔
413
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
414

415
               boost::system::error_code ec;
76✔
416
               setup_native_handle(connection_side, ec);
76✔
417

418
               detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
76✔
419
                  std::forward<completion_handler_t>(completion_handler), *this, ec};
420
            },
76✔
421
            completion_token,
422
            side);
423
      }
424

425
      /**
426
       * Not Implemented.
427
       * @throws Not_Implemented todo
428
       */
429
      template <typename ConstBufferSequence, detail::basic_completion_token BufferedHandshakeHandler>
430
      auto async_handshake(Connection_Side side,
431
                           const ConstBufferSequence& buffers,
432
                           BufferedHandshakeHandler&& handler) {
433
         BOTAN_UNUSED(side, buffers, handler);
434
         throw Not_Implemented("buffered async handshake is not implemented");
435
      }
436

437
      //! @}
438
      //! \name shutdown methods
439
      //! @{
440

441
      /**
442
       * @brief Shut down SSL on the stream.
443
       *
444
       * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
445
       * or an error occurs. Note that this will not close the lowest layer.
446
       *
447
       * Note that this can be used in reaction of a received shutdown alert from the peer.
448
       *
449
       * @param ec Set to indicate what error occured, if any.
450
       */
451
      void shutdown(boost::system::error_code& ec) {
16✔
452
         try_with_error_code([&] { native_handle()->close(); }, ec);
32✔
453

454
         send_pending_encrypted_data(ec);
16✔
455
      }
16✔
456

457
      /**
458
       * @brief Shut down SSL on the stream.
459
       *
460
       * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
461
       * or an error occurs. Note that this will not close the lowest layer.
462
       *
463
       * Note that this can be used in reaction of a received shutdown alert from the peer.
464
       *
465
       * @throws boost::system::system_error if error occured
466
       */
467
      void shutdown() {
468
         boost::system::error_code ec;
469
         shutdown(ec);
470
         boost::asio::detail::throw_error(ec, "shutdown");
471
      }
472

473
   private:
474
      /**
475
       * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
476
       *        signature of `AsyncWriteOperation`.
477
       *
478
       * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
479
       * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
480
       */
481
      template <typename Handler, typename Executor>
482
      struct Wrapper {
8✔
483
            void operator()(boost::system::error_code ec, std::size_t) { handler(ec); }
36✔
484

485
            using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
486

487
            executor_type get_executor() const noexcept {
40✔
488
               return boost::asio::get_associated_executor(handler, io_executor);
80✔
489
            }
490

491
            using allocator_type = boost::asio::associated_allocator_t<Handler>;
492

493
            allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); }
72✔
494

495
            Handler handler;
496
            Executor io_executor;
497
      };
498

499
   public:
500
      /**
501
       * @brief Asynchronously shut down SSL on the stream.
502
       *
503
       * This function call always returns immediately.
504
       *
505
       * Note that this can be used in reaction of a received shutdown alert from the peer.
506
       *
507
       * @param completion_token The completion handler to be called when the shutdown operation completes.
508
       *                         The completion signature of the handler must be: void(boost::system::error_code).
509
       */
510
      template <detail::basic_completion_token CompletionToken = default_completion_token>
511
      auto async_shutdown(CompletionToken&& completion_token = default_completion_token{}) {
32✔
512
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
32✔
513
            [this](auto&& completion_handler) {
68✔
514
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
515

516
               boost::system::error_code ec;
36✔
517
               try_with_error_code([&] { native_handle()->close(); }, ec);
72✔
518

519
               using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
520

521
               TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
36✔
522
                  write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
523
                  *this,
524
                  boost::asio::buffer_size(send_buffer()),
36✔
525
                  ec};
526
            },
36✔
527
            completion_token);
528
      }
529

530
      //! @}
531
      //! \name I/O methods
532
      //! @{
533

534
      /**
535
       * @brief Read some data from the stream.
536
       *
537
       * The function call will block until one or more bytes of data has been read successfully, or until an error
538
       * occurs.
539
       *
540
       * @param buffers The buffers into which the data will be read.
541
       * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
542
       *           has closed the connection but did not properly shut down the SSL connection.
543
       * @return The number of bytes read. Returns 0 if an error occurred.
544
       */
545
      template <typename MutableBufferSequence>
546
      std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) {
16✔
547
         // We read from the socket until either some error occured or we have
548
         // decrypted at least one byte of application data.
549
         while(!ec) {
51✔
550
            // Some previous invocation of process_encrypted_data() generated
551
            // application data in the output buffer that can now be returned.
552
            if(has_received_data()) {
31✔
553
               return copy_received_data(buffers);
6✔
554
            }
555

556
            // Handle and report any TLS protocol errors (including a
557
            // close_notify) that might have surfaced in a previous iteration
558
            // (in `read_and_process_encrypted_data_from_peer()`). This makes
559
            // sure that all received application data was handed out to the
560
            // caller before reporting an error (e.g. EOF at the end of the
561
            // stream).
562
            handle_tls_protocol_errors(ec);
25✔
563

564
            // If we don't have any plaintext application data, yet, we attempt
565
            // to read more data from the peer. This reports network errors
566
            // immediately. TLS protocol errors result in an internal state
567
            // change which is handled by `handle_tls_protocol_errors()` in the
568
            // next iteration.
569
            read_and_process_encrypted_data_from_peer(ec);
25✔
570
         }
571

572
         BOTAN_ASSERT_NOMSG(ec.failed());
×
573
         return 0;
574
      }
575

576
      /**
577
       * @brief Read some data from the stream.
578
       *
579
       * The function call will block until one or more bytes of data has been read successfully, or until an error
580
       * occurs.
581
       *
582
       * @param buffers The buffers into which the data will be read.
583
       * @return The number of bytes read. Returns 0 if an error occurred.
584
       * @throws boost::system::system_error if error occured
585
       */
586
      template <typename MutableBufferSequence>
587
      std::size_t read_some(const MutableBufferSequence& buffers) {
588
         boost::system::error_code ec;
589
         const auto n = read_some(buffers, ec);
590
         boost::asio::detail::throw_error(ec, "read_some");
591
         return n;
592
      }
593

594
      /**
595
       * @brief Write some data to the stream.
596
       *
597
       * The function call will block until one or more bytes of data has been written successfully, or until an error
598
       * occurs.
599
       *
600
       * @param buffers The data to be written.
601
       * @param ec Set to indicate what error occurred, if any.
602
       * @return The number of bytes processed from the input buffers.
603
       */
604
      template <typename ConstBufferSequence>
605
      std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
14✔
606
         tls_encrypt(buffers, ec);
14✔
607
         send_pending_encrypted_data(ec);
14✔
608
         return !ec ? boost::asio::buffer_size(buffers) : 0;
11✔
609
      }
610

611
      /**
612
       * @brief Write some data to the stream.
613
       *
614
       * The function call will block until one or more bytes of data has been written successfully, or until an error
615
       * occurs.
616
       *
617
       * @param buffers The data to be written.
618
       * @return The number of bytes written.
619
       * @throws boost::system::system_error if error occured
620
       */
621
      template <typename ConstBufferSequence>
622
      std::size_t write_some(const ConstBufferSequence& buffers) {
623
         boost::system::error_code ec;
624
         const auto n = write_some(buffers, ec);
625
         boost::asio::detail::throw_error(ec, "write_some");
626
         return n;
627
      }
628

629
      /**
630
       * @brief Start an asynchronous write. The function call always returns immediately.
631
       *
632
       * @param buffers The data to be written.
633
       * @param completion_token The completion handler to be called when the write operation completes. Copies of the
634
       *                         handler will be made as required. The completion signature of the handler must be:
635
       *                         void(boost::system::error_code, std::size_t).
636
       */
637
      template <typename ConstBufferSequence,
638
                detail::byte_size_completion_token CompletionToken = default_completion_token>
639
      auto async_write_some(const ConstBufferSequence& buffers,
41✔
640
                            CompletionToken&& completion_token = default_completion_token{}) {
641
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
41✔
642
            [this](auto&& completion_handler, const auto& bufs) {
123✔
643
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
644

645
               boost::system::error_code ec;
41✔
646
               tls_encrypt(bufs, ec);
41✔
647

648
               if(ec) {
×
649
                  // we cannot be sure how many bytes were committed here so clear the send_buffer and let the
650
                  // AsyncWriteOperation call the handler with the error_code set
651
                  m_core->send_buffer().consume(m_core->send_buffer().size());
1✔
652
               }
653

654
               detail::AsyncWriteOperation<completion_handler_t, Stream> op{
43✔
655
                  std::forward<completion_handler_t>(completion_handler),
656
                  *this,
657
                  ec ? 0 : boost::asio::buffer_size(bufs),
43✔
658
                  ec};
659
            },
41✔
660
            completion_token,
661
            buffers);
662
      }
663

664
      /**
665
       * @brief Start an asynchronous read. The function call always returns immediately.
666
       *
667
       * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as
668
       *                necessary, ownership of the underlying buffers is retained by the caller, which must guarantee
669
       *                that they remain valid until the handler is called.
670
       * @param completion_token The completion handler to be called when the read operation completes. The completion
671
       *                         signature of the handler must be: void(boost::system::error_code, std::size_t).
672
       */
673
      template <typename MutableBufferSequence,
674
                detail::byte_size_completion_token CompletionToken = default_completion_token>
675
      auto async_read_some(const MutableBufferSequence& buffers,
93✔
676
                           CompletionToken&& completion_token = default_completion_token{}) {
677
         return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
93✔
678
            [this](auto&& completion_handler, const auto& bufs) {
271✔
679
               using completion_handler_t = std::decay_t<decltype(completion_handler)>;
680

681
               detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
93✔
682
                  std::forward<completion_handler_t>(completion_handler), *this, bufs};
683
            },
93✔
684
            completion_token,
685
            buffers);
686
      }
687

688
      //! @}
689

690
      //! @brief Indicates whether a close_notify alert has been received from the peer.
691
      //!
692
      //! Note that we cannot m_core.is_closed_for_reading() because this wants to
693
      //! explicitly check that the peer sent close_notify.
694
      bool shutdown_received() const { return m_core->shutdown_received(); }
1,093✔
695

696
   protected:
697
      template <class H, class S, class M, class A>
698
      friend class detail::AsyncReadOperation;
699
      template <class H, class S, class A>
700
      friend class detail::AsyncWriteOperation;
701
      template <class H, class S, class A>
702
      friend class detail::AsyncHandshakeOperation;
703

704
      const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
451✔
705

706
      boost::asio::const_buffer send_buffer() const { return m_core->send_buffer().data(); }
253✔
707

708
      //! @brief Check if decrypted data is available in the receive buffer
709
      bool has_received_data() const { return m_core->receive_buffer().size() > 0; }
240✔
710

711
      //! @brief Copy decrypted data into the user-provided buffer
712
      template <typename MutableBufferSequence>
713
      std::size_t copy_received_data(MutableBufferSequence buffers) {
48✔
714
         // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the CallbacksT with
715
         // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record
716
         // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller
717
         // than the decrypted record, so this optimization might not be worth the additional complexity.
718
         const auto copiedBytes = boost::asio::buffer_copy(buffers, m_core->receive_buffer().data());
48✔
719
         m_core->receive_buffer().consume(copiedBytes);
48✔
720
         return copiedBytes;
48✔
721
      }
722

723
      //! @brief Check if encrypted data is available in the send buffer
724
      bool has_data_to_send() const { return m_core->send_buffer().size() > 0; }
857✔
725

726
      //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer
727
      void consume_send_buffer(std::size_t bytesConsumed) { m_core->send_buffer().consume(bytesConsumed); }
651✔
728

729
      /**
730
       * @brief Create the native handle.
731
       *
732
       * Depending on the desired connection side, this function will create a TLS::Client or a
733
       * TLS::Server.
734
       *
735
       * @param side The desired connection side (client or server)
736
       * @param ec Set to indicate what error occurred, if any.
737
       */
738
      void setup_native_handle(Connection_Side side, boost::system::error_code& ec) {
96✔
739
         // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has
740
         // been specified. This allows mocking the native_handle in test code.
741
         if constexpr(std::is_same<ChannelT, Channel>::value) {
742
            BOTAN_STATE_CHECK(m_native_handle == nullptr);
92✔
743

744
            try_with_error_code(
92✔
745
               [&] {
92✔
746
                  if(side == Connection_Side::Client) {
92✔
747
                     m_native_handle = std::unique_ptr<Client>(
44✔
748
                        new Client(m_core,
176✔
749
                                   m_context->m_session_manager,
44✔
750
                                   m_context->m_credentials_manager,
44✔
751
                                   m_context->m_policy,
44✔
752
                                   m_context->m_rng,
44✔
753
                                   m_context->m_server_info,
44✔
754
                                   m_context->m_policy->latest_supported_version(false /* no DTLS */)));
44✔
755
                  } else {
756
                     m_native_handle = std::unique_ptr<Server>(new Server(m_core,
144✔
757
                                                                          m_context->m_session_manager,
48✔
758
                                                                          m_context->m_credentials_manager,
48✔
759
                                                                          m_context->m_policy,
48✔
760
                                                                          m_context->m_rng,
48✔
761
                                                                          false /* no DTLS */));
762
                  }
763
               },
764
               ec);
765
         }
766
      }
96✔
767

768
      /**
769
       * The `Stream` has to distinguish from network-related issues (that are
770
       * reported immediately) from TLS protocol errors, that must be retained
771
       * and emitted once all legal application traffic received before is
772
       * pushed to the downstream application.
773
       *
774
       * See also `process_encrypted_data()` and `StreamCallbacks::tls_alert()`
775
       * where those TLS protocol errors are detected and retained for eventual
776
       * handling in this method.
777
       *
778
       * See also https://github.com/randombit/botan/pull/3801 for a detailed
779
       * description of the ASIO stream's state management.
780
       *
781
       * @param ec  this error code is set if we previously detected a TLS
782
       *            protocol error.
783
       */
784
      void handle_tls_protocol_errors(boost::system::error_code& ec) {
353✔
785
         if(ec) {
×
786
            return;
787
         }
788

789
         // If we had raised an error while processing TLS records received from
790
         // the peer, we expose that error here.
791
         //
792
         // See also `process_encrypted_data()`.
793
         else if(auto error = error_from_us()) {
×
794
            ec = error;
12✔
795
         }
796

797
         // If we had received a TLS alert from the peer, we expose that error
798
         // here. See also `StreamCallbacks::tls_alert()` where such alerts
799
         // would be detected and retained initially.
800
         //
801
         // Note that a close_notify is always a legal way for the peer to end a
802
         // TLS session. When received during the handshake it typically means
803
         // that the peer wanted to cancel the handshake for some reason not
804
         // related to the TLS protocol.
805
         else if(auto alert = alert_from_peer()) {
340✔
806
            if(alert->type() == AlertType::CloseNotify) {
50✔
807
               ec = boost::asio::error::eof;
42✔
808
            } else {
809
               ec = alert->type();
8✔
810
            }
811
         }
812
      }
813

814
      /**
815
       * Reads TLS record data from the peer and forwards it to the native
816
       * handle for processing. Note that @p ec will reflect network errors
817
       * only. Any detected or received TLS protocol errors will be retained and
818
       * must be handled by the downstream operation in due time by invoking
819
       * `handle_tls_protocol_errors()`.
820
       *
821
       * @param ec  this error code might be populated with network-related errors
822
       */
823
      void read_and_process_encrypted_data_from_peer(boost::system::error_code& ec) {
71✔
824
         if(ec) {
55✔
825
            return;
16✔
826
         }
827

828
         // If we have received application data in a previous invocation, this
829
         // data needs to be passed to the application first. Otherwise, it
830
         // might get overwritten.
831
         BOTAN_ASSERT(!has_received_data(), "receive buffer is empty");
55✔
832
         BOTAN_ASSERT(!error_from_us() && !alert_from_peer(), "TLS session is healthy");
×
833

834
         // If there's no existing error condition, read and process data from
835
         // the peer and report any sort of network error. TLS related errors do
836
         // not immediately cause an abort, they are checked in the invocation
837
         // via `error_from_us()`.
838
         boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
55✔
839
         if(!ec) {
1✔
840
            process_encrypted_data(read_buffer);
54✔
841
         } else if(ec == boost::asio::error::eof) {
1✔
842
            ec = StreamError::StreamTruncated;
×
843
         }
844
      }
845

846
      /** @brief Synchronously write encrypted data from the send buffer to the next layer.
847
       *
848
       * If this function is called with an error code other than 'Success', it will do nothing and return 0.
849
       *
850
       * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
851
       *           has closed the connection but did not properly shut down the SSL connection.
852
       * @return The number of bytes written.
853
       */
854
      size_t send_pending_encrypted_data(boost::system::error_code& ec) {
73✔
855
         if(ec) {
71✔
856
            return 0;
857
         }
858

859
         auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
71✔
860
         consume_send_buffer(writtenBytes);
71✔
861

862
         if(ec == boost::asio::error::eof && !shutdown_received()) {
71✔
863
            // transport layer was closed by peer without receiving 'close_notify'
864
            ec = StreamError::StreamTruncated;
×
865
         }
866

867
         return writtenBytes;
868
      }
869

870
      /**
871
       * @brief Pass plaintext data to the native handle for processing.
872
       *
873
       * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback.
874
       */
875
      template <typename ConstBufferSequence>
876
      void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
55✔
877
         // TODO: Once the TLS::Channel can deal with buffer sequences we can
878
         //       save this copy. Until then we optimize for the smallest amount
879
         //       of TLS records and flatten the boost buffer sequence.
880
         std::vector<uint8_t> copy_buffer;
55✔
881
         auto unpack = [&copy_buffer](const auto& bufs) -> std::span<const uint8_t> {
103✔
882
            const auto buffers_in_sequence =
883
               std::distance(boost::asio::buffer_sequence_begin(bufs), boost::asio::buffer_sequence_end(bufs));
52✔
884

885
            if(buffers_in_sequence == 0) {
8✔
886
               return {};
×
887
            } else if(buffers_in_sequence == 1) {
8✔
888
               const boost::asio::const_buffer buf = *boost::asio::buffer_sequence_begin(bufs);
44✔
889
               return {static_cast<const uint8_t*>(buf.data()), buf.size()};
44✔
890
            } else {
891
               copy_buffer.resize(boost::asio::buffer_size(bufs));
12✔
892
               boost::asio::buffer_copy(boost::asio::buffer(copy_buffer), bufs);
16✔
893
               return copy_buffer;
8✔
894
            }
895
         };
896

897
         // NOTE: This is not asynchronous: it encrypts the data synchronously.
898
         // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core,
899
         // but is not actually written to the wire, yet.
900
         try_with_error_code([&] { native_handle()->send(unpack(buffers)); }, ec);
110✔
901
      }
55✔
902

903
      /**
904
       * Pass encrypted data received from the peer to the native handle for
905
       * processing. If the @p read_buffer contains coalesced TLS records, this
906
       * might result in multiple TLS protocol state changes.
907
       *
908
       * To allow the ASIO stream wrapper to disentangle those state changes
909
       * properly, any TLS protocol errors are retained and must be handled by
910
       * calling `handle_tls_protocol_errors()` in due time.
911
       *
912
       * @param read_buffer Input buffer containing the encrypted data.
913
       */
914
      void process_encrypted_data(const boost::asio::const_buffer& read_buffer) {
271✔
915
         BOTAN_ASSERT(!alert_from_peer() && !error_from_us(),
542✔
916
                      "no one sent an alert before (no data allowed after that)");
917

918
         // If the local TLS implementation generates an alert, we are notified
919
         // with an exception that is caught in try_with_error_code(). The error
920
         // code is retained and not handled directly. Stream operations will
921
         // have to invoke `handle_tls_protocol_errors()` to handle them later.
922
         try_with_error_code(
271✔
923
            [&] {
530✔
924
               native_handle()->received_data({static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()});
271✔
925
            },
926
            m_ec_from_last_read);
271✔
927
      }
271✔
928

929
      //! @brief Catch exceptions and set an error_code
930
      template <typename Fun>
931
      void try_with_error_code(Fun f, boost::system::error_code& ec) {
470✔
932
         try {
933
            f();
470✔
934
         } catch(const TLS_Exception& e) {
15✔
935
            ec = e.type();
14✔
936
         } catch(const Exception& e) {
1✔
937
            ec = e.error_type();
1✔
938
         } catch(const std::exception&) {
×
939
            ec = ErrorType::Unknown;
×
940
         }
941
      }
470✔
942

943
   private:
944
      /**
945
       * Returns any alert previously received from the peer. This may include
946
       * close_notify. Once the peer has sent any alert, no more data must be
947
       * read from the stream.
948
       */
949
      std::optional<Alert> alert_from_peer() const { return m_core->alert_from_peer(); }
666✔
950

951
      /**
952
       * Returns any error code produced by the local TLS implementation. This
953
       * will _not_ include close_notify. Once our TLS stack has reported an
954
       * error, no more data must be written to the stream. The peer will receive
955
       * the error as a TLS alert.
956
       */
957
      boost::system::error_code error_from_us() const { return m_ec_from_last_read; }
678✔
958

959
   protected:
960
      std::shared_ptr<Context> m_context;
961
      StreamLayer m_nextLayer;
962

963
      std::shared_ptr<StreamCallbacks> m_core;
964
      std::unique_ptr<ChannelT> m_native_handle;
965
      boost::system::error_code m_ec_from_last_read;
966

967
      // Buffer space used to read input intended for the core
968
      std::vector<uint8_t> m_input_buffer_space;
969
      const boost::asio::mutable_buffer m_input_buffer;
970
};
971

972
// deduction guides for convenient construction from an existing
973
// underlying transport stream T
974
template <typename T>
975
Stream(Server_Information, T) -> Stream<T>;
976
template <typename T>
977
Stream(std::shared_ptr<Context>, std::shared_ptr<StreamCallbacks>, T) -> Stream<T>;
978
template <typename T>
979
Stream(std::shared_ptr<Context>, T) -> Stream<T>;
980
template <typename T>
981
Stream(T, std::shared_ptr<Context>) -> Stream<T>;
982

983
}  // namespace Botan::TLS
984

985
#endif
986
#endif  // BOTAN_ASIO_STREAM_H_
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