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

randombit / botan / 12303912292

12 Dec 2024 08:04PM UTC coverage: 91.266% (+0.005%) from 91.261%
12303912292

Pull #4475

github

web-flow
Merge 5e37000c4 into 281c77941
Pull Request #4475: Add a compile-time `hex_decode_array()`

93435 of 102377 relevant lines covered (91.27%)

11688969.01 hits per line

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

93.37
/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 <algorithm>
35
   #include <memory>
36
   #include <type_traits>
37

38
namespace Botan::TLS {
39

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

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

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

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

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

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

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

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

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

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

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

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

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

124
      boost::beast::flat_buffer& receive_buffer() { return m_receive_buffer; }
341✔
125

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

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

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

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

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

142
namespace detail {
143

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

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

150
}  // namespace detail
151

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

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

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

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

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

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

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

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

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

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

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

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

249
      next_layer_type& next_layer() { return m_nextLayer; }
×
250

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

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

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

257
      using executor_type = typename next_layer_type::executor_type;
258

259
      executor_type get_executor() noexcept { return m_nextLayer.get_executor(); }
728✔
260

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

263
      native_handle_type native_handle() {
641✔
264
         BOTAN_STATE_CHECK(m_native_handle != nullptr);
280✔
265
         return m_native_handle.get();
634✔
266
      }
267

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

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

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

296
      //! @throws Not_Implemented
297
      void set_verify_depth(int depth) {
298
         BOTAN_UNUSED(depth);
299
         throw Not_Implemented("set_verify_depth is not implemented");
300
      }
301

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

312
      //! @throws Not_Implemented
313
      template <typename verify_mode>
314
      void set_verify_mode(verify_mode v) {
315
         BOTAN_UNUSED(v);
316
         throw Not_Implemented("set_verify_mode is not implemented");
317
      }
318

319
      /**
320
       * Not Implemented.
321
       * @param v the desired verify mode
322
       * @param ec Will be set to `Botan::ErrorType::NotImplemented`
323
       */
324
      template <typename verify_mode>
325
      void set_verify_mode(verify_mode v, boost::system::error_code& ec) {
326
         BOTAN_UNUSED(v);
327
         ec = ErrorType::NotImplemented;
328
      }
329

330
      //! @}
331
      //! \name handshake methods
332
      //! @{
333

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

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

359
         // We write to the socket if we have data to send and read from it
360
         // otherwise, until either some error occured or we have successfully
361
         // performed the handshake.
362
         while(!ec) {
73✔
363
            // Send pending data to the peer and abort the handshake if that
364
            // fails with a network error. We do that first, to allow sending
365
            // any final message before reporting the handshake as "finished".
366
            if(has_data_to_send()) {
59✔
367
               send_pending_encrypted_data(ec);
39✔
368
            }
369

370
            // Once the underlying TLS implementation reports a complete and
371
            // successful handshake we're done.
372
            if(native_handle()->is_handshake_complete()) {
59✔
373
               return;
374
            }
375

376
            // Handle and report any TLS protocol errors that might have
377
            // surfaced in a previous iteration. By postponing their handling we
378
            // allow the stream to send a respective TLS alert to the peer before
379
            // aborting the handshake.
380
            handle_tls_protocol_errors(ec);
42✔
381

382
            // If we don't have any encrypted data to send we attempt to read
383
            // more data from the peer. This reports network errors immediately.
384
            // TLS protocol errors result in an internal state change which is
385
            // handled by `handle_tls_protocol_errors()` in the next iteration.
386
            read_and_process_encrypted_data_from_peer(ec);
42✔
387
         }
388

389
         BOTAN_ASSERT_NOMSG(ec.failed());
×
390
      }
391

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

408
               boost::system::error_code ec;
76✔
409
               setup_native_handle(connection_side, ec);
76✔
410

411
               detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
76✔
412
                  std::forward<completion_handler_t>(completion_handler), *this, ec};
413
            },
76✔
414
            completion_token,
