Botan 2.13.0 Crypto and TLS for C++11

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

asio_async_ops.h
Go to the documentation of this file.
1 /*
2 * Helpers for TLS ASIO Stream
3 * (C) 2018-2019 Jack Lloyd
4 * 2018-2019 Hannes Rantzsch, Tim Oesterreich, Rene Meusel
5 *
6 * Botan is released under the Simplified BSD License (see license.txt)
7 */
8
9 #ifndef BOTAN_ASIO_ASYNC_OPS_H_
10 #define BOTAN_ASIO_ASYNC_OPS_H_
11
12 #include <botan/build.h>
13
14 #include <boost/version.hpp>
15 #if BOOST_VERSION >= 106600
16
17 #include <botan/asio_error.h>
18
19 // We need to define BOOST_ASIO_DISABLE_SERIAL_PORT before any asio imports. Otherwise asio will include <termios.h>,
20 // which interferes with Botan's amalgamation by defining macros like 'B0' and 'FF1'.
21 #define BOOST_ASIO_DISABLE_SERIAL_PORT
22 #include <boost/asio.hpp>
23 #include <boost/asio/yield.hpp>
24
25 namespace Botan {
26 namespace TLS {
27 namespace detail {
28
29 /**
30 * Base class for asynchronous stream operations.
31 *
32 * Asynchronous operations, used for example to implement an interface for boost::asio::async_read_some and
33 * boost::asio::async_write_some, are based on boost::asio::coroutines.
34 * Derived operations should implement a call operator and invoke it with the correct parameters upon construction. The
35 * call operator needs to make sure that the user-provided handler is not called directly. Typically, yield / reenter is
36 * used for this in the following fashion:
37 *
38 * ```
39 * void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
40 * {
41 * reenter(this)
42 * {
43 * // operation specific logic, repeatedly interacting with the stream_core and the next_layer (socket)
44 *
45 * // make sure intermediate initiating function is called
46 * if(!isContinuation)
47 * {
48 * yield next_layer.async_operation(empty_buffer, this);
49 * }
50 *
51 * // call the completion handler
52 * complete_now(error_code, bytes_transferred);
53 * }
54 * }
55 * ```
56 *
57 * Once the operation is completed and ready to call the completion handler it checks if an intermediate initiating
58 * function has been called using the `isContinuation` parameter. If not, it will call an asynchronous operation, such
59 * as `async_read_some`, with and empty buffer, set the object itself as the handler, and `yield`. As a result, the call
60 * operator will be invoked again, this time as a continuation, and will jump to the location where it yielded before
61 * using `reenter`. It is now safe to call the handler function via `complete_now`.
62 *
63 * \tparam Handler Type of the completion handler
64 * \tparam Executor1 Type of the asio executor (usually derived from the lower layer)
65 * \tparam Allocator Type of the allocator to be used
66 */
67 template <class Handler, class Executor1, class Allocator>
68 class AsyncBase : public boost::asio::coroutine
69 {
70 public:
71 using allocator_type = boost::asio::associated_allocator_t<Handler, Allocator>;
72 using executor_type = boost::asio::associated_executor_t<Handler, Executor1>;
73
74 allocator_type get_allocator() const noexcept
75 {
76 return boost::asio::get_associated_allocator(m_handler);
77 }
78
79 executor_type get_executor() const noexcept
80 {
81 return boost::asio::get_associated_executor(m_handler, m_work_guard_1.get_executor());
82 }
83
84 protected:
85 template <class HandlerT>
86 AsyncBase(HandlerT&& handler, const Executor1& executor)
87 : m_handler(std::forward<HandlerT>(handler))
88 , m_work_guard_1(executor)
89 {
90 }
91
92 /**
93 * Call the completion handler.
94 *
95 * This function should only be called after an intermediate initiating function has been called.
96 *
97 * @param args Arguments forwarded to the completion handler function.
98 */
99 template<class... Args>
100 void complete_now(Args&& ... args)
101 {
102 m_work_guard_1.reset();
103 m_handler(std::forward<Args>(args)...);
104 }
105
106 Handler m_handler;
107 boost::asio::executor_work_guard<Executor1> m_work_guard_1;
108 };
109
110 template <class Handler, class Stream, class MutableBufferSequence, class Allocator = std::allocator<void>>
111 class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
112 {
113 public:
114 /**
115 * Construct and invoke an AsyncReadOperation.
116 *
117 * @param handler Handler function to be called upon completion.
118 * @param stream The stream from which the data will be read
119 * @param buffers The buffers into which the data will be read.
120 * @param ec Optional error code; used to report an error to the handler function.
121 */
122 template <class HandlerT>
123 AsyncReadOperation(HandlerT&& handler,
124 Stream& stream,
125 const MutableBufferSequence& buffers,
126 const boost::system::error_code& ec = {})
127 : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
128 std::forward<HandlerT>(handler),
129 stream.get_executor())
130 , m_stream(stream)
131 , m_buffers(buffers)
132 , m_decodedBytes(0)
133 {
134 this->operator()(ec, std::size_t(0), false);
135 }
136
137 AsyncReadOperation(AsyncReadOperation&&) = default;
138
139 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
140 {
141 reenter(this)
142 {
143 if(bytes_transferred > 0 && !ec)
144 {
145 // We have received encrypted data from the network, now hand it to TLS::Channel for decryption.
146 boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred};
147 try
148 {
149 m_stream.native_handle()->received_data(
150 static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()
151 );
152 }
153 catch(const TLS_Exception& e)
154 {
155 ec = e.type();
156 }
157 catch(const Botan::Exception& e)
158 {
159 ec = e.error_type();
160 }
161 catch(...)
162 {
163 ec = Botan::ErrorType::Unknown;
164 }
165 }
166
167 if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0)
168 {
169 // The channel did not decrypt a complete record yet, we need more data from the socket.
170 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
171 return;
172 }
173
174 if(m_stream.has_received_data() && !ec)
175 {
176 // The channel has decrypted a TLS record, now copy it to the output buffers.
177 m_decodedBytes = m_stream.copy_received_data(m_buffers);
178 }
179
180 if(!isContinuation)
181 {
182 // Make sure the handler is not called without an intermediate initiating function.
183 // "Reading" into a zero-byte buffer will complete immediately.
184 m_ec = ec;
185 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
186 ec = m_ec;
187 }
188
189 this->complete_now(ec, m_decodedBytes);
190 }
191 }
192
193 private:
194 Stream& m_stream;
195 MutableBufferSequence m_buffers;
196 std::size_t m_decodedBytes;
197 boost::system::error_code m_ec;
198 };
199
200 template <typename Handler, class Stream, class Allocator = std::allocator<void>>
201 class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
202 {
203 public:
204 /**
205 * Construct and invoke an AsyncWriteOperation.
206 *
207 * @param handler Handler function to be called upon completion.
208 * @param stream The stream from which the data will be read
209 * @param plainBytesTransferred Number of bytes to be reported to the user-provided handler function as
210 * bytes_transferred. This needs to be provided since the amount of plaintext data
211 * consumed from the input buffer can differ from the amount of encrypted data written
212 * to the next layer.
213 * @param ec Optional error code; used to report an error to the handler function.
214 */
215 template <class HandlerT>
216 AsyncWriteOperation(HandlerT&& handler,
217 Stream& stream,
218 std::size_t plainBytesTransferred,
219 const boost::system::error_code& ec = {})
220 : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
221 std::forward<HandlerT>(handler),
222 stream.get_executor())
223 , m_stream(stream)
224 , m_plainBytesTransferred(plainBytesTransferred)
225 {
226 this->operator()(ec, std::size_t(0), false);
227 }
228
229 AsyncWriteOperation(AsyncWriteOperation&&) = default;
230
231 void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true)
232 {
233 reenter(this)
234 {
235 // mark the number of encrypted bytes sent to the network as "consumed"
236 // Note: bytes_transferred will be zero on first call
237 m_stream.consume_send_buffer(bytes_transferred);
238
239 if(m_stream.has_data_to_send() && !ec)
240 {
241 m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
242 return;
243 }
244
245 if(!isContinuation)
246 {
247 // Make sure the handler is not called without an intermediate initiating function.
248 // "Writing" to a zero-byte buffer will complete immediately.
249 m_ec = ec;
250 yield m_stream.next_layer().async_write_some(boost::asio::const_buffer(), std::move(*this));
251 ec = m_ec;
252 }
253
254 // The size of the sent TLS record can differ from the size of the payload due to TLS encryption. We need to
255 // tell the handler how many bytes of the original data we already processed.
256 this->complete_now(ec, m_plainBytesTransferred);
257 }
258 }
259
260 private:
261 Stream& m_stream;
262 std::size_t m_plainBytesTransferred;
263 boost::system::error_code m_ec;
264 };
265
266 template <class Handler, class Stream, class Allocator = std::allocator<void>>
267 class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::executor_type, Allocator>
268 {
269 public:
270 /**
271 * Construct and invoke an AsyncHandshakeOperation.
272 *
273 * @param handler Handler function to be called upon completion.
274 * @param stream The stream from which the data will be read
275 * @param ec Optional error code; used to report an error to the handler function.
276 */
277 template<class HandlerT>
278 AsyncHandshakeOperation(
279 HandlerT&& handler,
280 Stream& stream,
281 const boost::system::error_code& ec = {})
282 : AsyncBase<Handler, typename Stream::executor_type, Allocator>(
283 std::forward<HandlerT>(handler),
284 stream.get_executor())
285 , m_stream(stream)
286 {
287 this->operator()(ec, std::size_t(0), false);
288 }
289
290 AsyncHandshakeOperation(AsyncHandshakeOperation&&) = default;
291
292 void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation = true)
293 {
294 reenter(this)
295 {
296 if(bytesTransferred > 0 && !ec)
297 {
298 // Provide encrypted TLS data received from the network to TLS::Channel for decryption
299 boost::asio::const_buffer read_buffer {m_stream.input_buffer().data(), bytesTransferred};
300 try
301 {
302 m_stream.native_handle()->received_data(
303 static_cast<const uint8_t*>(read_buffer.data()), read_buffer.size()
304 );
305 }
306 catch(const TLS_Exception& e)
307 {
308 ec = e.type();
309 }
310 catch(const Botan::Exception& e)
311 {
312 ec = e.error_type();
313 }
314 catch(...)
315 {
316 ec = Botan::ErrorType::Unknown;
317 }
318 }
319
320 if(m_stream.has_data_to_send() && !ec)
321 {
322 // Write encrypted TLS data provided by the TLS::Channel on the wire
323
324 // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This
325 // operation will eventually call `*this` as its own handler, passing the 0 back to this call operator.
326 // This is necessary because the check of `bytesTransferred > 0` assumes that `bytesTransferred` bytes
327 // were just read and are available in input_buffer for further processing.
328 AsyncWriteOperation<
329 AsyncHandshakeOperation<typename std::decay<Handler>::type, Stream, Allocator>,
330 Stream,
331 Allocator>
332 op{std::move(*this), m_stream, 0};
333 return;
334 }
335
336 if(!m_stream.native_handle()->is_active() && !ec)
337 {
338 // Read more encrypted TLS data from the network
339 m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
340 return;
341 }
342
343 if(!isContinuation)
344 {
345 // Make sure the handler is not called without an intermediate initiating function.
346 // "Reading" into a zero-byte buffer will complete immediately.
347 m_ec = ec;
348 yield m_stream.next_layer().async_read_some(boost::asio::mutable_buffer(), std::move(*this));
349 ec = m_ec;
350 }
351
352 this->complete_now(ec);
353 }
354 }
355
356 private:
357 Stream& m_stream;
358 boost::system::error_code m_ec;
359 };
360
361 } // namespace detail
362 } // namespace TLS
363 } // namespace Botan
364
365 #include <boost/asio/unyield.hpp>
366
367 #endif // BOOST_VERSION
368 #endif // BOTAN_ASIO_ASYNC_OPS_H_
Botan::TLS::detail::AsyncHandshakeOperation::operator()
void operator()(boost::system::error_code ec, std::size_t bytesTransferred, bool isContinuation=true)
Definition: asio_async_ops.h:292
Botan::TLS::detail::AsyncBase::AsyncBase
AsyncBase(HandlerT &&handler, const Executor1 &executor)
Definition: asio_async_ops.h:86
Botan::TLS::detail::AsyncHandshakeOperation::AsyncHandshakeOperation
AsyncHandshakeOperation(HandlerT &&handler, Stream &stream, const boost::system::error_code &ec={})
Definition: asio_async_ops.h:278
Botan::Exception
Definition: exceptn.h:85
Botan::TLS::detail::AsyncHandshakeOperation
Definition: asio_async_ops.h:267
Botan::TLS::Stream::next_layer
const next_layer_type & next_layer() const
Definition: asio_stream.h:110
Botan::TLS::detail::AsyncBase< Handler, Stream::executor_type, Allocator >::executor_type
boost::asio::associated_executor_t< Handler, Stream::executor_type > executor_type
Definition: asio_async_ops.h:72
Botan::Exception::error_type
virtual ErrorType error_type() const noexcept
Definition: exceptn.h:101
Botan::TLS::detail::AsyncWriteOperation::operator()
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
Definition: asio_async_ops.h:231
std
Definition: bigint.h:1135
Botan::TLS::detail::AsyncWriteOperation::AsyncWriteOperation
AsyncWriteOperation(HandlerT &&handler, Stream &stream, std::size_t plainBytesTransferred, const boost::system::error_code &ec={})
Definition: asio_async_ops.h:216
Botan::TLS::detail::AsyncReadOperation::AsyncReadOperation
AsyncReadOperation(HandlerT &&handler, Stream &stream, const MutableBufferSequence &buffers, const boost::system::error_code &ec={})
Definition: asio_async_ops.h:123
Botan::TLS::Stream::input_buffer
const boost::asio::mutable_buffer & input_buffer()
Definition: asio_stream.h:594
Botan::TLS::Stream::copy_received_data
std::size_t copy_received_data(MutableBufferSequence buffers)
Copy decrypted data into the user-provided buffer.
Definition: asio_stream.h:602
Botan::TLS::Stream::native_handle
native_handle_type native_handle()
Definition: asio_stream.h:116
Botan::TLS::detail::AsyncReadOperation
Definition: asio_async_ops.h:111
Botan::TLS::detail::AsyncBase::m_handler
Handler m_handler
Definition: asio_async_ops.h:106
Botan::TLS::Stream::get_executor
executor_type get_executor() noexcept
Definition: asio_stream.h:108
Botan::TLS::detail::AsyncWriteOperation
Definition: asio_async_ops.h:201
Botan::TLS::Stream
boost::asio compatible SSL/TLS stream
Definition: asio_stream.h:49
Botan::ErrorType::Unknown
Botan
Definition: alg_id.cpp:13
Botan::TLS::Stream::send_buffer
boost::asio::const_buffer send_buffer() const
Definition: asio_stream.h:595
Botan::TLS::detail::AsyncBase
Definition: asio_async_ops.h:68
Botan::TLS::detail::AsyncBase::m_work_guard_1
boost::asio::executor_work_guard< Executor1 > m_work_guard_1
Definition: asio_async_ops.h:107
Botan::TLS::detail::AsyncReadOperation::operator()
void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation=true)
Definition: asio_async_ops.h:139
Botan::TLS::Stream::has_data_to_send
bool has_data_to_send() const
Check if encrypted data is available in the send buffer.
Definition: asio_stream.h:614
Botan::TLS::detail::AsyncBase::get_allocator
allocator_type get_allocator() const noexcept
Definition: asio_async_ops.h:74
Botan::TLS::Stream::has_received_data
bool has_received_data() const
Check if decrypted data is available in the receive buffer.
Definition: asio_stream.h:598
Botan::TLS::detail::AsyncBase< Handler, Stream::executor_type, Allocator >::allocator_type
boost::asio::associated_allocator_t< Handler, Allocator > allocator_type
Definition: asio_async_ops.h:71
Botan::TLS::Stream::consume_send_buffer
void consume_send_buffer(std::size_t bytesConsumed)
Mark bytes in the send buffer as consumed, removing them from the buffer.
Definition: asio_stream.h:617
Botan::TLS::TLS_Exception::type
Alert::Type type() const
Definition: tls_exceptn.h:24
Botan::TLS::detail::AsyncBase::get_executor
executor_type get_executor() const noexcept
Definition: asio_async_ops.h:79
Botan::TLS::TLS_Exception
Definition: tls_exceptn.h:21
Botan::TLS::detail::AsyncBase::complete_now
void complete_now(Args &&...args)
Definition: asio_async_ops.h:100