2 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
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)
7 // Official repository: https://github.com/boostorg/beast
10 #ifndef BOOST_BEAST_CORE_DETECT_SSL_HPP
11 #define BOOST_BEAST_CORE_DETECT_SSL_HPP
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>
26 //------------------------------------------------------------------------------
28 // Example: Detect TLS client_hello
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.
35 //------------------------------------------------------------------------------
37 //[example_core_detect_ssl_1
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.
44 // Using a detail namespace gives the library writer the freedom to change
45 // the interface or behavior later, and maintain backward-compatibility.
49 /** Return `true` if the buffer contains a TLS Protocol client_hello message.
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
57 The return value will be:
59 @li `true` if the contents of the buffer unambiguously define
60 contain a client_hello message,
62 @li `false` if the contents of the buffer cannot possibly
63 be a valid client_hello message, or
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.
70 @param buffers The buffer sequence to inspect.
71 This type must meet the requirements of <em>ConstBufferSequence</em>.
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.
79 <a href="https://tools.ietf.org/html/rfc2246#section-7.4">7.4. Handshake protocol</a>
80 (RFC2246: The TLS Protocol)
82 template <class ConstBufferSequence>
84 is_tls_client_hello (ConstBufferSequence const& buffers);
90 //[example_core_detect_ssl_2
94 template <class ConstBufferSequence>
96 is_tls_client_hello (ConstBufferSequence const& buffers)
98 // Make sure buffers meets the requirements
100 net::is_const_buffer_sequence<ConstBufferSequence>::value,
101 "ConstBufferSequence type requirements not met");
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:
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]
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);
127 // Can't do much without any bytes
129 return boost::indeterminate;
131 // Require the first byte to be 0x16, indicating a TLS handshake record
135 // We need at least 5 bytes to know the record payload size
137 return boost::indeterminate;
139 // Calculate the record payload size
140 std::uint32_t const length = (buf[3] << 8) + buf[4];
142 // A ClientHello message payload is at least 34 bytes.
143 // There can be multiple handshake messages in the same record.
147 // We need at least 6 bytes to know the handshake type
149 return boost::indeterminate;
151 // The handshake_type must be 0x01 == client_hello
155 // We need at least 9 bytes to know the payload size
157 return boost::indeterminate;
159 // Calculate the message payload size
160 std::uint32_t const size =
161 (buf[6] << 16) + (buf[7] << 8) + buf[8];
163 // The message payload can't be bigger than the enclosing record
164 if(size + 4 > length)
167 // This can only be a TLS client_hello message
175 //[example_core_detect_ssl_3
177 /** Detect a TLS client handshake on a stream.
179 This function reads from a stream to determine if a client
180 handshake message is being received.
182 The call blocks until one of the following is true:
184 @li A TLS client opening handshake is detected,
186 @li The received data is invalid for a TLS client handshake, or
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.
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.
198 @param stream The stream to read from. This type must meet the
199 requirements of <em>SyncReadStream</em>.
201 @param buffer The dynamic buffer to use. This type must meet the
202 requirements of <em>DynamicBuffer</em>.
204 @param ec Set to the error if any occurred.
206 @return `true` if the buffer contains a TLS client handshake and
207 no error occurred, otherwise `false`.
210 class SyncReadStream,
214 SyncReadStream& stream,
215 DynamicBuffer& buffer,
218 namespace beast = boost::beast;
220 // Make sure arguments meet the requirements
223 is_sync_read_stream<SyncReadStream>::value,
224 "SyncReadStream type requirements not met");
227 net::is_dynamic_buffer<DynamicBuffer>::value,
228 "DynamicBuffer type requirements not met");
230 // Loop until an error occurs or we get a definitive answer
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());
237 // If we got an answer, return it
238 if(! boost::indeterminate(result))
240 // A definite answer is a success
242 return static_cast<bool>(result);
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.
251 std::size_t const bytes_transferred = stream.read_some(
252 buffer.prepare(beast::read_size(buffer, 1536)), ec);
254 // Commit what we read into the buffer's input area.
255 buffer.commit(bytes_transferred);
257 // Check for an error
268 //[example_core_detect_ssl_4
270 /** Detect a TLS/SSL handshake asynchronously on a stream.
272 This function reads asynchronously from a stream to determine
273 if a client handshake message is being received.
275 This call always returns immediately. The asynchronous operation
276 will continue until one of the following conditions is true:
278 @li A TLS client opening handshake is detected,
280 @li The received data is invalid for a TLS client handshake, or
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.
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.
294 @param stream The stream to read from. This type must meet the
295 requirements of <em>AsyncReadStream</em>.
297 @param buffer The dynamic buffer to use. This type must meet the
298 requirements of <em>DynamicBuffer</em>.
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:
307 error_code const& error, // Set to the error, if any
308 bool result // The result of the detector
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`.
317 class AsyncReadStream,
319 class CompletionToken =
320 net::default_completion_token_t<beast::executor_type<AsyncReadStream>>
322 #if BOOST_BEAST_DOXYGEN
323 BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
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 >*/
337 //[example_core_detect_ssl_5
339 // These implementation details don't need to be public
343 // The composed operation object
346 class AsyncReadStream,
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`).
357 struct run_detect_ssl_op
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
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)`.
372 class AsyncReadStream,
376 AsyncReadStream* s, // references are passed as pointers
380 typename std::decay<DetectHandler>::type,
383 std::forward<DetectHandler>(h), *s, b);
391 //[example_core_detect_ssl_6
393 // Here is the implementation of the asynchronous initiation function
395 class AsyncReadStream,
397 class CompletionToken>
398 #if BOOST_BEAST_DOXYGEN
399 BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
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
411 // Make sure arguments meet the type requirements
414 is_async_read_stream<AsyncReadStream>::value,
415 "SyncReadStream type requirements not met");
418 net::is_dynamic_buffer<DynamicBuffer>::value,
419 "DynamicBuffer type requirements not met");
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.
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`.
433 // Non-const references need to be passed as pointers,
434 // since we don't want a decay-copy.
436 return net::async_initiate<
438 void(error_code, bool)>(
439 detail::run_detect_ssl_op{},
441 &stream, // pass the reference by pointer
447 //[example_core_detect_ssl_7
451 // Read from a stream, calling is_tls_client_hello on the data
452 // data to determine if the TLS client handshake is present.
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.
458 // The library type `async_base` takes care of all of the
459 // boilerplate for writing composed operations, including:
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
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
475 class AsyncReadStream,
478 : public boost::asio::coroutine
480 DetectHandler, executor_type<AsyncReadStream>>
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.
486 AsyncReadStream& stream_;
488 // The callers buffer is used to hold all received data
489 DynamicBuffer& buffer_;
491 // We're going to need this in case we have to post the handler
494 boost::tribool result_ = false;
497 // Completion handlers must be MoveConstructible.
498 detect_ssl_op(detect_ssl_op&&) = default;
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.
504 template<class DetectHandler_>
506 DetectHandler_&& handler,
507 AsyncReadStream& stream,
508 DynamicBuffer& buffer)
511 beast::executor_type<AsyncReadStream>>(
512 std::forward<DetectHandler_>(handler),
513 stream.get_executor())
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
523 (*this)({}, 0, false);
526 // Our main entry point. This will get called as our
527 // intermediate operations complete. Definition below.
529 // The parameter `cont` indicates if we are being called subsequently
530 // from the original invocation
534 std::size_t bytes_transferred,
542 //[example_core_detect_ssl_8
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>
551 // detect_ssl_op is callable with the signature void(error_code, bytes_transferred),
552 // allowing `*this` to be used as a ReadHandler
559 detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
560 operator()(error_code ec, std::size_t bytes_transferred, bool cont)
562 namespace beast = boost::beast;
564 // This introduces the scope of the stackless coroutine
567 // Loop until an error occurs or we get a definitive answer
570 // There could already be a hello in the buffer so check first
571 result_ = is_tls_client_hello(buffer_.data());
573 // If we got an answer, then the operation is complete
574 if(! boost::indeterminate(result_))
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.
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.
594 yield stream_.async_read_some(buffer_.prepare(
595 read_size(buffer_, 1536)), std::move(*this));
597 // Commit what we read into the buffer's input area.
598 buffer_.commit(bytes_transferred);
600 // Check for an error
605 // If `cont` is true, the handler will be invoked directly.
607 // Otherwise, the handler cannot be invoked directly, because
608 // initiating functions are not allowed to call the handler
609 // before returning. Instead, the handler must be posted to
610 // the I/O context. We issue a zero-byte read using the same
611 // type of buffers used in the ordinary read above, to prevent
612 // the compiler from creating an extra instantiation of the
613 // function template. This reduces compile times and the size
614 // of the program executable.
618 // Save the error, otherwise it will be overwritten with
619 // a successful error code when this read completes
623 // Zero-byte reads and writes are guaranteed to complete
624 // immediately with succcess. The type of buffers and the
625 // type of handler passed here need to exactly match the types
626 // used in the call to async_read_some above, to avoid
627 // instantiating another version of the function template.
629 yield stream_.async_read_some(buffer_.prepare(0), std::move(*this));
631 // Restore the saved error code
635 // Invoke the final handler.
636 // At this point, we are guaranteed that the original initiating
637 // function is no longer on our stack frame.
639 this->complete_now(ec, static_cast<bool>(result_));
643 // Including this file undefines the macros used by the stackless fauxroutines.
644 #include <boost/asio/unyield.hpp>