415
            side);
416
      }
417

418
      //! @throws Not_Implemented
419
      template <typename ConstBufferSequence, detail::basic_completion_token BufferedHandshakeHandler>
420
      auto async_handshake(Connection_Side side,
421
                           const ConstBufferSequence& buffers,
422
                           BufferedHandshakeHandler&& handler) {
423
         BOTAN_UNUSED(side, buffers, handler);
424
         throw Not_Implemented("buffered async handshake is not implemented");
425
      }
426

427
      //! @}
428
      //! \name shutdown methods
429
      //! @{
430

431
      /**
432
       * @brief Shut down SSL on the stream.
433
       *
434
       * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
435
       * or an error occurs. Note that this will not close the lowest layer.
436
       *
437
       * Note that this can be used in reaction of a received shutdown alert from the peer.
438
       *
439
       * @param ec Set to indicate what error occured, if any.
440
       */
441
      void shutdown(boost::system::error_code& ec) {
16✔
442
         try_with_error_code([&] { native_handle()->close(); }, ec);
32✔
443

444
         send_pending_encrypted_data(ec);
16✔
445
      }
16✔
446

447
      /**
448
       * @brief Shut down SSL on the stream.
449
       *
450
       * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
451
       * or an error occurs. Note that this will not close the lowest layer.
452
       *
453
       * Note that this can be used in reaction of a received shutdown alert from the peer.
454
       *
455
       * @throws boost::system::system_error if error occured
456
       */
457
      void shutdown() {
458
         boost::system::error_code ec;
459
         shutdown(ec);
460
         boost::asio::detail::throw_error(ec, "shutdown");
461
      }
462

463
   private:
464
      /**
465
       * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
466
       *        signature of `AsyncWriteOperation`.
467
       *
468
       * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
469
       * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
470
       */
471
      template <typename Handler, typename Executor>
472
      struct Wrapper {
8✔
473
            void operator()(boost::system::error_code ec, std::size_t) { handler(ec); }
36✔
474

475
            using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
476

477
            executor_type get_executor() const noexcept {
40✔
478
               return boost::asio::get_associated_executor(handler, io_executor);
80✔
479
            }
480

481
            using allocator_type = boost::asio::associated_allocator_t<Handler>;
482

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

485
            Handler handler;
486
            Executor io_executor;
487
      };
488

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

506
               boost::system::error_code ec;
36✔
507
               try_with_error_code([&] { native_handle()->close(); }, ec);
72✔
508

509
               using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
510

511
               TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
36✔
512
                  write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
513
                  *this,
514
                  boost::asio::buffer_size(send_buffer()),
36✔
515
                  ec};
516
            },
36✔
517
            completion_token);
518
      }
519

520
      //! @}
521
      //! \name I/O methods
522
      //! @{
523

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

546
            // Handle and report any TLS protocol errors (including a
547
            // close_notify) that might have surfaced in a previous iteration
548
            // (in `read_and_process_encrypted_data_from_peer()`). This makes
549
            // sure that all received application data was handed out to the
550
            // caller before reporting an error (e.g. EOF at the end of the
551
            // stream).
552
            handle_tls_protocol_errors(ec);
25✔
553

554
            // If we don't have any plaintext application data, yet, we attempt
555
            // to read more data from the peer. This reports network errors
556
            // immediately. TLS protocol errors result in an internal state
557
            // change which is handled by `handle_tls_protocol_errors()` in the
558
            // next iteration.
559
            read_and_process_encrypted_data_from_peer(ec);
25✔
560
         }
561

562
         BOTAN_ASSERT_NOMSG(ec.failed());
×
563
         return 0;
564
      }
565

566
      /**
567
       * @brief Read some data from the stream.
568
       *
569
       * The function call will block until one or more bytes of data has been read successfully, or until an error
570
       * occurs.
571
       *
572
       * @param buffers The buffers into which the data will be read.
573
       * @return The number of bytes read. Returns 0 if an error occurred.
574
       * @throws boost::system::system_error if error occured
575
       */
