Botan 3.8.1
Crypto and TLS for C&
asio_stream.h
Go to the documentation of this file.
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
37namespace Botan::TLS {
38
39template <class SL, class C>
40class 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 */
57class StreamCallbacks : public Callbacks {
58 public:
59 StreamCallbacks() {}
60
61 void tls_emit_data(std::span<const uint8_t> data) final {
62 m_send_buffer.commit(boost::asio::buffer_copy(m_send_buffer.prepare(data.size()),
63 boost::asio::buffer(data.data(), data.size())));
64 }
65
66 void tls_record_received(uint64_t, std::span<const uint8_t> data) final {
67 m_receive_buffer.commit(boost::asio::buffer_copy(m_receive_buffer.prepare(data.size()),
68 boost::asio::const_buffer(data.data(), data.size())));
69 }
70
71 bool tls_peer_closed_connection() final {
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;
80 }
81
82 /**
83 * @param alert a TLS alert sent by the peer
84 */
85 void tls_alert(TLS::Alert alert) final {
86 if(alert.is_fatal() || alert.type() == TLS::AlertType::CloseNotify) {
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;
92 }
93 }
94
95 void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain,
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();
102
103 if(ctx && ctx->has_verify_callback()) {
104 ctx->get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
105 } else {
106 Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
107 }
108 }
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); }
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; }
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; }
124
125 const boost::beast::flat_buffer& receive_buffer() const { return m_receive_buffer; }
126
127 bool shutdown_received() const {
128 return m_alert_from_peer && m_alert_from_peer->type() == AlertType::CloseNotify;
129 }
130
131 std::optional<Alert> alert_from_peer() const { return m_alert_from_peer; }
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
141namespace detail {
142
143template <typename T>
144concept basic_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code)>;
145
146template <typename T>
147concept 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 */
157template <class StreamLayer, class ChannelT = Channel>
158class 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) :
181 m_context(std::move(context)),
182 m_nextLayer(std::forward<Args>(args)...),
183 m_core(std::move(callbacks)),
184 m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'),
185 m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {
186 m_core->set_context(m_context);
187 }
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) :
197 Stream(std::move(context), std::make_shared<StreamCallbacks>(), std::forward<Args>(args)...) {}
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,
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)) {}
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;
233
234 Stream(Stream&& other) = default;
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; }
247
248 next_layer_type& next_layer() { return m_nextLayer; }
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(); }
259
260 using native_handle_type = typename std::add_pointer<ChannelT>::type;
261
262 native_handle_type native_handle() {
263 if(m_native_handle == nullptr) {
264 throw Botan::Invalid_State("ASIO native handle unexpectedly null");
265 }
266 return m_native_handle.get();
267 }
268
269 //! @}
270 //! \name configuration and callback setters
271 //! @{
272
273 /**
274 * @brief Override the tls_verify_cert_chain callback
275 *
276 * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback
277 * used in the handshake.
278 * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback
279 *
280 * @note This function should only be called before initiating the TLS handshake
281 */
282 void set_verify_callback(Context::Verify_Callback callback) {
283 m_context->set_verify_callback(std::move(callback));
284 }
285
286 /**
287 * @brief Compatibility overload of @ref set_verify_callback
288 *
289 * @param callback the callback implementation
290 * @param ec This parameter is unused.
291 */
292 void set_verify_callback(Context::Verify_Callback callback, [[maybe_unused]] boost::system::error_code& ec) {
293 m_context->set_verify_callback(std::move(callback));
294 }
295
296 /**
297 * Not Implemented.
298 * @param depth the desired verification depth
299 * @throws Not_Implemented todo
300 */
301 void set_verify_depth([[maybe_unused]] int 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([[maybe_unused]] int depth, boost::system::error_code& ec) {
311 ec = ErrorType::NotImplemented;
312 }
313
314 /**
315 * Not Implemented.
316 * @param v the desired verify mode
317 * @throws Not_Implemented todo
318 */
319 template <typename verify_mode>
320 void set_verify_mode([[maybe_unused]] verify_mode v) {
321 throw Not_Implemented("set_verify_mode is not implemented");
322 }
323
324 /**
325 * Not Implemented.
326 * @param v the desired verify mode
327 * @param ec Will be set to `Botan::ErrorType::NotImplemented`
328 */
329 template <typename verify_mode>
330 void set_verify_mode([[maybe_unused]] verify_mode v, boost::system::error_code& ec) {
331 ec = ErrorType::NotImplemented;
332 }
333
334 //! @}
335 //! \name handshake methods
336 //! @{
337
338 /**
339 * @brief Performs SSL handshaking.
340 *
341 * The function call will block until handshaking is complete or an error occurs.
342 *
343 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
344 * @throws boost::system::system_error if error occured
345 */
346 void handshake(Connection_Side side) {
347 boost::system::error_code ec;
348 handshake(side, ec);
349 boost::asio::detail::throw_error(ec, "handshake");
350 }
351
352 /**
353 * @brief Performs SSL handshaking.
354 *
355 * The function call will block until handshaking is complete or an error occurs.
356 *
357 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
358 * @param ec Set to indicate what error occurred, if any.
359 */
360 void handshake(Connection_Side side, boost::system::error_code& ec) {
361 setup_native_handle(side, ec);
362
363 // We write to the socket if we have data to send and read from it
364 // otherwise, until either some error occured or we have successfully
365 // performed the handshake.
366 while(!ec) {
367 // Send pending data to the peer and abort the handshake if that
368 // fails with a network error. We do that first, to allow sending
369 // any final message before reporting the handshake as "finished".
370 if(has_data_to_send()) {
371 send_pending_encrypted_data(ec);
372 }
373
374 // Once the underlying TLS implementation reports a complete and
375 // successful handshake we're done.
376 if(native_handle()->is_handshake_complete()) {
377 return;
378 }
379
380 // Handle and report any TLS protocol errors that might have
381 // surfaced in a previous iteration. By postponing their handling we
382 // allow the stream to send a respective TLS alert to the peer before
383 // aborting the handshake.
384 handle_tls_protocol_errors(ec);
385
386 // If we don't have any encrypted data to send we attempt to read
387 // more data from the peer. This reports network errors immediately.
388 // TLS protocol errors result in an internal state change which is
389 // handled by `handle_tls_protocol_errors()` in the next iteration.
390 read_and_process_encrypted_data_from_peer(ec);
391 }
392 }
393
394 /**
395 * @brief Starts an asynchronous SSL handshake.
396 *
397 * This function call always returns immediately.
398 *
399 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
400 * @param completion_token The completion handler to be called when the handshake operation completes.
401 * The completion signature of the handler must be: void(boost::system::error_code).
402 */
403 template <detail::basic_completion_token CompletionToken = default_completion_token>
404 auto async_handshake(Botan::TLS::Connection_Side side,
405 CompletionToken&& completion_token = default_completion_token{}) {
406 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
407 [this](auto&& completion_handler, TLS::Connection_Side connection_side) {
408 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
409
410 boost::system::error_code ec;
411 setup_native_handle(connection_side, ec);
412
413 detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
414 std::forward<completion_handler_t>(completion_handler), *this, ec};
415 },
416 completion_token,
417 side);
418 }
419
420 /**
421 * Not Implemented.
422 * @throws Not_Implemented todo
423 */
424 template <typename ConstBufferSequence, detail::basic_completion_token BufferedHandshakeHandler>
425 auto async_handshake([[maybe_unused]] Connection_Side side,
426 [[maybe_unused]] const ConstBufferSequence& buffers,
427 [[maybe_unused]] BufferedHandshakeHandler&& handler) {
428 throw Not_Implemented("buffered async handshake is not implemented");
429 }
430
431 //! @}
432 //! \name shutdown methods
433 //! @{
434
435 /**
436 * @brief Shut down SSL on the stream.
437 *
438 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
439 * or an error occurs. Note that this will not close the lowest layer.
440 *
441 * Note that this can be used in reaction of a received shutdown alert from the peer.
442 *
443 * @param ec Set to indicate what error occured, if any.
444 */
445 void shutdown(boost::system::error_code& ec) {
446 try_with_error_code([&] { native_handle()->close(); }, ec);
447
448 send_pending_encrypted_data(ec);
449 }
450
451 /**
452 * @brief Shut down SSL on the stream.
453 *
454 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
455 * or an error occurs. Note that this will not close the lowest layer.
456 *
457 * Note that this can be used in reaction of a received shutdown alert from the peer.
458 *
459 * @throws boost::system::system_error if error occured
460 */
461 void shutdown() {
462 boost::system::error_code ec;
463 shutdown(ec);
464 boost::asio::detail::throw_error(ec, "shutdown");
465 }
466
467 private:
468 /**
469 * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
470 * signature of `AsyncWriteOperation`.
471 *
472 * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
473 * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
474 */
475 template <typename Handler, typename Executor>
476 struct Wrapper {
477 void operator()(boost::system::error_code ec, std::size_t) { handler(ec); }
478
479 using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
480
481 executor_type get_executor() const noexcept {
482 return boost::asio::get_associated_executor(handler, io_executor);
483 }
484
485 using allocator_type = boost::asio::associated_allocator_t<Handler>;
486
487 allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); }
488
489 Handler handler;
490 Executor io_executor;
491 };
492
493 public:
494 /**
495 * @brief Asynchronously shut down SSL on the stream.
496 *
497 * This function call always returns immediately.
498 *
499 * Note that this can be used in reaction of a received shutdown alert from the peer.
500 *
501 * @param completion_token The completion handler to be called when the shutdown operation completes.
502 * The completion signature of the handler must be: void(boost::system::error_code).
503 */
504 template <detail::basic_completion_token CompletionToken = default_completion_token>
505 auto async_shutdown(CompletionToken&& completion_token = default_completion_token{}) {
506 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
507 [this](auto&& completion_handler) {
508 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
509
510 boost::system::error_code ec;
511 try_with_error_code([&] { native_handle()->close(); }, ec);
512
513 using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
514
515 TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
516 write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
517 *this,
518 boost::asio::buffer_size(send_buffer()),
519 ec};
520 },
521 completion_token);
522 }
523
524 //! @}
525 //! \name I/O methods
526 //! @{
527
528 /**
529 * @brief Read some data from the stream.
530 *
531 * The function call will block until one or more bytes of data has been read successfully, or until an error
532 * occurs.
533 *
534 * @param buffers The buffers into which the data will be read.
535 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
536 * has closed the connection but did not properly shut down the SSL connection.
537 * @return The number of bytes read. Returns 0 if an error occurred.
538 */
539 template <typename MutableBufferSequence>
540 std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) {
541 // We read from the socket until either some error occured or we have
542 // decrypted at least one byte of application data.
543 while(!ec) {
544 // Some previous invocation of process_encrypted_data() generated
545 // application data in the output buffer that can now be returned.
546 if(has_received_data()) {
547 return copy_received_data(buffers);
548 }
549
550 // Handle and report any TLS protocol errors (including a
551 // close_notify) that might have surfaced in a previous iteration
552 // (in `read_and_process_encrypted_data_from_peer()`). This makes
553 // sure that all received application data was handed out to the
554 // caller before reporting an error (e.g. EOF at the end of the
555 // stream).
556 handle_tls_protocol_errors(ec);
557
558 // If we don't have any plaintext application data, yet, we attempt
559 // to read more data from the peer. This reports network errors
560 // immediately. TLS protocol errors result in an internal state
561 // change which is handled by `handle_tls_protocol_errors()` in the
562 // next iteration.
563 read_and_process_encrypted_data_from_peer(ec);
564 }
565
566 return 0;
567 }
568
569 /**
570 * @brief Read some data from the stream.
571 *
572 * The function call will block until one or more bytes of data has been read successfully, or until an error
573 * occurs.
574 *
575 * @param buffers The buffers into which the data will be read.
576 * @return The number of bytes read. Returns 0 if an error occurred.
577 * @throws boost::system::system_error if error occured
578 */
579 template <typename MutableBufferSequence>
580 std::size_t read_some(const MutableBufferSequence& buffers) {
581 boost::system::error_code ec;
582 const auto n = read_some(buffers, ec);
583 boost::asio::detail::throw_error(ec, "read_some");
584 return n;
585 }
586
587 /**
588 * @brief Write some data to the stream.
589 *
590 * The function call will block until one or more bytes of data has been written successfully, or until an error
591 * occurs.
592 *
593 * @param buffers The data to be written.
594 * @param ec Set to indicate what error occurred, if any.
595 * @return The number of bytes processed from the input buffers.
596 */
597 template <typename ConstBufferSequence>
598 std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
599 tls_encrypt(buffers, ec);
600 send_pending_encrypted_data(ec);
601 return !ec ? boost::asio::buffer_size(buffers) : 0;
602 }
603
604 /**
605 * @brief Write some data to the stream.
606 *
607 * The function call will block until one or more bytes of data has been written successfully, or until an error
608 * occurs.
609 *
610 * @param buffers The data to be written.
611 * @return The number of bytes written.
612 * @throws boost::system::system_error if error occured
613 */
614 template <typename ConstBufferSequence>
615 std::size_t write_some(const ConstBufferSequence& buffers) {
616 boost::system::error_code ec;
617 const auto n = write_some(buffers, ec);
618 boost::asio::detail::throw_error(ec, "write_some");
619 return n;
620 }
621
622 /**
623 * @brief Start an asynchronous write. The function call always returns immediately.
624 *
625 * @param buffers The data to be written.
626 * @param completion_token The completion handler to be called when the write operation completes. Copies of the
627 * handler will be made as required. The completion signature of the handler must be:
628 * void(boost::system::error_code, std::size_t).
629 */
630 template <typename ConstBufferSequence,
631 detail::byte_size_completion_token CompletionToken = default_completion_token>
632 auto async_write_some(const ConstBufferSequence& buffers,
633 CompletionToken&& completion_token = default_completion_token{}) {
634 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
635 [this](auto&& completion_handler, const auto& bufs) {
636 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
637
638 boost::system::error_code ec;
639 tls_encrypt(bufs, ec);
640
641 if(ec) {
642 // we cannot be sure how many bytes were committed here so clear the send_buffer and let the
643 // AsyncWriteOperation call the handler with the error_code set
644 m_core->send_buffer().consume(m_core->send_buffer().size());
645 }
646
647 detail::AsyncWriteOperation<completion_handler_t, Stream> op{
648 std::forward<completion_handler_t>(completion_handler),
649 *this,
650 ec ? 0 : boost::asio::buffer_size(bufs),
651 ec};
652 },
653 completion_token,
654 buffers);
655 }
656
657 /**
658 * @brief Start an asynchronous read. The function call always returns immediately.
659 *
660 * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as
661 * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee
662 * that they remain valid until the handler is called.
663 * @param completion_token The completion handler to be called when the read operation completes. The completion
664 * signature of the handler must be: void(boost::system::error_code, std::size_t).
665 */
666 template <typename MutableBufferSequence,
667 detail::byte_size_completion_token CompletionToken = default_completion_token>
668 auto async_read_some(const MutableBufferSequence& buffers,
669 CompletionToken&& completion_token = default_completion_token{}) {
670 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
671 [this](auto&& completion_handler, const auto& bufs) {
672 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
673
674 detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
675 std::forward<completion_handler_t>(completion_handler), *this, bufs};
676 },
677 completion_token,
678 buffers);
679 }
680
681 //! @}
682
683 //! @brief Indicates whether a close_notify alert has been received from the peer.
684 //!
685 //! Note that we cannot m_core.is_closed_for_reading() because this wants to
686 //! explicitly check that the peer sent close_notify.
687 bool shutdown_received() const { return m_core->shutdown_received(); }
688
689 protected:
690 template <class H, class S, class M, class A>
691 friend class detail::AsyncReadOperation;
692 template <class H, class S, class A>
693 friend class detail::AsyncWriteOperation;
694 template <class H, class S, class A>
695 friend class detail::AsyncHandshakeOperation;
696
697 const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
698
699 boost::asio::const_buffer send_buffer() const { return m_core->send_buffer().data(); }
700
701 //! @brief Check if decrypted data is available in the receive buffer
702 bool has_received_data() const { return m_core->receive_buffer().size() > 0; }
703
704 //! @brief Copy decrypted data into the user-provided buffer
705 template <typename MutableBufferSequence>
706 std::size_t copy_received_data(MutableBufferSequence buffers) {
707 // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the CallbacksT with
708 // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record
709 // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller
710 // than the decrypted record, so this optimization might not be worth the additional complexity.
711 const auto copiedBytes = boost::asio::buffer_copy(buffers, m_core->receive_buffer().data());
712 m_core->receive_buffer().consume(copiedBytes);
713 return copiedBytes;
714 }
715
716 //! @brief Check if encrypted data is available in the send buffer
717 bool has_data_to_send() const { return m_core->send_buffer().size() > 0; }
718
719 //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer
720 void consume_send_buffer(std::size_t bytesConsumed) { m_core->send_buffer().consume(bytesConsumed); }
721
722 /**
723 * @brief Create the native handle.
724 *
725 * Depending on the desired connection side, this function will create a TLS::Client or a
726 * TLS::Server.
727 *
728 * @param side The desired connection side (client or server)
729 * @param ec Set to indicate what error occurred, if any.
730 */
731 void setup_native_handle(Connection_Side side, boost::system::error_code& ec) {
732 // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has
733 // been specified. This allows mocking the native_handle in test code.
734 if constexpr(std::is_same<ChannelT, Channel>::value) {
735 if(m_native_handle != nullptr) {
736 throw Botan::Invalid_State("ASIO native handle unexpectedly set");
737 }
738
739 try_with_error_code(
740 [&] {
741 if(side == Connection_Side::Client) {
742 m_native_handle = std::unique_ptr<Client>(
743 new Client(m_core,
744 m_context->m_session_manager,
745 m_context->m_credentials_manager,
746 m_context->m_policy,
747 m_context->m_rng,
748 m_context->m_server_info,
749 m_context->m_policy->latest_supported_version(false /* no DTLS */)));
750 } else {
751 m_native_handle = std::unique_ptr<Server>(new Server(m_core,
752 m_context->m_session_manager,
753 m_context->m_credentials_manager,
754 m_context->m_policy,
755 m_context->m_rng,
756 false /* no DTLS */));
757 }
758 },
759 ec);
760 }
761 }
762
763 /**
764 * The `Stream` has to distinguish from network-related issues (that are
765 * reported immediately) from TLS protocol errors, that must be retained
766 * and emitted once all legal application traffic received before is
767 * pushed to the downstream application.
768 *
769 * See also `process_encrypted_data()` and `StreamCallbacks::tls_alert()`
770 * where those TLS protocol errors are detected and retained for eventual
771 * handling in this method.
772 *
773 * See also https://github.com/randombit/botan/pull/3801 for a detailed
774 * description of the ASIO stream's state management.
775 *
776 * @param ec this error code is set if we previously detected a TLS
777 * protocol error.
778 */
779 void handle_tls_protocol_errors(boost::system::error_code& ec) {
780 if(ec) {
781 return;
782 }
783
784 // If we had raised an error while processing TLS records received from
785 // the peer, we expose that error here.
786 //
787 // See also `process_encrypted_data()`.
788 else if(auto error = error_from_us()) {
789 ec = error;
790 }
791
792 // If we had received a TLS alert from the peer, we expose that error
793 // here. See also `StreamCallbacks::tls_alert()` where such alerts
794 // would be detected and retained initially.
795 //
796 // Note that a close_notify is always a legal way for the peer to end a
797 // TLS session. When received during the handshake it typically means
798 // that the peer wanted to cancel the handshake for some reason not
799 // related to the TLS protocol.
800 else if(auto alert = alert_from_peer()) {
801 if(alert->type() == AlertType::CloseNotify) {
802 ec = boost::asio::error::eof;
803 } else {
804 ec = alert->type();
805 }
806 }
807 }
808
809 /**
810 * Reads TLS record data from the peer and forwards it to the native
811 * handle for processing. Note that @p ec will reflect network errors
812 * only. Any detected or received TLS protocol errors will be retained and
813 * must be handled by the downstream operation in due time by invoking
814 * `handle_tls_protocol_errors()`.
815 *
816 * @param ec this error code might be populated with network-related errors
817 */
818 void read_and_process_encrypted_data_from_peer(boost::system::error_code& ec) {
819 if(ec) {
820 return;
821 }
822
823 // If we have received application data in a previous invocation, this
824 // data needs to be passed to the application first. Otherwise, it
825 // might get overwritten.
826 if(has_received_data()) {
827 throw Botan::Invalid_State("ASIO receive buffer not empty");
828 }
829
830 if(error_from_us() || alert_from_peer()) {
831 throw Botan::Invalid_State("ASIO TLS session no longer healthy");
832 }
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)};
839 if(!ec) {
840 process_encrypted_data(read_buffer);
841 } else if(ec == boost::asio::error::eof) {
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) {
855 if(ec) {
856 return 0;
857 }
858
859 auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
860 consume_send_buffer(writtenBytes);
861
862 if(ec == boost::asio::error::eof && !shutdown_received()) {
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) {
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;
881 auto unpack = [&copy_buffer](const auto& bufs) -> std::span<const uint8_t> {
882 const auto buffers_in_sequence =
883 std::distance(boost::asio::buffer_sequence_begin(bufs), boost::asio::buffer_sequence_end(bufs));
884
885 if(buffers_in_sequence == 0) {
886 return {};
887 } else if(buffers_in_sequence == 1) {
888 const boost::asio::const_buffer buf = *boost::asio::buffer_sequence_begin(bufs);
889 return {static_cast<const uint8_t*>(buf.data()), buf.size()};
890 } else {
891 copy_buffer.resize(boost::asio::buffer_size(bufs));
892 boost::asio::buffer_copy(boost::asio::buffer(copy_buffer), bufs);
893 return copy_buffer;
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);
901 }
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) {
915 if(alert_from_peer() || error_from_us()) {
916 throw Botan::Invalid_State("ASIO TLS session no longer healthy");
917 }
918
919 // If the local TLS implementation generates an alert, we are notified
920 // with an exception that is caught in try_with_error_code(). The error
921 // code is retained and not handled directly. Stream operations will
922 // have to invoke `handle_tls_protocol_errors()` to handle them later.
923 try_with_error_code(
924 [&] {
925 native_handle()->received_data({static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()});
926 },
927 m_ec_from_last_read);
928 }
929
930 //! @brief Catch exceptions and set an error_code
931 template <typename Fun>
932 void try_with_error_code(Fun f, boost::system::error_code& ec) {
933 try {
934 f();
935 } catch(const TLS_Exception& e) {
936 ec = e.type();
937 } catch(const Exception& e) {
938 ec = e.error_type();
939 } catch(const std::exception&) {
940 ec = ErrorType::Unknown;
941 }
942 }
943
944 private:
945 /**
946 * Returns any alert previously received from the peer. This may include
947 * close_notify. Once the peer has sent any alert, no more data must be
948 * read from the stream.
949 */
950 std::optional<Alert> alert_from_peer() const { return m_core->alert_from_peer(); }
951
952 /**
953 * Returns any error code produced by the local TLS implementation. This
954 * will _not_ include close_notify. Once our TLS stack has reported an
955 * error, no more data must be written to the stream. The peer will receive
956 * the error as a TLS alert.
957 */
958 boost::system::error_code error_from_us() const { return m_ec_from_last_read; }
959
960 protected:
961 std::shared_ptr<Context> m_context;
962 StreamLayer m_nextLayer;
963
964 std::shared_ptr<StreamCallbacks> m_core;
965 std::unique_ptr<ChannelT> m_native_handle;
966 boost::system::error_code m_ec_from_last_read;
967
968 // Buffer space used to read input intended for the core
969 std::vector<uint8_t> m_input_buffer_space;
970 const boost::asio::mutable_buffer m_input_buffer;
971};
972
973// deduction guides for convenient construction from an existing
974// underlying transport stream T
975template <typename T>
976Stream(Server_Information, T) -> Stream<T>;
977template <typename T>
978Stream(std::shared_ptr<Context>, std::shared_ptr<StreamCallbacks>, T) -> Stream<T>;
979template <typename T>
980Stream(std::shared_ptr<Context>, T) -> Stream<T>;
981template <typename T>
982Stream(T, std::shared_ptr<Context>) -> Stream<T>;
983
984} // namespace Botan::TLS
985
986#endif
987#endif // BOTAN_ASIO_STREAM_H_
constexpr void unpack(Polynomial< PolyTrait, D > &p, ByteSourceT &byte_source, UnmapFnT unmap)
@ MAX_CIPHERTEXT_SIZE
Definition tls_magic.h:33