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 
23 namespace boost {
24 namespace 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 
47 namespace 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 */
82 template <class ConstBufferSequence>
83 boost::tribool
84 is_tls_client_hello (ConstBufferSequence const& buffers);
85 
86 } // detail
87 
88 //]
89 
90 //[example_core_detect_ssl_2
91 
92 namespace detail {
93 
94 template <class ConstBufferSequence>
95 boost::tribool
is_tls_client_hello(ConstBufferSequence const & buffers)96 is_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 */
209 template<
210     class SyncReadStream,
211     class DynamicBuffer>
212 bool
detect_ssl(SyncReadStream & stream,DynamicBuffer & buffer,error_code & ec)213 detect_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 */
316 template<
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
323 BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
324 #else
325 auto
326 #endif
327 async_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 
341 namespace detail {
342 
343 // The composed operation object
344 template<
345     class DetectHandler,
346     class AsyncReadStream,
347     class DynamicBuffer>
348 class 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 
357 struct 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>
operator ()boost::beast::detail::run_detect_ssl_op374     void operator()(
375         DetectHandler&& h,
376         AsyncReadStream* s, // references are passed as pointers
377         DynamicBuffer* b)
378     {
379         detect_ssl_op<
380             typename std::decay<DetectHandler>::type,
381             AsyncReadStream,
382             DynamicBuffer>(
383                 std::forward<DetectHandler>(h), *s, *b);
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
394 template<
395     class AsyncReadStream,
396     class DynamicBuffer,
397     class CompletionToken>
398 #if BOOST_BEAST_DOXYGEN
BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken,void (error_code,bool))399 BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
400 #else
401 auto
402 #endif
403 async_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
442             &buffer);
443 }
444 
445 //]
446 
447 //[example_core_detect_ssl_7
448 
449 namespace 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 //
473 template<
474     class DetectHandler,
475     class AsyncReadStream,
476     class DynamicBuffer>
477 class 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 
496 public:
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_>
detect_ssl_op(DetectHandler_ && handler,AsyncReadStream & stream,DynamicBuffer & buffer)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 
544 namespace 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 //
554 template<
555     class AsyncStream,
556     class DynamicBuffer,
557     class Handler>
558 void
559 detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
operator ()(error_code ec,std::size_t bytes_transferred,bool cont)560 operator()(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 
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             }
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 
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             }
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
672