576
      template <typename MutableBufferSequence>
577
      std::size_t read_some(const MutableBufferSequence& buffers) {
578
         boost::system::error_code ec;
579
         const auto n = read_some(buffers, ec);
580
         boost::asio::detail::throw_error(ec, "read_some");
581
         return n;
582
      }
583

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

601
      /**
602
       * @brief Write some data to the stream.
603
       *
604
       * The function call will block until one or more bytes of data has been written successfully, or until an error
605
       * occurs.
606
       *
607
       * @param buffers The data to be written.
608
       * @return The number of bytes written.
609
       * @throws boost::system::system_error if error occured
610
       */
611
      template <typename ConstBufferSequence>
612
      std::size_t write_some(const ConstBufferSequence& buffers) {
613
         boost::system::error_code ec;
614
         const auto n = write_some(buffers, ec);
615
         boost::asio::detail::throw_error(ec, "write_some");
616
         return n;
617
      }
618

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

635
               boost::system::error_code ec;
41✔
636
               tls_encrypt(bufs, ec);
41✔
637

638
               if(ec) {
×
639
                  // we cannot be sure how many bytes were committed here so clear the send_buffer and let the
640
                  // AsyncWriteOperation call the handler with the error_code set
641
                  m_core->send_buffer().consume(m_core->send_buffer().size());
1✔
642
               }
643

644
               detail::AsyncWriteOperation<completion_handler_t, Stream> op{
43✔
645
                  std::forward<completion_handler_t>(completion_handler),
646
                  *this,
647
                  ec ? 0 : boost::asio::buffer_size(bufs),
43✔
648
                  ec};
649
            },
41✔
650
            completion_token,
651
            buffers);
652
      }
653

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

671
               detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
93✔
672
                  std::forward<completion_handler_t>(completion_handler), *this, bufs};
673
            },
93✔
674
            completion_token,
675
            buffers);
676
      }
677

678
      //! @}
679

680
      //! @brief Indicates whether a close_notify alert has been received from the peer.
681
      //!
682
      //! Note that we cannot m_core.is_closed_for_reading() because this wants to
683
      //! explicitly check that the peer sent close_notify.
684
      bool shutdown_received() const { return m_core->shutdown_received(); }
1,037✔
685

686
   protected:
687
      template <class H, class S, class M, class A>
688
      friend class detail::AsyncReadOperation;
689
      template <class H, class S, class A>
690
      friend class detail::AsyncWriteOperation;
691
      template <class H, class S, class A>
692
      friend class detail::AsyncHandshakeOperation;
693

694
      const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
427✔
695

696
      boost::asio::const_buffer send_buffer() const { return m_core->send_buffer().data(); }
239✔
697

698
      //! @brief Check if decrypted data is available in the receive buffer
699
      bool has_received_data() const { return m_core->receive_buffer().size() > 0; }
242✔
700

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

713
      //! @brief Check if encrypted data is available in the send buffer
714
      bool has_data_to_send() const { return m_core->send_buffer().size() > 0; }
795✔
715

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

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

734
            try_with_error_code(
92✔
735
               [&] {
92✔
736
                  if(side == Connection_Side::Client) {
92✔
737
                     m_native_handle = std::unique_ptr<Client>(
44✔
738
                        new Client(m_core,
176✔
739
                                   m_context->m_session_manager,
44✔
740
                                   m_context->m_credentials_manager,
44✔
741
                                   m_context->m_policy,
44✔
742
                                   m_context->m_rng,
44✔
743
                                   m_context->m_server_info,
44✔
744
                                   m_context->m_policy->latest_supported_version(false /* no DTLS */)));
44✔
745
                  } else {
746
                     m_native_handle = std::unique_ptr<Server>(new Server(m_core,
144✔
747
                                                                          m_context->m_session_manager,
48✔
748
                                                                          m_context->m_credentials_manager,
48✔
749
                                                                          m_context->m_policy,
48✔
750
                                                                          m_context->m_rng,
48✔
751
                                                                          false /* no DTLS */));
752
                  }
753
               },
754
               ec);
