Botan 3.12.0
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() = default;
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 /*record_number*/, 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 std::string tls_server_choose_app_protocol(const std::vector<std::string>& client_protos) override {
111 if(client_protos.empty()) {
112 return "";
113 }
114 auto ctx = m_context.lock();
115
116 if(!ctx || ctx->m_app_protocols.empty()) {
117 return "";
118 }
119 // Priority is to the server.
120 for(const auto& server_proto : ctx->m_app_protocols) {
121 for(const auto& client_proto : client_protos) {
122 if(server_proto == client_proto) {
123 return server_proto;
124 }
125 }
126 }
127 // No match.
128 throw TLS_Exception(
129 Alert::NoApplicationProtocol,
130 "Rejecting ALPN request: no overlap between client-offered and server-configured application protocols");
131 }
132
133 private:
134 // The members below are meant for the tightly-coupled Stream class only
135 template <class SL, class C>
136 friend class Stream;
137
138 void set_context(std::weak_ptr<Botan::TLS::Context> context) { m_context = std::move(context); }
139
140 void consume_send_buffer() { m_send_buffer.consume(m_send_buffer.size()); }
141
142 boost::beast::flat_buffer& send_buffer() { return m_send_buffer; }
143
144 const boost::beast::flat_buffer& send_buffer() const { return m_send_buffer; }
145
146 boost::beast::flat_buffer& receive_buffer() { return m_receive_buffer; }
147
148 const boost::beast::flat_buffer& receive_buffer() const { return m_receive_buffer; }
149
150 bool shutdown_received() const {
151 return m_alert_from_peer && m_alert_from_peer->type() == AlertType::CloseNotify;
152 }
153
154 std::optional<Alert> alert_from_peer() const { return m_alert_from_peer; }
155
156 private:
157 std::optional<Alert> m_alert_from_peer;
158 boost::beast::flat_buffer m_receive_buffer;
159 boost::beast::flat_buffer m_send_buffer;
160
161 std::weak_ptr<TLS::Context> m_context;
162};
163
164namespace detail {
165
166template <typename T>
167concept basic_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code)>;
168
169template <typename T>
170concept byte_size_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code, size_t)>;
171
172} // namespace detail
173
174/**
175 * @brief boost::asio compatible SSL/TLS stream
176 *
177 * @tparam StreamLayer type of the next layer, usually a network socket
178 * @tparam ChannelT type of the native_handle, defaults to TLS::Channel, only needed for testing purposes
179 */
180template <class StreamLayer, class ChannelT = Channel>
181class Stream {
182 private:
183 using default_completion_token =
184 boost::asio::default_completion_token_t<boost::beast::executor_type<StreamLayer>>;
185
186 public:
187 //! \name construction
188 //! @{
189
190 /**
191 * @brief Construct a new Stream with a customizable instance of Callbacks
192 *
193 * @param context The context parameter is used to set up the underlying native handle.
194 * @param callbacks The callbacks parameter may contain an instance of a derived TLS::Callbacks
195 * class to allow for fine-grained customization of the TLS stream. Note that
196 * applications need to ensure a 1-to-1 relationship between instances of
197 * Callbacks and Streams. A future major version of Botan will use a unique_ptr
198 * here.
199 *
200 * @param args Arguments to be forwarded to the construction of the next layer.
201 */
202 template <typename... Args>
203 explicit Stream(std::shared_ptr<Context> context, std::shared_ptr<StreamCallbacks> callbacks, Args&&... args) :
204 m_context(std::move(context)),
205 m_nextLayer(std::forward<Args>(args)...),
206 m_core(std::move(callbacks)),
207 m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'),
208 m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {
209 m_core->set_context(m_context);
210 }
211
212 /**
213 * @brief Construct a new Stream
214 *
215 * @param context The context parameter is used to set up the underlying native handle.
216 * @param args Arguments to be forwarded to the construction of the next layer.
217 */
218 template <typename... Args>
219 explicit Stream(std::shared_ptr<Context> context, Args&&... args) :
220 Stream(std::move(context), std::make_shared<StreamCallbacks>(), std::forward<Args>(args)...) {}
221
222 /**
223 * @brief Construct a new Stream
224 *
225 * Convenience overload for boost::asio::ssl::stream compatibility.
226 *
227 * @param arg This argument is forwarded to the construction of the next layer.
228 * @param context The context parameter is used to set up the underlying native handle.
229 * @param callbacks The (optional) callbacks object that the stream should use. Note that
230 * applications need to ensure a 1-to-1 relationship between instances of Callbacks
231 * and Streams. A future major version of Botan will use a unique_ptr here.
232 */
233 template <typename Arg>
234 explicit Stream(Arg&& arg,
235 std::shared_ptr<Context> context,
236 std::shared_ptr<StreamCallbacks> callbacks = std::make_shared<StreamCallbacks>()) :
237 Stream(std::move(context), std::move(callbacks), std::forward<Arg>(arg)) {}
238
239 #if defined(BOTAN_HAS_DEFAULT_TLS_CONTEXT)
240 /**
241 * @brief Conveniently construct a new Stream with default settings
242 *
243 * This is typically a good starting point for a basic TLS client. For
244 * much more control and configuration options, consider creating a custom
245 * Context object and pass it to the respective constructor.
246 *
247 * @param server_info Basic information about the host to connect to (SNI)
248 * @param args Arguments to be forwarded to the construction of the next layer.
249 */
250 template <typename... Args>
251 explicit Stream(Server_Information server_info, Args&&... args) :
252 Stream(std::make_shared<Context>(std::move(server_info)), std::forward<Args>(args)...) {}
253 #endif
254
255 virtual ~Stream() = default;
256
257 Stream(Stream&& other) = default;
258 Stream& operator=(Stream&& other) = default;
259
260 Stream(const Stream& other) = delete;
261 Stream& operator=(const Stream& other) = delete;
262
263 //! @}
264 //! \name boost::asio accessor methods
265 //! @{
266
267 using next_layer_type = std::remove_reference_t<StreamLayer>;
268
269 const next_layer_type& next_layer() const { return m_nextLayer; }
270
271 next_layer_type& next_layer() { return m_nextLayer; }
272
273 using lowest_layer_type = typename boost::beast::lowest_layer_type<StreamLayer>;
274
275 lowest_layer_type& lowest_layer() { return boost::beast::get_lowest_layer(m_nextLayer); }
276
277 const lowest_layer_type& lowest_layer() const { return boost::beast::get_lowest_layer(m_nextLayer); }
278
279 using executor_type = typename next_layer_type::executor_type;
280
281 executor_type get_executor() noexcept { return m_nextLayer.get_executor(); }
282
283 using native_handle_type = std::add_pointer_t<ChannelT>;
284
285 native_handle_type native_handle() {
286 if(m_native_handle == nullptr) {
287 throw Botan::Invalid_State("ASIO native handle unexpectedly null");
288 }
289 return m_native_handle.get();
290 }
291
292 //! @}
293 //! \name configuration and callback setters
294 //! @{
295
296 /**
297 * @brief Override the tls_verify_cert_chain callback
298 *
299 * This changes the verify_callback in the stream's TLS::Context, and hence the tls_verify_cert_chain callback
300 * used in the handshake.
301 * Using this function is equivalent to setting the callback via @see Botan::TLS::Context::set_verify_callback
302 *
303 * @note This function should only be called before initiating the TLS handshake
304 */
305 void set_verify_callback(Context::Verify_Callback callback) {
306 m_context->set_verify_callback(std::move(callback));
307 }
308
309 /**
310 * @brief Compatibility overload of @ref set_verify_callback
311 *
312 * @param callback the callback implementation
313 * @param ec This parameter is unused.
314 */
315 void set_verify_callback(Context::Verify_Callback callback, [[maybe_unused]] boost::system::error_code& ec) {
316 m_context->set_verify_callback(std::move(callback));
317 }
318
319 /**
320 * Not Implemented.
321 * @param depth the desired verification depth
322 * @throws Not_Implemented todo
323 */
324 void set_verify_depth([[maybe_unused]] int depth) {
325 throw Not_Implemented("set_verify_depth is not implemented");
326 }
327
328 /**
329 * Not Implemented.
330 * @param depth the desired verification depth
331 * @param ec Will be set to `Botan::ErrorType::NotImplemented`
332 */
333 void set_verify_depth([[maybe_unused]] int depth, boost::system::error_code& ec) {
334 ec = ErrorType::NotImplemented;
335 }
336
337 /**
338 * Not Implemented.
339 * @param v the desired verify mode
340 * @throws Not_Implemented todo
341 */
342 template <typename verify_mode>
343 void set_verify_mode([[maybe_unused]] verify_mode v) {
344 throw Not_Implemented("set_verify_mode is not implemented");
345 }
346
347 /**
348 * Not Implemented.
349 * @param v the desired verify mode
350 * @param ec Will be set to `Botan::ErrorType::NotImplemented`
351 */
352 template <typename verify_mode>
353 void set_verify_mode([[maybe_unused]] verify_mode v, boost::system::error_code& ec) {
354 ec = ErrorType::NotImplemented;
355 }
356
357 //! @}
358 //! \name handshake methods
359 //! @{
360
361 /**
362 * @brief Performs SSL handshaking.
363 *
364 * The function call will block until handshaking is complete or an error occurs.
365 *
366 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
367 * @throws boost::system::system_error if error occurred
368 */
369 void handshake(Connection_Side side) {
370 boost::system::error_code ec;
371 handshake(side, ec);
372 boost::asio::detail::throw_error(ec, "handshake");
373 }
374
375 /**
376 * @brief Performs SSL handshaking.
377 *
378 * The function call will block until handshaking is complete or an error occurs.
379 *
380 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
381 * @param ec Set to indicate what error occurred, if any.
382 */
383 void handshake(Connection_Side side, boost::system::error_code& ec) {
384 setup_native_handle(side, ec);
385
386 // We write to the socket if we have data to send and read from it
387 // otherwise, until either some error occurred or we have successfully
388 // performed the handshake.
389 while(!ec) {
390 // Send pending data to the peer and abort the handshake if that
391 // fails with a network error. We do that first, to allow sending
392 // any final message before reporting the handshake as "finished".
393 if(has_data_to_send()) {
394 send_pending_encrypted_data(ec);
395 }
396
397 // Once the underlying TLS implementation reports a complete and
398 // successful handshake we're done.
399 if(native_handle()->is_handshake_complete()) {
400 return;
401 }
402
403 // Handle and report any TLS protocol errors that might have
404 // surfaced in a previous iteration. By postponing their handling we
405 // allow the stream to send a respective TLS alert to the peer before
406 // aborting the handshake.
407 handle_tls_protocol_errors(ec);
408
409 // If we don't have any encrypted data to send we attempt to read
410 // more data from the peer. This reports network errors immediately.
411 // TLS protocol errors result in an internal state change which is
412 // handled by `handle_tls_protocol_errors()` in the next iteration.
413 read_and_process_encrypted_data_from_peer(ec);
414 }
415 }
416
417 /**
418 * @brief Starts an asynchronous SSL handshake.
419 *
420 * This function call always returns immediately.
421 *
422 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
423 * @param completion_token The completion handler to be called when the handshake operation completes.
424 * The completion signature of the handler must be: void(boost::system::error_code).
425 */
426 template <detail::basic_completion_token CompletionToken = default_completion_token>
427 auto async_handshake(Botan::TLS::Connection_Side side,
428 // NOLINTNEXTLINE(*-missing-std-forward)
429 CompletionToken&& completion_token = default_completion_token{}) {
430 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
431 [this](auto&& completion_handler, TLS::Connection_Side connection_side) {
432 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
433
434 boost::system::error_code ec;
435 setup_native_handle(connection_side, ec);
436
437 const detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
438 std::forward<completion_handler_t>(completion_handler), *this, ec};
439 },
440 completion_token,
441 side);
442 }
443
444 /**
445 * Not Implemented.
446 * @throws Not_Implemented todo
447 */
448 template <typename ConstBufferSequence, detail::basic_completion_token BufferedHandshakeHandler>
449 auto async_handshake([[maybe_unused]] Connection_Side side,
450 [[maybe_unused]] const ConstBufferSequence& buffers,
451 [[maybe_unused]] BufferedHandshakeHandler&& handler /* NOLINT(*missing-std-forward) */) {
452 throw Not_Implemented("buffered async handshake is not implemented");
453 }
454
455 //! @}
456 //! \name shutdown methods
457 //! @{
458
459 /**
460 * @brief Shut down SSL on the stream.
461 *
462 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
463 * or an error occurs. Note that this will not close the lowest layer.
464 *
465 * Note that this can be used in reaction of a received shutdown alert from the peer.
466 *
467 * @param ec Set to indicate what error occurred, if any.
468 */
469 void shutdown(boost::system::error_code& ec) {
470 try_with_error_code([&] { native_handle()->close(); }, ec);
471
472 send_pending_encrypted_data(ec);
473 }
474
475 /**
476 * @brief Shut down SSL on the stream.
477 *
478 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
479 * or an error occurs. Note that this will not close the lowest layer.
480 *
481 * Note that this can be used in reaction of a received shutdown alert from the peer.
482 *
483 * @throws boost::system::system_error if error occurred
484 */
485 void shutdown() {
486 boost::system::error_code ec;
487 shutdown(ec);
488 boost::asio::detail::throw_error(ec, "shutdown");
489 }
490
491 private:
492 /**
493 * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
494 * signature of `AsyncWriteOperation`.
495 *
496 * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
497 * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
498 */
499 template <typename Handler, typename Executor>
500 struct Wrapper {
501 void operator()(boost::system::error_code ec, std::size_t /*unused*/) { handler(ec); }
502
503 using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
504
505 executor_type get_executor() const noexcept {
506 return boost::asio::get_associated_executor(handler, io_executor);
507 }
508
509 using allocator_type = boost::asio::associated_allocator_t<Handler>;
510
511 allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); }
512
513 Handler handler; // NOLINT(*-non-private-member-variable*)
514 Executor io_executor; // NOLINT(*-non-private-member-variable*)
515 };
516
517 public:
518 /**
519 * @brief Asynchronously shut down SSL on the stream.
520 *
521 * This function call always returns immediately.
522 *
523 * Note that this can be used in reaction of a received shutdown alert from the peer.
524 *
525 * @param completion_token The completion handler to be called when the shutdown operation completes.
526 * The completion signature of the handler must be: void(boost::system::error_code).
527 */
528 template <detail::basic_completion_token CompletionToken = default_completion_token>
529 // NOLINTNEXTLINE(*-missing-std-forward)
530 auto async_shutdown(CompletionToken&& completion_token = default_completion_token{}) {
531 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
532 [this](auto&& completion_handler) {
533 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
534
535 boost::system::error_code ec;
536 try_with_error_code([&] { native_handle()->close(); }, ec);
537
538 using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
539
540 const TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
541 write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
542 *this,
543 boost::asio::buffer_size(send_buffer()),
544 ec};
545 },
546 completion_token);
547 }
548
549 //! @}
550 //! \name I/O methods
551 //! @{
552
553 /**
554 * @brief Read some data from the stream.
555 *
556 * The function call will block until one or more bytes of data has been read successfully, or until an error
557 * occurs.
558 *
559 * @param buffers The buffers into which the data will be read.
560 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
561 * has closed the connection but did not properly shut down the SSL connection.
562 * @return The number of bytes read. Returns 0 if an error occurred.
563 */
564 template <typename MutableBufferSequence>
565 std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) {
566 // We read from the socket until either some error occurred or we have
567 // decrypted at least one byte of application data.
568 while(!ec) {
569 // Some previous invocation of process_encrypted_data() generated
570 // application data in the output buffer that can now be returned.
571 if(has_received_data()) {
572 return copy_received_data(buffers);
573 }
574
575 // Handle and report any TLS protocol errors (including a
576 // close_notify) that might have surfaced in a previous iteration
577 // (in `read_and_process_encrypted_data_from_peer()`). This makes
578 // sure that all received application data was handed out to the
579 // caller before reporting an error (e.g. EOF at the end of the
580 // stream).
581 handle_tls_protocol_errors(ec);
582
583 // If we don't have any plaintext application data, yet, we attempt
584 // to read more data from the peer. This reports network errors
585 // immediately. TLS protocol errors result in an internal state
586 // change which is handled by `handle_tls_protocol_errors()` in the
587 // next iteration.
588 read_and_process_encrypted_data_from_peer(ec);
589 }
590
591 return 0;
592 }
593
594 /**
595 * @brief Read some data from the stream.
596 *
597 * The function call will block until one or more bytes of data has been read successfully, or until an error
598 * occurs.
599 *
600 * @param buffers The buffers into which the data will be read.
601 * @return The number of bytes read. Returns 0 if an error occurred.
602 * @throws boost::system::system_error if error occurred
603 */
604 template <typename MutableBufferSequence>
605 std::size_t read_some(const MutableBufferSequence& buffers) {
606 boost::system::error_code ec;
607 const auto n = read_some(buffers, ec);
608 boost::asio::detail::throw_error(ec, "read_some");
609 return n;
610 }
611
612 /**
613 * @brief Write some data to the stream.
614 *
615 * The function call will block until one or more bytes of data has been written successfully, or until an error
616 * occurs.
617 *
618 * @param buffers The data to be written.
619 * @param ec Set to indicate what error occurred, if any.
620 * @return The number of bytes processed from the input buffers.
621 */
622 template <typename ConstBufferSequence>
623 std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
624 tls_encrypt(buffers, ec);
625 send_pending_encrypted_data(ec);
626 return !ec ? boost::asio::buffer_size(buffers) : 0;
627 }
628
629 /**
630 * @brief Write some data to the stream.
631 *
632 * The function call will block until one or more bytes of data has been written successfully, or until an error
633 * occurs.
634 *
635 * @param buffers The data to be written.
636 * @return The number of bytes written.
637 * @throws boost::system::system_error if error occurred
638 */
639 template <typename ConstBufferSequence>
640 std::size_t write_some(const ConstBufferSequence& buffers) {
641 boost::system::error_code ec;
642 const auto n = write_some(buffers, ec);
643 boost::asio::detail::throw_error(ec, "write_some");
644 return n;
645 }
646
647 /**
648 * @brief Start an asynchronous write. The function call always returns immediately.
649 *
650 * @param buffers The data to be written.
651 * @param completion_token The completion handler to be called when the write operation completes. Copies of the
652 * handler will be made as required. The completion signature of the handler must be:
653 * void(boost::system::error_code, std::size_t).
654 */
655 template <typename ConstBufferSequence,
656 detail::byte_size_completion_token CompletionToken = default_completion_token>
657 auto async_write_some(const ConstBufferSequence& buffers,
658 // NOLINTNEXTLINE(*-missing-std-forward)
659 CompletionToken&& completion_token = default_completion_token{}) {
660 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
661 [this](auto&& completion_handler, const auto& bufs) {
662 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
663
664 boost::system::error_code ec;
665 tls_encrypt(bufs, ec);
666
667 if(ec) {
668 // we cannot be sure how many bytes were committed here so clear the send_buffer and let the
669 // AsyncWriteOperation call the handler with the error_code set
670 m_core->send_buffer().consume(m_core->send_buffer().size());
671 }
672
673 const detail::AsyncWriteOperation<completion_handler_t, Stream> op{
674 std::forward<completion_handler_t>(completion_handler),
675 *this,
676 ec ? 0 : boost::asio::buffer_size(bufs),
677 ec};
678 },
679 completion_token,
680 buffers);
681 }
682
683 /**
684 * @brief Start an asynchronous read. The function call always returns immediately.
685 *
686 * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as
687 * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee
688 * that they remain valid until the handler is called.
689 * @param completion_token The completion handler to be called when the read operation completes. The completion
690 * signature of the handler must be: void(boost::system::error_code, std::size_t).
691 */
692 template <typename MutableBufferSequence,
693 detail::byte_size_completion_token CompletionToken = default_completion_token>
694 auto async_read_some(const MutableBufferSequence& buffers,
695 // NOLINTNEXTLINE(*-missing-std-forward)
696 CompletionToken&& completion_token = default_completion_token{}) {
697 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
698 [this](auto&& completion_handler, const auto& bufs) {
699 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
700
701 const detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
702 std::forward<completion_handler_t>(completion_handler), *this, bufs};
703 },
704 completion_token,
705 buffers);
706 }
707
708 //! @}
709
710 //! @brief Indicates whether a close_notify alert has been received from the peer.
711 //!
712 //! Note that we cannot m_core.is_closed_for_reading() because this wants to
713 //! explicitly check that the peer sent close_notify.
714 bool shutdown_received() const { return m_core->shutdown_received(); }
715
716 protected:
717 template <class H, class S, class M, class A>
718 friend class detail::AsyncReadOperation;
719 template <class H, class S, class A>
720 friend class detail::AsyncWriteOperation;
721 template <class H, class S, class A>
722 friend class detail::AsyncHandshakeOperation;
723
724 const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
725
726 boost::asio::const_buffer send_buffer() const { return m_core->send_buffer().data(); }
727
728 //! @brief Check if decrypted data is available in the receive buffer
729 bool has_received_data() const { return m_core->receive_buffer().size() > 0; }
730
731 //! @brief Copy decrypted data into the user-provided buffer
732 template <typename MutableBufferSequence>
733 std::size_t copy_received_data(MutableBufferSequence buffers) {
734 // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the CallbacksT with
735 // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record
736 // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller
737 // than the decrypted record, so this optimization might not be worth the additional complexity.
738 const auto copiedBytes = boost::asio::buffer_copy(buffers, m_core->receive_buffer().data());
739 m_core->receive_buffer().consume(copiedBytes);
740 return copiedBytes;
741 }
742
743 //! @brief Check if encrypted data is available in the send buffer
744 bool has_data_to_send() const { return m_core->send_buffer().size() > 0; }
745
746 //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer
747 void consume_send_buffer(std::size_t bytesConsumed) { m_core->send_buffer().consume(bytesConsumed); }
748
749 /**
750 * @brief Create the native handle.
751 *
752 * Depending on the desired connection side, this function will create a TLS::Client or a
753 * TLS::Server.
754 *
755 * @param side The desired connection side (client or server)
756 * @param ec Set to indicate what error occurred, if any.
757 */
758 void setup_native_handle(Connection_Side side, boost::system::error_code& ec) {
759 // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has
760 // been specified. This allows mocking the native_handle in test code.
761 if constexpr(std::is_same_v<ChannelT, Channel>) {
762 if(m_native_handle != nullptr) {
763 throw Botan::Invalid_State("ASIO native handle unexpectedly set");
764 }
765
766 try_with_error_code(
767 [&] {
768 if(side == Connection_Side::Client) {
769 m_native_handle = std::unique_ptr<Client>(
770 new Client(m_core,
771 m_context->m_session_manager,
772 m_context->m_credentials_manager,
773 m_context->m_policy,
774 m_context->m_rng,
775 m_context->m_server_info,
776 m_context->m_policy->latest_supported_version(false /* no DTLS */),
777 m_context->m_app_protocols));
778 } else {
779 m_native_handle = std::unique_ptr<Server>(new Server(m_core,
780 m_context->m_session_manager,
781 m_context->m_credentials_manager,
782 m_context->m_policy,
783 m_context->m_rng,
784 false /* no DTLS */));
785 }
786 },
787 ec);
788 }
789 }
790
791 /**
792 * The `Stream` has to distinguish from network-related issues (that are
793 * reported immediately) from TLS protocol errors, that must be retained
794 * and emitted once all legal application traffic received before is
795 * pushed to the downstream application.
796 *
797 * See also `process_encrypted_data()` and `StreamCallbacks::tls_alert()`
798 * where those TLS protocol errors are detected and retained for eventual
799 * handling in this method.
800 *
801 * See also https://github.com/randombit/botan/pull/3801 for a detailed
802 * description of the ASIO stream's state management.
803 *
804 * @param ec this error code is set if we previously detected a TLS
805 * protocol error.
806 */
807 void handle_tls_protocol_errors(boost::system::error_code& ec) {
808 if(ec) {
809 return;
810 }
811
812 // If we had raised an error while processing TLS records received from
813 // the peer, we expose that error here.
814 //
815 // See also `process_encrypted_data()`.
816 else if(auto error = error_from_us()) {
817 ec = error;
818 }
819
820 // If we had received a TLS alert from the peer, we expose that error
821 // here. See also `StreamCallbacks::tls_alert()` where such alerts
822 // would be detected and retained initially.
823 //
824 // Note that a close_notify is always a legal way for the peer to end a
825 // TLS session. When received during the handshake it typically means
826 // that the peer wanted to cancel the handshake for some reason not
827 // related to the TLS protocol.
828 else if(auto alert = alert_from_peer()) {
829 if(alert->type() == AlertType::CloseNotify) {
830 ec = boost::asio::error::eof;
831 } else {
832 ec = alert->type();
833 }
834 }
835 }
836
837 /**
838 * Reads TLS record data from the peer and forwards it to the native
839 * handle for processing. Note that @p ec will reflect network errors
840 * only. Any detected or received TLS protocol errors will be retained and
841 * must be handled by the downstream operation in due time by invoking
842 * `handle_tls_protocol_errors()`.
843 *
844 * @param ec this error code might be populated with network-related errors
845 */
846 void read_and_process_encrypted_data_from_peer(boost::system::error_code& ec) {
847 if(ec) {
848 return;
849 }
850
851 // If we have received application data in a previous invocation, this
852 // data needs to be passed to the application first. Otherwise, it
853 // might get overwritten.
854 if(has_received_data()) {
855 throw Botan::Invalid_State("ASIO receive buffer not empty");
856 }
857
858 if(error_from_us() || alert_from_peer()) {
859 throw Botan::Invalid_State("ASIO TLS session no longer healthy");
860 }
861
862 // If there's no existing error condition, read and process data from
863 // the peer and report any sort of network error. TLS related errors do
864 // not immediately cause an abort, they are checked in the invocation
865 // via `error_from_us()`.
866 const boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
867 if(!ec) {
868 process_encrypted_data(read_buffer);
869 } else if(ec == boost::asio::error::eof) {
870 ec = StreamError::StreamTruncated;
871 }
872 }
873
874 /** @brief Synchronously write encrypted data from the send buffer to the next layer.
875 *
876 * If this function is called with an error code other than 'Success', it will do nothing and return 0.
877 *
878 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
879 * has closed the connection but did not properly shut down the SSL connection.
880 * @return The number of bytes written.
881 */
882 size_t send_pending_encrypted_data(boost::system::error_code& ec) {
883 if(ec) {
884 return 0;
885 }
886
887 auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
888 consume_send_buffer(writtenBytes);
889
890 if(ec == boost::asio::error::eof && !shutdown_received()) {
891 // transport layer was closed by peer without receiving 'close_notify'
892 ec = StreamError::StreamTruncated;
893 }
894
895 return writtenBytes;
896 }
897
898 /**
899 * @brief Pass plaintext data to the native handle for processing.
900 *
901 * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback.
902 */
903 template <typename ConstBufferSequence>
904 void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
905 // TODO: Once the TLS::Channel can deal with buffer sequences we can
906 // save this copy. Until then we optimize for the smallest amount
907 // of TLS records and flatten the boost buffer sequence.
908 std::vector<uint8_t> copy_buffer;
909 auto unpack = [&copy_buffer](const auto& bufs) -> std::span<const uint8_t> {
910 const auto buffers_in_sequence =
911 std::distance(boost::asio::buffer_sequence_begin(bufs), boost::asio::buffer_sequence_end(bufs));
912
913 if(buffers_in_sequence == 0) {
914 return {};
915 } else if(buffers_in_sequence == 1) {
916 const boost::asio::const_buffer buf = *boost::asio::buffer_sequence_begin(bufs);
917 return {static_cast<const uint8_t*>(buf.data()), buf.size()};
918 } else {
919 copy_buffer.resize(boost::asio::buffer_size(bufs));
920 boost::asio::buffer_copy(boost::asio::buffer(copy_buffer), bufs);
921 return copy_buffer;
922 }
923 };
924
925 // NOTE: This is not asynchronous: it encrypts the data synchronously.
926 // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core,
927 // but is not actually written to the wire, yet.
928 try_with_error_code([&] { native_handle()->send(unpack(buffers)); }, ec);
929 }
930
931 /**
932 * Pass encrypted data received from the peer to the native handle for
933 * processing. If the @p read_buffer contains coalesced TLS records, this
934 * might result in multiple TLS protocol state changes.
935 *
936 * To allow the ASIO stream wrapper to disentangle those state changes
937 * properly, any TLS protocol errors are retained and must be handled by
938 * calling `handle_tls_protocol_errors()` in due time.
939 *
940 * @param read_buffer Input buffer containing the encrypted data.
941 */
942 void process_encrypted_data(const boost::asio::const_buffer& read_buffer) {
943 if(alert_from_peer() || error_from_us()) {
944 throw Botan::Invalid_State("ASIO TLS session no longer healthy");
945 }
946
947 // If the local TLS implementation generates an alert, we are notified
948 // with an exception that is caught in try_with_error_code(). The error
949 // code is retained and not handled directly. Stream operations will
950 // have to invoke `handle_tls_protocol_errors()` to handle them later.
951 try_with_error_code(
952 [&] {
953 native_handle()->received_data({static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()});
954 },
955 m_ec_from_last_read);
956 }
957
958 //! @brief Catch exceptions and set an error_code
959 template <typename Fun>
960 void try_with_error_code(Fun f, boost::system::error_code& ec) {
961 try {
962 f();
963 } catch(const TLS_Exception& e) {
964 ec = e.type();
965 } catch(const Exception& e) {
966 ec = e.error_type();
967 } catch(const std::exception&) {
968 ec = ErrorType::Unknown;
969 }
970 }
971
972 private:
973 /**
974 * Returns any alert previously received from the peer. This may include
975 * close_notify. Once the peer has sent any alert, no more data must be
976 * read from the stream.
977 */
978 std::optional<Alert> alert_from_peer() const { return m_core->alert_from_peer(); }
979
980 /**
981 * Returns any error code produced by the local TLS implementation. This
982 * will _not_ include close_notify. Once our TLS stack has reported an
983 * error, no more data must be written to the stream. The peer will receive
984 * the error as a TLS alert.
985 */
986 boost::system::error_code error_from_us() const { return m_ec_from_last_read; }
987
988 protected:
989 std::shared_ptr<Context> m_context; // NOLINT(*-non-private-member-variable*)
990 StreamLayer m_nextLayer; // NOLINT(*-non-private-member-variable*)
991
992 std::shared_ptr<StreamCallbacks> m_core; // NOLINT(*-non-private-member-variable*)
993 std::unique_ptr<ChannelT> m_native_handle; // NOLINT(*-non-private-member-variable*)
994 boost::system::error_code m_ec_from_last_read; // NOLINT(*-non-private-member-variable*)
995
996 // Buffer space used to read input intended for the core
997 std::vector<uint8_t> m_input_buffer_space; // NOLINT(*-non-private-member-variable*)
998 const boost::asio::mutable_buffer m_input_buffer; // NOLINT(*-non-private-member-variable*)
999};
1000
1001// deduction guides for convenient construction from an existing
1002// underlying transport stream T
1003template <typename T>
1004Stream(Server_Information, T) -> Stream<T>;
1005template <typename T>
1006Stream(std::shared_ptr<Context>, std::shared_ptr<StreamCallbacks>, T) -> Stream<T>;
1007template <typename T>
1008Stream(std::shared_ptr<Context>, T) -> Stream<T>;
1009template <typename T>
1010Stream(T, std::shared_ptr<Context>) -> Stream<T>;
1011
1012} // namespace Botan::TLS
1013
1014#endif
1015#endif // BOTAN_ASIO_STREAM_H_
constexpr void unpack(Polynomial< PolyTrait, D > &p, ByteSourceT &byte_source, UnmapFnT unmap)
@ MAX_CIPHERTEXT_SIZE
Definition tls_magic.h:34