Botan 3.7.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 <algorithm>
35 #include <memory>
36 #include <type_traits>
37
38namespace Botan::TLS {
39
40template <class SL, class C>
41class 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 */
58class StreamCallbacks : public Callbacks {
59 public:
60 StreamCallbacks() {}
61
62 void tls_emit_data(std::span<const uint8_t> data) final {
63 m_send_buffer.commit(boost::asio::buffer_copy(m_send_buffer.prepare(data.size()),
64 boost::asio::buffer(data.data(), data.size())));
65 }
66
67 void tls_record_received(uint64_t, std::span<const uint8_t> data) final {
68 m_receive_buffer.commit(boost::asio::buffer_copy(m_receive_buffer.prepare(data.size()),
69 boost::asio::const_buffer(data.data(), data.size())));
70 }
71
72 bool tls_peer_closed_connection() final {
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;
81 }
82
83 /**
84 * @param alert a TLS alert sent by the peer
85 */
86 void tls_alert(TLS::Alert alert) final {
87 if(alert.is_fatal() || alert.type() == TLS::AlertType::CloseNotify) {
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;
93 }
94 }
95
96 void tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain,
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();
103
104 if(ctx && ctx->has_verify_callback()) {
105 ctx->get_verify_callback()(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
106 } else {
107 Callbacks::tls_verify_cert_chain(cert_chain, ocsp_responses, trusted_roots, usage, hostname, policy);
108 }
109 }
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); }
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; }
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; }
125
126 const boost::beast::flat_buffer& receive_buffer() const { return m_receive_buffer; }
127
128 bool shutdown_received() const {
129 return m_alert_from_peer && m_alert_from_peer->type() == AlertType::CloseNotify;
130 }
131
132 std::optional<Alert> alert_from_peer() const { return m_alert_from_peer; }
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
142namespace detail {
143
144template <typename T>
145concept basic_completion_token = boost::asio::completion_token_for<T, void(boost::system::error_code)>;
146
147template <typename T>
148concept 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 */
158template <class StreamLayer, class ChannelT = Channel>
159class 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) :
182 m_context(std::move(context)),
183 m_nextLayer(std::forward<Args>(args)...),
184 m_core(std::move(callbacks)),
185 m_input_buffer_space(MAX_CIPHERTEXT_SIZE, '\0'),
186 m_input_buffer(m_input_buffer_space.data(), m_input_buffer_space.size()) {
187 m_core->set_context(m_context);
188 }
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) :
198 Stream(std::move(context), std::make_shared<StreamCallbacks>(), std::forward<Args>(args)...) {}
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,
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)) {}
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;
234
235 Stream(Stream&& other) = default;
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; }
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(); }
260
261 using native_handle_type = typename std::add_pointer<ChannelT>::type;
262
263 native_handle_type native_handle() {
264 BOTAN_STATE_CHECK(m_native_handle != nullptr);
265 return m_native_handle.get();
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 /**
297 * Not Implemented.
298 * @param depth the desired verification depth
299 * @throws Not_Implemented todo
300 */
301 void set_verify_depth(int depth) {
302 BOTAN_UNUSED(depth);
303 throw Not_Implemented("set_verify_depth is not implemented");
304 }
305
306 /**
307 * Not Implemented.
308 * @param depth the desired verification depth
309 * @param ec Will be set to `Botan::ErrorType::NotImplemented`
310 */
311 void set_verify_depth(int depth, boost::system::error_code& ec) {
312 BOTAN_UNUSED(depth);
313 ec = ErrorType::NotImplemented;
314 }
315
316 /**
317 * Not Implemented.
318 * @param v the desired verify mode
319 * @throws Not_Implemented todo
320 */
321 template <typename verify_mode>
322 void set_verify_mode(verify_mode v) {
323 BOTAN_UNUSED(v);
324 throw Not_Implemented("set_verify_mode is not implemented");
325 }
326
327 /**
328 * Not Implemented.
329 * @param v the desired verify mode
330 * @param ec Will be set to `Botan::ErrorType::NotImplemented`
331 */
332 template <typename verify_mode>
333 void set_verify_mode(verify_mode v, boost::system::error_code& ec) {
334 BOTAN_UNUSED(v);
335 ec = ErrorType::NotImplemented;
336 }
337
338 //! @}
339 //! \name handshake methods
340 //! @{
341
342 /**
343 * @brief Performs SSL handshaking.
344 *
345 * The function call will block until handshaking is complete or an error occurs.
346 *
347 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
348 * @throws boost::system::system_error if error occured
349 */
350 void handshake(Connection_Side side) {
351 boost::system::error_code ec;
352 handshake(side, ec);
353 boost::asio::detail::throw_error(ec, "handshake");
354 }
355
356 /**
357 * @brief Performs SSL handshaking.
358 *
359 * The function call will block until handshaking is complete or an error occurs.
360 *
361 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
362 * @param ec Set to indicate what error occurred, if any.
363 */
364 void handshake(Connection_Side side, boost::system::error_code& ec) {
365 setup_native_handle(side, ec);
366
367 // We write to the socket if we have data to send and read from it
368 // otherwise, until either some error occured or we have successfully
369 // performed the handshake.
370 while(!ec) {
371 // Send pending data to the peer and abort the handshake if that
372 // fails with a network error. We do that first, to allow sending
373 // any final message before reporting the handshake as "finished".
374 if(has_data_to_send()) {
375 send_pending_encrypted_data(ec);
376 }
377
378 // Once the underlying TLS implementation reports a complete and
379 // successful handshake we're done.
380 if(native_handle()->is_handshake_complete()) {
381 return;
382 }
383
384 // Handle and report any TLS protocol errors that might have
385 // surfaced in a previous iteration. By postponing their handling we
386 // allow the stream to send a respective TLS alert to the peer before
387 // aborting the handshake.
388 handle_tls_protocol_errors(ec);
389
390 // If we don't have any encrypted data to send we attempt to read
391 // more data from the peer. This reports network errors immediately.
392 // TLS protocol errors result in an internal state change which is
393 // handled by `handle_tls_protocol_errors()` in the next iteration.
394 read_and_process_encrypted_data_from_peer(ec);
395 }
396
397 BOTAN_ASSERT_NOMSG(ec.failed());
398 }
399
400 /**
401 * @brief Starts an asynchronous SSL handshake.
402 *
403 * This function call always returns immediately.
404 *
405 * @param side The type of handshaking to be performed, i.e. as a client or as a server.
406 * @param completion_token The completion handler to be called when the handshake operation completes.
407 * The completion signature of the handler must be: void(boost::system::error_code).
408 */
409 template <detail::basic_completion_token CompletionToken = default_completion_token>
410 auto async_handshake(Botan::TLS::Connection_Side side,
411 CompletionToken&& completion_token = default_completion_token{}) {
412 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
413 [this](auto&& completion_handler, TLS::Connection_Side connection_side) {
414 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
415
416 boost::system::error_code ec;
417 setup_native_handle(connection_side, ec);
418
419 detail::AsyncHandshakeOperation<completion_handler_t, Stream> op{
420 std::forward<completion_handler_t>(completion_handler), *this, ec};
421 },
422 completion_token,
423 side);
424 }
425
426 /**
427 * Not Implemented.
428 * @throws Not_Implemented todo
429 */
430 template <typename ConstBufferSequence, detail::basic_completion_token BufferedHandshakeHandler>
431 auto async_handshake(Connection_Side side,
432 const ConstBufferSequence& buffers,
433 BufferedHandshakeHandler&& handler) {
434 BOTAN_UNUSED(side, buffers, handler);
435 throw Not_Implemented("buffered async handshake is not implemented");
436 }
437
438 //! @}
439 //! \name shutdown methods
440 //! @{
441
442 /**
443 * @brief Shut down SSL on the stream.
444 *
445 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
446 * or an error occurs. Note that this will not close the lowest layer.
447 *
448 * Note that this can be used in reaction of a received shutdown alert from the peer.
449 *
450 * @param ec Set to indicate what error occured, if any.
451 */
452 void shutdown(boost::system::error_code& ec) {
453 try_with_error_code([&] { native_handle()->close(); }, ec);
454
455 send_pending_encrypted_data(ec);
456 }
457
458 /**
459 * @brief Shut down SSL on the stream.
460 *
461 * This function is used to shut down SSL on the stream. The function call will block until SSL has been shut down
462 * or an error occurs. Note that this will not close the lowest layer.
463 *
464 * Note that this can be used in reaction of a received shutdown alert from the peer.
465 *
466 * @throws boost::system::system_error if error occured
467 */
468 void shutdown() {
469 boost::system::error_code ec;
470 shutdown(ec);
471 boost::asio::detail::throw_error(ec, "shutdown");
472 }
473
474 private:
475 /**
476 * @brief Internal wrapper type to adapt the expected signature of `async_shutdown` to the completion handler
477 * signature of `AsyncWriteOperation`.
478 *
479 * This is boilerplate to ignore the `size_t` parameter that is passed to the completion handler of
480 * `AsyncWriteOperation`. Note that it needs to retain the wrapped handler's executor.
481 */
482 template <typename Handler, typename Executor>
483 struct Wrapper {
484 void operator()(boost::system::error_code ec, std::size_t) { handler(ec); }
485
486 using executor_type = boost::asio::associated_executor_t<Handler, Executor>;
487
488 executor_type get_executor() const noexcept {
489 return boost::asio::get_associated_executor(handler, io_executor);
490 }
491
492 using allocator_type = boost::asio::associated_allocator_t<Handler>;
493
494 allocator_type get_allocator() const noexcept { return boost::asio::get_associated_allocator(handler); }
495
496 Handler handler;
497 Executor io_executor;
498 };
499
500 public:
501 /**
502 * @brief Asynchronously shut down SSL on the stream.
503 *
504 * This function call always returns immediately.
505 *
506 * Note that this can be used in reaction of a received shutdown alert from the peer.
507 *
508 * @param completion_token The completion handler to be called when the shutdown operation completes.
509 * The completion signature of the handler must be: void(boost::system::error_code).
510 */
511 template <detail::basic_completion_token CompletionToken = default_completion_token>
512 auto async_shutdown(CompletionToken&& completion_token = default_completion_token{}) {
513 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
514 [this](auto&& completion_handler) {
515 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
516
517 boost::system::error_code ec;
518 try_with_error_code([&] { native_handle()->close(); }, ec);
519
520 using write_handler_t = Wrapper<completion_handler_t, typename Stream::executor_type>;
521
522 TLS::detail::AsyncWriteOperation<write_handler_t, Stream> op{
523 write_handler_t{std::forward<completion_handler_t>(completion_handler), get_executor()},
524 *this,
525 boost::asio::buffer_size(send_buffer()),
526 ec};
527 },
528 completion_token);
529 }
530
531 //! @}
532 //! \name I/O methods
533 //! @{
534
535 /**
536 * @brief Read some data from the stream.
537 *
538 * The function call will block until one or more bytes of data has been read successfully, or until an error
539 * occurs.
540 *
541 * @param buffers The buffers into which the data will be read.
542 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
543 * has closed the connection but did not properly shut down the SSL connection.
544 * @return The number of bytes read. Returns 0 if an error occurred.
545 */
546 template <typename MutableBufferSequence>
547 std::size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) {
548 // We read from the socket until either some error occured or we have
549 // decrypted at least one byte of application data.
550 while(!ec) {
551 // Some previous invocation of process_encrypted_data() generated
552 // application data in the output buffer that can now be returned.
553 if(has_received_data()) {
554 return copy_received_data(buffers);
555 }
556
557 // Handle and report any TLS protocol errors (including a
558 // close_notify) that might have surfaced in a previous iteration
559 // (in `read_and_process_encrypted_data_from_peer()`). This makes
560 // sure that all received application data was handed out to the
561 // caller before reporting an error (e.g. EOF at the end of the
562 // stream).
563 handle_tls_protocol_errors(ec);
564
565 // If we don't have any plaintext application data, yet, we attempt
566 // to read more data from the peer. This reports network errors
567 // immediately. TLS protocol errors result in an internal state
568 // change which is handled by `handle_tls_protocol_errors()` in the
569 // next iteration.
570 read_and_process_encrypted_data_from_peer(ec);
571 }
572
573 BOTAN_ASSERT_NOMSG(ec.failed());
574 return 0;
575 }
576
577 /**
578 * @brief Read some data from the stream.
579 *
580 * The function call will block until one or more bytes of data has been read successfully, or until an error
581 * occurs.
582 *
583 * @param buffers The buffers into which the data will be read.
584 * @return The number of bytes read. Returns 0 if an error occurred.
585 * @throws boost::system::system_error if error occured
586 */
587 template <typename MutableBufferSequence>
588 std::size_t read_some(const MutableBufferSequence& buffers) {
589 boost::system::error_code ec;
590 const auto n = read_some(buffers, ec);
591 boost::asio::detail::throw_error(ec, "read_some");
592 return n;
593 }
594
595 /**
596 * @brief Write some data to the stream.
597 *
598 * The function call will block until one or more bytes of data has been written successfully, or until an error
599 * occurs.
600 *
601 * @param buffers The data to be written.
602 * @param ec Set to indicate what error occurred, if any.
603 * @return The number of bytes processed from the input buffers.
604 */
605 template <typename ConstBufferSequence>
606 std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
607 tls_encrypt(buffers, ec);
608 send_pending_encrypted_data(ec);
609 return !ec ? boost::asio::buffer_size(buffers) : 0;
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 * @return The number of bytes written.
620 * @throws boost::system::system_error if error occured
621 */
622 template <typename ConstBufferSequence>
623 std::size_t write_some(const ConstBufferSequence& buffers) {
624 boost::system::error_code ec;
625 const auto n = write_some(buffers, ec);
626 boost::asio::detail::throw_error(ec, "write_some");
627 return n;
628 }
629
630 /**
631 * @brief Start an asynchronous write. The function call always returns immediately.
632 *
633 * @param buffers The data to be written.
634 * @param completion_token The completion handler to be called when the write operation completes. Copies of the
635 * handler will be made as required. The completion signature of the handler must be:
636 * void(boost::system::error_code, std::size_t).
637 */
638 template <typename ConstBufferSequence,
639 detail::byte_size_completion_token CompletionToken = default_completion_token>
640 auto async_write_some(const ConstBufferSequence& buffers,
641 CompletionToken&& completion_token = default_completion_token{}) {
642 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
643 [this](auto&& completion_handler, const auto& bufs) {
644 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
645
646 boost::system::error_code ec;
647 tls_encrypt(bufs, ec);
648
649 if(ec) {
650 // we cannot be sure how many bytes were committed here so clear the send_buffer and let the
651 // AsyncWriteOperation call the handler with the error_code set
652 m_core->send_buffer().consume(m_core->send_buffer().size());
653 }
654
655 detail::AsyncWriteOperation<completion_handler_t, Stream> op{
656 std::forward<completion_handler_t>(completion_handler),
657 *this,
658 ec ? 0 : boost::asio::buffer_size(bufs),
659 ec};
660 },
661 completion_token,
662 buffers);
663 }
664
665 /**
666 * @brief Start an asynchronous read. The function call always returns immediately.
667 *
668 * @param buffers The buffers into which the data will be read. Although the buffers object may be copied as
669 * necessary, ownership of the underlying buffers is retained by the caller, which must guarantee
670 * that they remain valid until the handler is called.
671 * @param completion_token The completion handler to be called when the read operation completes. The completion
672 * signature of the handler must be: void(boost::system::error_code, std::size_t).
673 */
674 template <typename MutableBufferSequence,
675 detail::byte_size_completion_token CompletionToken = default_completion_token>
676 auto async_read_some(const MutableBufferSequence& buffers,
677 CompletionToken&& completion_token = default_completion_token{}) {
678 return boost::asio::async_initiate<CompletionToken, void(boost::system::error_code, std::size_t)>(
679 [this](auto&& completion_handler, const auto& bufs) {
680 using completion_handler_t = std::decay_t<decltype(completion_handler)>;
681
682 detail::AsyncReadOperation<completion_handler_t, Stream, MutableBufferSequence> op{
683 std::forward<completion_handler_t>(completion_handler), *this, bufs};
684 },
685 completion_token,
686 buffers);
687 }
688
689 //! @}
690
691 //! @brief Indicates whether a close_notify alert has been received from the peer.
692 //!
693 //! Note that we cannot m_core.is_closed_for_reading() because this wants to
694 //! explicitly check that the peer sent close_notify.
695 bool shutdown_received() const { return m_core->shutdown_received(); }
696
697 protected:
698 template <class H, class S, class M, class A>
699 friend class detail::AsyncReadOperation;
700 template <class H, class S, class A>
701 friend class detail::AsyncWriteOperation;
702 template <class H, class S, class A>
703 friend class detail::AsyncHandshakeOperation;
704
705 const boost::asio::mutable_buffer& input_buffer() { return m_input_buffer; }
706
707 boost::asio::const_buffer send_buffer() const { return m_core->send_buffer().data(); }
708
709 //! @brief Check if decrypted data is available in the receive buffer
710 bool has_received_data() const { return m_core->receive_buffer().size() > 0; }
711
712 //! @brief Copy decrypted data into the user-provided buffer
713 template <typename MutableBufferSequence>
714 std::size_t copy_received_data(MutableBufferSequence buffers) {
715 // Note: It would be nice to avoid this buffer copy. This could be achieved by equipping the CallbacksT with
716 // the user's desired target buffer once a read is started, and reading directly into that buffer in tls_record
717 // received. However, we need to deal with the case that the receive buffer provided by the caller is smaller
718 // than the decrypted record, so this optimization might not be worth the additional complexity.
719 const auto copiedBytes = boost::asio::buffer_copy(buffers, m_core->receive_buffer().data());
720 m_core->receive_buffer().consume(copiedBytes);
721 return copiedBytes;
722 }
723
724 //! @brief Check if encrypted data is available in the send buffer
725 bool has_data_to_send() const { return m_core->send_buffer().size() > 0; }
726
727 //! @brief Mark bytes in the send buffer as consumed, removing them from the buffer
728 void consume_send_buffer(std::size_t bytesConsumed) { m_core->send_buffer().consume(bytesConsumed); }
729
730 /**
731 * @brief Create the native handle.
732 *
733 * Depending on the desired connection side, this function will create a TLS::Client or a
734 * TLS::Server.
735 *
736 * @param side The desired connection side (client or server)
737 * @param ec Set to indicate what error occurred, if any.
738 */
739 void setup_native_handle(Connection_Side side, boost::system::error_code& ec) {
740 // Do not attempt to instantiate the native_handle when a custom (mocked) channel type template parameter has
741 // been specified. This allows mocking the native_handle in test code.
742 if constexpr(std::is_same<ChannelT, Channel>::value) {
743 BOTAN_STATE_CHECK(m_native_handle == nullptr);
744
745 try_with_error_code(
746 [&] {
747 if(side == Connection_Side::Client) {
748 m_native_handle = std::unique_ptr<Client>(
749 new Client(m_core,
750 m_context->m_session_manager,
751 m_context->m_credentials_manager,
752 m_context->m_policy,
753 m_context->m_rng,
754 m_context->m_server_info,
755 m_context->m_policy->latest_supported_version(false /* no DTLS */)));
756 } else {
757 m_native_handle = std::unique_ptr<Server>(new Server(m_core,
758 m_context->m_session_manager,
759 m_context->m_credentials_manager,
760 m_context->m_policy,
761 m_context->m_rng,
762 false /* no DTLS */));
763 }
764 },
765 ec);
766 }
767 }
768
769 /**
770 * The `Stream` has to distinguish from network-related issues (that are
771 * reported immediately) from TLS protocol errors, that must be retained
772 * and emitted once all legal application traffic received before is
773 * pushed to the downstream application.
774 *
775 * See also `process_encrypted_data()` and `StreamCallbacks::tls_alert()`
776 * where those TLS protocol errors are detected and retained for eventual
777 * handling in this method.
778 *
779 * See also https://github.com/randombit/botan/pull/3801 for a detailed
780 * description of the ASIO stream's state management.
781 *
782 * @param ec this error code is set if we previously detected a TLS
783 * protocol error.
784 */
785 void handle_tls_protocol_errors(boost::system::error_code& ec) {
786 if(ec) {
787 return;
788 }
789
790 // If we had raised an error while processing TLS records received from
791 // the peer, we expose that error here.
792 //
793 // See also `process_encrypted_data()`.
794 else if(auto error = error_from_us()) {
795 ec = error;
796 }
797
798 // If we had received a TLS alert from the peer, we expose that error
799 // here. See also `StreamCallbacks::tls_alert()` where such alerts
800 // would be detected and retained initially.
801 //
802 // Note that a close_notify is always a legal way for the peer to end a
803 // TLS session. When received during the handshake it typically means
804 // that the peer wanted to cancel the handshake for some reason not
805 // related to the TLS protocol.
806 else if(auto alert = alert_from_peer()) {
807 if(alert->type() == AlertType::CloseNotify) {
808 ec = boost::asio::error::eof;
809 } else {
810 ec = alert->type();
811 }
812 }
813 }
814
815 /**
816 * Reads TLS record data from the peer and forwards it to the native
817 * handle for processing. Note that @p ec will reflect network errors
818 * only. Any detected or received TLS protocol errors will be retained and
819 * must be handled by the downstream operation in due time by invoking
820 * `handle_tls_protocol_errors()`.
821 *
822 * @param ec this error code might be populated with network-related errors
823 */
824 void read_and_process_encrypted_data_from_peer(boost::system::error_code& ec) {
825 if(ec) {
826 return;
827 }
828
829 // If we have received application data in a previous invocation, this
830 // data needs to be passed to the application first. Otherwise, it
831 // might get overwritten.
832 BOTAN_ASSERT(!has_received_data(), "receive buffer is empty");
833 BOTAN_ASSERT(!error_from_us() && !alert_from_peer(), "TLS session is healthy");
834
835 // If there's no existing error condition, read and process data from
836 // the peer and report any sort of network error. TLS related errors do
837 // not immediately cause an abort, they are checked in the invocation
838 // via `error_from_us()`.
839 boost::asio::const_buffer read_buffer{input_buffer().data(), m_nextLayer.read_some(input_buffer(), ec)};
840 if(!ec) {
841 process_encrypted_data(read_buffer);
842 } else if(ec == boost::asio::error::eof) {
843 ec = StreamError::StreamTruncated;
844 }
845 }
846
847 /** @brief Synchronously write encrypted data from the send buffer to the next layer.
848 *
849 * If this function is called with an error code other than 'Success', it will do nothing and return 0.
850 *
851 * @param ec Set to indicate what error occurred, if any. Specifically, StreamTruncated will be set if the peer
852 * has closed the connection but did not properly shut down the SSL connection.
853 * @return The number of bytes written.
854 */
855 size_t send_pending_encrypted_data(boost::system::error_code& ec) {
856 if(ec) {
857 return 0;
858 }
859
860 auto writtenBytes = boost::asio::write(m_nextLayer, send_buffer(), ec);
861 consume_send_buffer(writtenBytes);
862
863 if(ec == boost::asio::error::eof && !shutdown_received()) {
864 // transport layer was closed by peer without receiving 'close_notify'
865 ec = StreamError::StreamTruncated;
866 }
867
868 return writtenBytes;
869 }
870
871 /**
872 * @brief Pass plaintext data to the native handle for processing.
873 *
874 * The native handle will then create TLS records and hand them back to the Stream via the tls_emit_data callback.
875 */
876 template <typename ConstBufferSequence>
877 void tls_encrypt(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
878 // TODO: Once the TLS::Channel can deal with buffer sequences we can
879 // save this copy. Until then we optimize for the smallest amount
880 // of TLS records and flatten the boost buffer sequence.
881 std::vector<uint8_t> copy_buffer;
882 auto unpack = [&copy_buffer](const auto& bufs) -> std::span<const uint8_t> {
883 const auto buffers_in_sequence =
884 std::distance(boost::asio::buffer_sequence_begin(bufs), boost::asio::buffer_sequence_end(bufs));
885
886 if(buffers_in_sequence == 0) {
887 return {};
888 } else if(buffers_in_sequence == 1) {
889 const boost::asio::const_buffer buf = *boost::asio::buffer_sequence_begin(bufs);
890 return {static_cast<const uint8_t*>(buf.data()), buf.size()};
891 } else {
892 copy_buffer.resize(boost::asio::buffer_size(bufs));
893 boost::asio::buffer_copy(boost::asio::buffer(copy_buffer), bufs);
894 return copy_buffer;
895 }
896 };
897
898 // NOTE: This is not asynchronous: it encrypts the data synchronously.
899 // The data encrypted by native_handle()->send() is synchronously stored in the send_buffer of m_core,
900 // but is not actually written to the wire, yet.
901 try_with_error_code([&] { native_handle()->send(unpack(buffers)); }, ec);
902 }
903
904 /**
905 * Pass encrypted data received from the peer to the native handle for
906 * processing. If the @p read_buffer contains coalesced TLS records, this
907 * might result in multiple TLS protocol state changes.
908 *
909 * To allow the ASIO stream wrapper to disentangle those state changes
910 * properly, any TLS protocol errors are retained and must be handled by
911 * calling `handle_tls_protocol_errors()` in due time.
912 *
913 * @param read_buffer Input buffer containing the encrypted data.
914 */
915 void process_encrypted_data(const boost::asio::const_buffer& read_buffer) {
916 BOTAN_ASSERT(!alert_from_peer() && !error_from_us(),
917 "no one sent an alert before (no data allowed after that)");
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_
#define BOTAN_UNUSED
Definition assert.h:118
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:59
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:41
#define BOTAN_ASSERT(expr, assertion_made)
Definition assert.h:50
int(* final)(unsigned char *, CTX *)
FE_25519 T
Definition ge.cpp:34
constexpr void unpack(Polynomial< PolyTrait, D > &p, ByteSourceT &byte_source, UnmapFnT unmap)
@ MAX_CIPHERTEXT_SIZE
Definition tls_magic.h:33