755
         }
756
      }
96✔
757

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

779
         // If we had raised an error while processing TLS records received from
780
         // the peer, we expose that error here.
781
         //
782
         // See also `process_encrypted_data()`.
783
         else if(auto error = error_from_us()) {
×
784
            ec = error;
12✔
785
         }
786

787
         // If we had received a TLS alert from the peer, we expose that error
788
         // here. See also `StreamCallbacks::tls_alert()` where such alerts
789
         // would be detected and retained initially.
790
         //
791
         // Note that a close_notify is always a legal way for the peer to end a
792
         // TLS session. When received during the handshake it typically means
793
         // that the peer wanted to cancel the handshake for some reason not
794
         // related to the TLS protocol.
795
         else if(auto alert = alert_from_peer()) {
324✔
796
            if(alert->type() == AlertType::CloseNotify) {
50✔
797
               ec = boost::asio::error::eof;
42✔
798
            } else {
799
               ec = alert->type();
8✔
800
            }
801
         }
802
      }
803

804
      /**
805
       * Reads TLS record data from the peer and forwards it to the native
806
       * handle for processing. Note that @p ec will reflect network errors
807
       * only. Any detected or received TLS protocol errors will be retained and
808
       * must be handled by the downstream operation in due time by invoking
809
       * `handle_tls_protocol_errors()`.
810
       *
811
       * @param ec  this error code might be populated with network-related errors
812
       */
813
      void read_and_process_encrypted_data_from_peer(boost::system::error_code& ec) {
67✔
814
         if(ec) {
51✔
815
            return;
16✔
816
         }
817

818
         // If we have received application data in a previous invocation, this
819
         // data needs to be passed to the application first. Otherwise, it
820
         // might get overwritten.
821
         BOTAN_ASSERT(!has_received_data(), "receive buffer is empty");
51✔
822
         BOTAN_ASSERT(!error_from_us() && !alert_from_peer(), "TLS session is healthy");
×
823

824
         // If there's no existing error condition, read and process data from
825
         // the peer and report any sort of network error. TLS related errors do
826
         // not immediately cause an abort, they are checked in the invocation
827
         // via `error_from_us()`.
828
         boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
51✔
829
         if(!ec) {
1✔
830
            process_encrypted_data(read_buffer);
50✔
831
         } else if(ec == boost::asio::error::eof) {
1✔
832
            ec = StreamError::StreamTruncated;
×
833
         }
834
      }
835

836
      /** @brief Synchronously write encrypted data from the send buffer to the next layer.
837
       *
838
       * If this function is called with an error code other than 'Success', it will do nothing and return 0.
839
       *
840
       * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
841
       *           has closed the connection but did not properly shut down the SSL connection.
842
       * @return The number of bytes written.
843
       */
844
      size_t send_pending_encrypted_data(boost::system::error_code& ec) {
69✔
845
         if(ec) {
67✔
846
            return 0;
847
         }
848

849
         auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
67✔
850
         consume_send_buffer(writtenBytes);
67✔
851

852
         if(ec == boost::asio::error::eof && !shutdown_received()) {
67✔
853
            // transport layer was closed by peer without receiving 'close_notify'
854
            ec = StreamError::StreamTruncated;
×
855
         }
856

857
         return writtenBytes;
858
      }
859

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

875
            if(buffers_in_sequence == 0) {
8✔
876
               return {};
×
877
            } else if(buffers_in_sequence == 1) {
8✔
878
               const boost::asio::const_buffer buf = *boost::asio::buffer_sequence_begin(bufs);
44✔
879
               return {static_cast<const uint8_t*>(buf.data()), buf.size()};
44✔
880
            } else {
881
               copy_buffer.resize(boost::asio::buffer_size(bufs));
12✔
882
               boost::asio::buffer_copy(boost::asio::buffer(copy_buffer), bufs);
16✔
883
               return copy_buffer;
8✔
884
            }
