]> git.proxmox.com Git - ceph.git/blame - ceph/src/boost/boost/beast/core/detect_ssl.hpp
import quincy beta 17.1.0
[ceph.git] / ceph / src / boost / boost / beast / core / detect_ssl.hpp
CommitLineData
92f5a8d4
TL
1//
2// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
3//
4// Distributed under the Boost Software License, Version 1.0. (See accompanying
5// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6//
7// Official repository: https://github.com/boostorg/beast
8//
9
10#ifndef BOOST_BEAST_CORE_DETECT_SSL_HPP
11#define BOOST_BEAST_CORE_DETECT_SSL_HPP
12
13#include <boost/beast/core/detail/config.hpp>
14#include <boost/beast/core/async_base.hpp>
15#include <boost/beast/core/error.hpp>
16#include <boost/beast/core/read_size.hpp>
17#include <boost/beast/core/stream_traits.hpp>
18#include <boost/logic/tribool.hpp>
19#include <boost/asio/async_result.hpp>
20#include <boost/asio/coroutine.hpp>
21#include <type_traits>
22
23namespace boost {
24namespace beast {
25
26//------------------------------------------------------------------------------
27//
28// Example: Detect TLS client_hello
29//
30// This is an example and also a public interface. It implements
31// an algorithm for determining if a "TLS client_hello" message
32// is received. It can be used to implement a listening port that
33// can handle both plain and TLS encrypted connections.
34//
35//------------------------------------------------------------------------------
36
37//[example_core_detect_ssl_1
38
39// By convention, the "detail" namespace means "not-public."
40// Identifiers in a detail namespace are not visible in the documentation,
41// and users should not directly use those identifiers in programs, otherwise
42// their program may break in the future.
43//
44// Using a detail namespace gives the library writer the freedom to change
45// the interface or behavior later, and maintain backward-compatibility.
46
47namespace detail {
48
49/** Return `true` if the buffer contains a TLS Protocol client_hello message.
50
51 This function analyzes the bytes at the beginning of the buffer
52 and compares it to a valid client_hello message. This is the
53 message required to be sent by a client at the beginning of
54 any TLS (encrypted communication) session, including when
55 resuming a session.
56
57 The return value will be:
58
59 @li `true` if the contents of the buffer unambiguously define
60 contain a client_hello message,
61
62 @li `false` if the contents of the buffer cannot possibly
63 be a valid client_hello message, or
64
65 @li `boost::indeterminate` if the buffer contains an
66 insufficient number of bytes to determine the result. In
67 this case the caller should read more data from the relevant
68 stream, append it to the buffers, and call this function again.
69
70 @param buffers The buffer sequence to inspect.
71 This type must meet the requirements of <em>ConstBufferSequence</em>.
72
73 @return `boost::tribool` indicating whether the buffer contains
74 a TLS client handshake, does not contain a handshake, or needs
75 additional bytes to determine an outcome.
76
77 @see
78
79 <a href="https://tools.ietf.org/html/rfc2246#section-7.4">7.4. Handshake protocol</a>
80 (RFC2246: The TLS Protocol)
81*/
82template <class ConstBufferSequence>
83boost::tribool
84is_tls_client_hello (ConstBufferSequence const& buffers);
85
86} // detail
87
88//]
89
90//[example_core_detect_ssl_2
91
92namespace detail {
93
94template <class ConstBufferSequence>
95boost::tribool
96is_tls_client_hello (ConstBufferSequence const& buffers)
97{
98 // Make sure buffers meets the requirements
99 static_assert(
100 net::is_const_buffer_sequence<ConstBufferSequence>::value,
101 "ConstBufferSequence type requirements not met");
102
103/*
104 The first message on a TLS connection must be the client_hello,
105 which is a type of handshake record, and it cannot be compressed
106 or encrypted. A plaintext record has this format:
107
108 0 byte record_type // 0x16 = handshake
109 1 byte major // major protocol version
110 2 byte minor // minor protocol version
111 3-4 uint16 length // size of the payload
112 5 byte handshake_type // 0x01 = client_hello
113 6 uint24 length // size of the ClientHello
114 9 byte major // major protocol version
115 10 byte minor // minor protocol version
116 11 uint32 gmt_unix_time
117 15 byte random_bytes[28]
118 ...
119*/
120
121 // Flatten the input buffers into a single contiguous range
122 // of bytes on the stack to make it easier to work with the data.
123 unsigned char buf[9];
124 auto const n = net::buffer_copy(
125 net::mutable_buffer(buf, sizeof(buf)), buffers);
126
127 // Can't do much without any bytes
128 if(n < 1)
129 return boost::indeterminate;
130
131 // Require the first byte to be 0x16, indicating a TLS handshake record
132 if(buf[0] != 0x16)
133 return false;
134
135 // We need at least 5 bytes to know the record payload size
136 if(n < 5)
137 return boost::indeterminate;
138
139 // Calculate the record payload size
140 std::uint32_t const length = (buf[3] << 8) + buf[4];
141
142 // A ClientHello message payload is at least 34 bytes.
143 // There can be multiple handshake messages in the same record.
144 if(length < 34)
145 return false;
146
147 // We need at least 6 bytes to know the handshake type
148 if(n < 6)
149 return boost::indeterminate;
150
151 // The handshake_type must be 0x01 == client_hello
152 if(buf[5] != 0x01)
153 return false;
154
155 // We need at least 9 bytes to know the payload size
156 if(n < 9)
157 return boost::indeterminate;
158
159 // Calculate the message payload size
160 std::uint32_t const size =
161 (buf[6] << 16) + (buf[7] << 8) + buf[8];
162
163 // The message payload can't be bigger than the enclosing record
164 if(size + 4 > length)
165 return false;
166
167 // This can only be a TLS client_hello message
168 return true;
169}
170
171} // detail
172
173//]
174
175//[example_core_detect_ssl_3
176
177/** Detect a TLS client handshake on a stream.
178
179 This function reads from a stream to determine if a client
180 handshake message is being received.
181
182 The call blocks until one of the following is true:
183
184 @li A TLS client opening handshake is detected,
185
186 @li The received data is invalid for a TLS client handshake, or
187
188 @li An error occurs.
189
190 The algorithm, known as a <em>composed operation</em>, is implemented
191 in terms of calls to the next layer's `read_some` function.
192
193 Bytes read from the stream will be stored in the passed dynamic
194 buffer, which may be used to perform the TLS handshake if the
195 detector returns true, or be otherwise consumed by the caller based
196 on the expected protocol.
197
198 @param stream The stream to read from. This type must meet the
199 requirements of <em>SyncReadStream</em>.
200
201 @param buffer The dynamic buffer to use. This type must meet the
202 requirements of <em>DynamicBuffer</em>.
203
204 @param ec Set to the error if any occurred.
205
206 @return `true` if the buffer contains a TLS client handshake and
207 no error occurred, otherwise `false`.
208*/
209template<
210 class SyncReadStream,
211 class DynamicBuffer>
212bool
213detect_ssl(
214 SyncReadStream& stream,
215 DynamicBuffer& buffer,
216 error_code& ec)
217{
218 namespace beast = boost::beast;
219
220 // Make sure arguments meet the requirements
221
222 static_assert(
223 is_sync_read_stream<SyncReadStream>::value,
224 "SyncReadStream type requirements not met");
225
226 static_assert(
227 net::is_dynamic_buffer<DynamicBuffer>::value,
228 "DynamicBuffer type requirements not met");
229
230 // Loop until an error occurs or we get a definitive answer
231 for(;;)
232 {
233 // There could already be data in the buffer
234 // so we do this first, before reading from the stream.
235 auto const result = detail::is_tls_client_hello(buffer.data());
236
237 // If we got an answer, return it
238 if(! boost::indeterminate(result))
239 {
240 // A definite answer is a success
241 ec = {};
242 return static_cast<bool>(result);
243 }
244
245 // Try to fill our buffer by reading from the stream.
246 // The function read_size calculates a reasonable size for the
247 // amount to read next, using existing capacity if possible to
248 // avoid allocating memory, up to the limit of 1536 bytes which
249 // is the size of a normal TCP frame.
250
251 std::size_t const bytes_transferred = stream.read_some(
252 buffer.prepare(beast::read_size(buffer, 1536)), ec);
253
254 // Commit what we read into the buffer's input area.
255 buffer.commit(bytes_transferred);
256
257 // Check for an error
258 if(ec)
259 break;
260 }
261
262 // error
263 return false;
264}
265
266//]
267
268//[example_core_detect_ssl_4
269
270/** Detect a TLS/SSL handshake asynchronously on a stream.
271
272 This function reads asynchronously from a stream to determine
273 if a client handshake message is being received.
274
275 This call always returns immediately. The asynchronous operation
276 will continue until one of the following conditions is true:
277
278 @li A TLS client opening handshake is detected,
279
280 @li The received data is invalid for a TLS client handshake, or
281
282 @li An error occurs.
283
284 The algorithm, known as a <em>composed asynchronous operation</em>,
285 is implemented in terms of calls to the next layer's `async_read_some`
286 function. The program must ensure that no other calls to
287 `async_read_some` are performed until this operation completes.
288
289 Bytes read from the stream will be stored in the passed dynamic
290 buffer, which may be used to perform the TLS handshake if the
291 detector returns true, or be otherwise consumed by the caller based
292 on the expected protocol.
293
294 @param stream The stream to read from. This type must meet the
295 requirements of <em>AsyncReadStream</em>.
296
297 @param buffer The dynamic buffer to use. This type must meet the
298 requirements of <em>DynamicBuffer</em>.
299
300 @param token The completion token used to determine the method
301 used to provide the result of the asynchronous operation. If
302 this is a completion handler, the implementation takes ownership
303 of the handler by performing a decay-copy, and the equivalent
304 function signature of the handler must be:
305 @code
306 void handler(
307 error_code const& error, // Set to the error, if any
308 bool result // The result of the detector
309 );
310 @endcode
311 Regardless of whether the asynchronous operation completes
312 immediately or not, the handler will not be invoked from within
313 this function. Invocation of the handler will be performed in a
314 manner equivalent to using `net::post`.
315*/
316template<
317 class AsyncReadStream,
318 class DynamicBuffer,
319 class CompletionToken =
320 net::default_completion_token_t<beast::executor_type<AsyncReadStream>>
321>
322#if BOOST_BEAST_DOXYGEN
323BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
324#else
325auto
326#endif
327async_detect_ssl(
328 AsyncReadStream& stream,
329 DynamicBuffer& buffer,
330 CompletionToken&& token = net::default_completion_token_t<
331 beast::executor_type<AsyncReadStream>>{}) ->
332 typename net::async_result<
333 typename std::decay<CompletionToken>::type, /*< `async_result` customizes the return value based on the completion token >*/
334 void(error_code, bool)>::return_type; /*< This is the signature for the completion handler >*/
335//]
336
337//[example_core_detect_ssl_5
338
339// These implementation details don't need to be public
340
341namespace detail {
342
343// The composed operation object
344template<
345 class DetectHandler,
346 class AsyncReadStream,
347 class DynamicBuffer>
348class detect_ssl_op;
349
350// This is a function object which `net::async_initiate` can use to launch
351// our composed operation. This is a relatively new feature in networking
352// which allows the asynchronous operation to be "lazily" executed (meaning
353// that it is launched later). Users don't need to worry about this, but
354// authors of composed operations need to write it this way to get the
355// very best performance, for example when using Coroutines TS (`co_await`).
356
357struct run_detect_ssl_op
358{
359 // The implementation of `net::async_initiate` captures the
360 // arguments of the initiating function, and then calls this
361 // function object later with the captured arguments in order
362 // to launch the composed operation. All we need to do here
363 // is take those arguments and construct our composed operation
364 // object.
365 //
366 // `async_initiate` takes care of transforming the completion
367 // token into the "real handler" which must have the correct
368 // signature, in this case `void(error_code, boost::tri_bool)`.
369
370 template<
371 class DetectHandler,
372 class AsyncReadStream,
373 class DynamicBuffer>
374 void operator()(
375 DetectHandler&& h,
376 AsyncReadStream* s, // references are passed as pointers
20effc67 377 DynamicBuffer* b)
92f5a8d4
TL
378 {
379 detect_ssl_op<
380 typename std::decay<DetectHandler>::type,
381 AsyncReadStream,
382 DynamicBuffer>(
20effc67 383 std::forward<DetectHandler>(h), *s, *b);
92f5a8d4
TL
384 }
385};
386
387} // detail
388
389//]
390
391//[example_core_detect_ssl_6
392
393// Here is the implementation of the asynchronous initiation function
394template<
395 class AsyncReadStream,
396 class DynamicBuffer,
397 class CompletionToken>
398#if BOOST_BEAST_DOXYGEN
399BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
400#else
401auto
402#endif
403async_detect_ssl(
404 AsyncReadStream& stream,
405 DynamicBuffer& buffer,
406 CompletionToken&& token)
407 -> typename net::async_result<
408 typename std::decay<CompletionToken>::type,
409 void(error_code, bool)>::return_type
410{
411 // Make sure arguments meet the type requirements
412
413 static_assert(
414 is_async_read_stream<AsyncReadStream>::value,
415 "SyncReadStream type requirements not met");
416
417 static_assert(
418 net::is_dynamic_buffer<DynamicBuffer>::value,
419 "DynamicBuffer type requirements not met");
420
421 // The function `net::async_initate` uses customization points
422 // to allow one asynchronous initiating function to work with
423 // all sorts of notification systems, such as callbacks but also
424 // fibers, futures, coroutines, and user-defined types.
425 //
426 // It works by capturing all of the arguments using perfect
427 // forwarding, and then depending on the specialization of
428 // `net::async_result` for the type of `CompletionToken`,
429 // the `initiation` object will be invoked with the saved
430 // parameters and the actual completion handler. Our
431 // initiating object is `run_detect_ssl_op`.
432 //
433 // Non-const references need to be passed as pointers,
434 // since we don't want a decay-copy.
435
436 return net::async_initiate<
437 CompletionToken,
438 void(error_code, bool)>(
439 detail::run_detect_ssl_op{},
440 token,
441 &stream, // pass the reference by pointer
20effc67 442 &buffer);
92f5a8d4
TL
443}
444
445//]
446
447//[example_core_detect_ssl_7
448
449namespace detail {
450
451// Read from a stream, calling is_tls_client_hello on the data
452// data to determine if the TLS client handshake is present.
453//
454// This will be implemented using Asio's "stackless coroutines"
455// which are based on macros forming a switch statement. The
456// operation is derived from `coroutine` for this reason.
457//
458// The library type `async_base` takes care of all of the
459// boilerplate for writing composed operations, including:
460//
461// * Storing the user's completion handler
462// * Maintaining the work guard for the handler's associated executor
463// * Propagating the associated allocator of the handler
464// * Propagating the associated executor of the handler
465// * Deallocating temporary storage before invoking the handler
466// * Posting the handler to the executor on an immediate completion
467//
468// `async_base` needs to know the type of the handler, as well
469// as the executor of the I/O object being used. The metafunction
470// `executor_type` returns the type of executor used by an
471// I/O object.
472//
473template<
474 class DetectHandler,
475 class AsyncReadStream,
476 class DynamicBuffer>
477class detect_ssl_op
478 : public boost::asio::coroutine
479 , public async_base<
480 DetectHandler, executor_type<AsyncReadStream>>
481{
482 // This composed operation has trivial state,
483 // so it is just kept inside the class and can
484 // be cheaply copied as needed by the implementation.
485
486 AsyncReadStream& stream_;
487
488 // The callers buffer is used to hold all received data
489 DynamicBuffer& buffer_;
490
491 // We're going to need this in case we have to post the handler
492 error_code ec_;
493
494 boost::tribool result_ = false;
495
496public:
497 // Completion handlers must be MoveConstructible.
498 detect_ssl_op(detect_ssl_op&&) = default;
499
500 // Construct the operation. The handler is deduced through
501 // the template type `DetectHandler_`, this lets the same constructor
502 // work properly for both lvalues and rvalues.
503 //
504 template<class DetectHandler_>
505 detect_ssl_op(
506 DetectHandler_&& handler,
507 AsyncReadStream& stream,
508 DynamicBuffer& buffer)
509 : beast::async_base<
510 DetectHandler,
511 beast::executor_type<AsyncReadStream>>(
512 std::forward<DetectHandler_>(handler),
513 stream.get_executor())
514 , stream_(stream)
515 , buffer_(buffer)
516 {
517 // This starts the operation. We pass `false` to tell the
518 // algorithm that it needs to use net::post if it wants to
519 // complete immediately. This is required by Networking,
520 // as initiating functions are not allowed to invoke the
521 // completion handler on the caller's thread before
522 // returning.
523 (*this)({}, 0, false);
524 }
525
526 // Our main entry point. This will get called as our
527 // intermediate operations complete. Definition below.
528 //
529 // The parameter `cont` indicates if we are being called subsequently
530 // from the original invocation
531 //
532 void operator()(
533 error_code ec,
534 std::size_t bytes_transferred,
535 bool cont = true);
536};
537
538} // detail
539
540//]
541
542//[example_core_detect_ssl_8
543
544namespace detail {
545
546// This example uses the Asio's stackless "fauxroutines", implemented
547// using a macro-based solution. It makes the code easier to write and
548// easier to read. This include file defines the necessary macros and types.
549#include <boost/asio/yield.hpp>
550
551// detect_ssl_op is callable with the signature void(error_code, bytes_transferred),
552// allowing `*this` to be used as a ReadHandler
553//
554template<
555 class AsyncStream,
556 class DynamicBuffer,
557 class Handler>
558void
559detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
560operator()(error_code ec, std::size_t bytes_transferred, bool cont)
561{
562 namespace beast = boost::beast;
563
564 // This introduces the scope of the stackless coroutine
565 reenter(*this)
566 {
567 // Loop until an error occurs or we get a definitive answer
568 for(;;)
569 {
570 // There could already be a hello in the buffer so check first
571 result_ = is_tls_client_hello(buffer_.data());
572
573 // If we got an answer, then the operation is complete
574 if(! boost::indeterminate(result_))
575 break;
576
577 // Try to fill our buffer by reading from the stream.
578 // The function read_size calculates a reasonable size for the
579 // amount to read next, using existing capacity if possible to
580 // avoid allocating memory, up to the limit of 1536 bytes which
581 // is the size of a normal TCP frame.
582 //
583 // `async_read_some` expects a ReadHandler as the completion
584 // handler. The signature of a read handler is void(error_code, size_t),
585 // and this function matches that signature (the `cont` parameter has
586 // a default of true). We pass `std::move(*this)` as the completion
587 // handler for the read operation. This transfers ownership of this
588 // entire state machine back into the `async_read_some` operation.
589 // Care must be taken with this idiom, to ensure that parameters
590 // passed to the initiating function which could be invalidated
591 // by the move, are first moved to the stack before calling the
592 // initiating function.
593
20effc67
TL
594 yield
595 {
596 // This macro facilitates asynchrnous handler tracking and
597 // debugging when the preprocessor macro
598 // BOOST_ASIO_CUSTOM_HANDLER_TRACKING is defined.
599
600 BOOST_ASIO_HANDLER_LOCATION((
601 __FILE__, __LINE__,
602 "async_detect_ssl"));
603
604 stream_.async_read_some(buffer_.prepare(
605 read_size(buffer_, 1536)), std::move(*this));
606 }
92f5a8d4
TL
607
608 // Commit what we read into the buffer's input area.
609 buffer_.commit(bytes_transferred);
610
611 // Check for an error
612 if(ec)
613 break;
614 }
615
616 // If `cont` is true, the handler will be invoked directly.
617 //
618 // Otherwise, the handler cannot be invoked directly, because
619 // initiating functions are not allowed to call the handler
620 // before returning. Instead, the handler must be posted to
621 // the I/O context. We issue a zero-byte read using the same
622 // type of buffers used in the ordinary read above, to prevent
623 // the compiler from creating an extra instantiation of the
624 // function template. This reduces compile times and the size
625 // of the program executable.
626
627 if(! cont)
628 {
629 // Save the error, otherwise it will be overwritten with
630 // a successful error code when this read completes
631 // immediately.
632 ec_ = ec;
633
634 // Zero-byte reads and writes are guaranteed to complete
635 // immediately with succcess. The type of buffers and the
636 // type of handler passed here need to exactly match the types
637 // used in the call to async_read_some above, to avoid
638 // instantiating another version of the function template.
639
20effc67
TL
640 yield
641 {
642 BOOST_ASIO_HANDLER_LOCATION((
643 __FILE__, __LINE__,
644 "async_detect_ssl"));
645
646 stream_.async_read_some(buffer_.prepare(0), std::move(*this));
647 }
92f5a8d4
TL
648
649 // Restore the saved error code
650 ec = ec_;
651 }
652
653 // Invoke the final handler.
654 // At this point, we are guaranteed that the original initiating
655 // function is no longer on our stack frame.
656
657 this->complete_now(ec, static_cast<bool>(result_));
658 }
659}
660
661// Including this file undefines the macros used by the stackless fauxroutines.
662#include <boost/asio/unyield.hpp>
663
664} // detail
665
666//]
667
668} // beast
669} // boost
670
671#endif