885
         };
886

887
         // NOTE: This is not asynchronous: it encrypts the data synchronously.
888
         // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core,
889
         // but is not actually written to the wire, yet.
890
         try_with_error_code([&] { native_handle()->send(unpack(buffers)); }, ec);
110✔
891
      }
55✔
892

893
      /**
894
       * Pass encrypted data received from the peer to the native handle for
895
       * processing. If the @p read_buffer contains coalesced TLS records, this
896
       * might result in multiple TLS protocol state changes.
897
       *
898
       * To allow the ASIO stream wrapper to disentangle those state changes
899
       * properly, any TLS protocol errors are retained and must be handled by
900
       * calling `handle_tls_protocol_errors()` in due time.
901
       *
902
       * @param read_buffer Input buffer containing the encrypted data.
903
       */
904
      void process_encrypted_data(const boost::asio::const_buffer& read_buffer) {
255✔
905
         BOTAN_ASSERT(!alert_from_peer() && !error_from_us(),
510✔
906
                      "no one sent an alert before (no data allowed after that)");
907

908
         // If the local TLS implementation generates an alert, we are notified
909
         // with an exception that is caught in try_with_error_code(). The error
910
         // code is retained and not handled directly. Stream operations will
911
         // have to invoke `handle_tls_protocol_errors()` to handle them later.
912
         try_with_error_code(
255✔
913
            [&] {
498✔
914
               native_handle()->received_data({static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()});
255✔
915
            },
916
            m_ec_from_last_read);
255✔
917
      }
255✔
918

919
      //! @brief Catch exceptions and set an error_code
920
      template <typename Fun>
921
      void try_with_error_code(Fun f, boost::system::error_code& ec) {
454✔
922
         try {
923
            f();
454✔
924
         } catch(const TLS_Exception& e) {
15✔
925
            ec = e.type();
14✔
926
         } catch(const Exception& e) {
1✔
927
            ec = e.error_type();
1✔
928
         } catch(const std::exception&) {
×
929
            ec = ErrorType::Unknown;
×
930
         }
931
      }
454✔
932

933
   private:
934
      /**
935
       * Returns any alert previously received from the peer. This may include
936
       * close_notify. Once the peer has sent any alert, no more data must be
937
       * read from the stream.
938
       */
939
      std::optional<Alert> alert_from_peer() const { return m_core->alert_from_peer(); }
630✔
940

941
      /**
942
       * Returns any error code produced by the local TLS implementation. This
943
       * will _not_ include close_notify. Once our TLS stack has reported an
944
       * error, no more data must be written to the stream. The peer will receive
945
       * the error as a TLS alert.
946
       */
947
      boost::system::error_code error_from_us() const { return m_ec_from_last_read; }
642✔
948

949
   protected:
950
      std::shared_ptr<Context> m_context;
951
      StreamLayer m_nextLayer;
952

953
      std::shared_ptr<StreamCallbacks> m_core;
954
      std::unique_ptr<ChannelT> m_native_handle;
955
      boost::system::error_code m_ec_from_last_read;
956

957
      // Buffer space used to read input intended for the core
958
      std::vector<uint8_t> m_input_buffer_space;
959
      const boost::asio::mutable_buffer m_input_buffer;
960
};
961

962
// deduction guides for convenient construction from an existing
963
// underlying transport stream T
964
template <typename T>
965
Stream(Server_Information, T) -> Stream<T>;
966
template <typename T>
967
Stream(std::shared_ptr<Context>, std::shared_ptr<StreamCallbacks>, T) -> Stream<T>;
968
template <typename T>
969
Stream(std::shared_ptr<Context>, T) -> Stream<T>;
970
template <typename T>
971
Stream(T, std::shared_ptr<Context>) -> Stream<T>;
972

973
}  // namespace Botan::TLS
974

975
#endif
976
#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