summaryrefslogtreecommitdiff
path: root/boost/beast/core/detect_ssl.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'boost/beast/core/detect_ssl.hpp')
-rw-r--r--boost/beast/core/detect_ssl.hpp651
1 files changed, 651 insertions, 0 deletions
diff --git a/boost/beast/core/detect_ssl.hpp b/boost/beast/core/detect_ssl.hpp
new file mode 100644
index 0000000000..b7b0550175
--- /dev/null
+++ b/boost/beast/core/detect_ssl.hpp
@@ -0,0 +1,651 @@
+//
+// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// Official repository: https://github.com/boostorg/beast
+//
+
+#ifndef BOOST_BEAST_CORE_DETECT_SSL_HPP
+#define BOOST_BEAST_CORE_DETECT_SSL_HPP
+
+#include <boost/beast/core/detail/config.hpp>
+#include <boost/beast/core/async_base.hpp>
+#include <boost/beast/core/error.hpp>
+#include <boost/beast/core/read_size.hpp>
+#include <boost/beast/core/stream_traits.hpp>
+#include <boost/logic/tribool.hpp>
+#include <boost/asio/async_result.hpp>
+#include <boost/asio/coroutine.hpp>
+#include <type_traits>
+
+namespace boost {
+namespace beast {
+
+//------------------------------------------------------------------------------
+//
+// Example: Detect TLS client_hello
+//
+// This is an example and also a public interface. It implements
+// an algorithm for determining if a "TLS client_hello" message
+// is received. It can be used to implement a listening port that
+// can handle both plain and TLS encrypted connections.
+//
+//------------------------------------------------------------------------------
+
+//[example_core_detect_ssl_1
+
+// By convention, the "detail" namespace means "not-public."
+// Identifiers in a detail namespace are not visible in the documentation,
+// and users should not directly use those identifiers in programs, otherwise
+// their program may break in the future.
+//
+// Using a detail namespace gives the library writer the freedom to change
+// the interface or behavior later, and maintain backward-compatibility.
+
+namespace detail {
+
+/** Return `true` if the buffer contains a TLS Protocol client_hello message.
+
+ This function analyzes the bytes at the beginning of the buffer
+ and compares it to a valid client_hello message. This is the
+ message required to be sent by a client at the beginning of
+ any TLS (encrypted communication) session, including when
+ resuming a session.
+
+ The return value will be:
+
+ @li `true` if the contents of the buffer unambiguously define
+ contain a client_hello message,
+
+ @li `false` if the contents of the buffer cannot possibly
+ be a valid client_hello message, or
+
+ @li `boost::indeterminate` if the buffer contains an
+ insufficient number of bytes to determine the result. In
+ this case the caller should read more data from the relevant
+ stream, append it to the buffers, and call this function again.
+
+ @param buffers The buffer sequence to inspect.
+ This type must meet the requirements of <em>ConstBufferSequence</em>.
+
+ @return `boost::tribool` indicating whether the buffer contains
+ a TLS client handshake, does not contain a handshake, or needs
+ additional bytes to determine an outcome.
+
+ @see
+
+ <a href="https://tools.ietf.org/html/rfc2246#section-7.4">7.4. Handshake protocol</a>
+ (RFC2246: The TLS Protocol)
+*/
+template <class ConstBufferSequence>
+boost::tribool
+is_tls_client_hello (ConstBufferSequence const& buffers);
+
+} // detail
+
+//]
+
+//[example_core_detect_ssl_2
+
+namespace detail {
+
+template <class ConstBufferSequence>
+boost::tribool
+is_tls_client_hello (ConstBufferSequence const& buffers)
+{
+ // Make sure buffers meets the requirements
+ static_assert(
+ net::is_const_buffer_sequence<ConstBufferSequence>::value,
+ "ConstBufferSequence type requirements not met");
+
+/*
+ The first message on a TLS connection must be the client_hello,
+ which is a type of handshake record, and it cannot be compressed
+ or encrypted. A plaintext record has this format:
+
+ 0 byte record_type // 0x16 = handshake
+ 1 byte major // major protocol version
+ 2 byte minor // minor protocol version
+ 3-4 uint16 length // size of the payload
+ 5 byte handshake_type // 0x01 = client_hello
+ 6 uint24 length // size of the ClientHello
+ 9 byte major // major protocol version
+ 10 byte minor // minor protocol version
+ 11 uint32 gmt_unix_time
+ 15 byte random_bytes[28]
+ ...
+*/
+
+ // Flatten the input buffers into a single contiguous range
+ // of bytes on the stack to make it easier to work with the data.
+ unsigned char buf[9];
+ auto const n = net::buffer_copy(
+ net::mutable_buffer(buf, sizeof(buf)), buffers);
+
+ // Can't do much without any bytes
+ if(n < 1)
+ return boost::indeterminate;
+
+ // Require the first byte to be 0x16, indicating a TLS handshake record
+ if(buf[0] != 0x16)
+ return false;
+
+ // We need at least 5 bytes to know the record payload size
+ if(n < 5)
+ return boost::indeterminate;
+
+ // Calculate the record payload size
+ std::uint32_t const length = (buf[3] << 8) + buf[4];
+
+ // A ClientHello message payload is at least 34 bytes.
+ // There can be multiple handshake messages in the same record.
+ if(length < 34)
+ return false;
+
+ // We need at least 6 bytes to know the handshake type
+ if(n < 6)
+ return boost::indeterminate;
+
+ // The handshake_type must be 0x01 == client_hello
+ if(buf[5] != 0x01)
+ return false;
+
+ // We need at least 9 bytes to know the payload size
+ if(n < 9)
+ return boost::indeterminate;
+
+ // Calculate the message payload size
+ std::uint32_t const size =
+ (buf[6] << 16) + (buf[7] << 8) + buf[8];
+
+ // The message payload can't be bigger than the enclosing record
+ if(size + 4 > length)
+ return false;
+
+ // This can only be a TLS client_hello message
+ return true;
+}
+
+} // detail
+
+//]
+
+//[example_core_detect_ssl_3
+
+/** Detect a TLS client handshake on a stream.
+
+ This function reads from a stream to determine if a client
+ handshake message is being received.
+
+ The call blocks until one of the following is true:
+
+ @li A TLS client opening handshake is detected,
+
+ @li The received data is invalid for a TLS client handshake, or
+
+ @li An error occurs.
+
+ The algorithm, known as a <em>composed operation</em>, is implemented
+ in terms of calls to the next layer's `read_some` function.
+
+ Bytes read from the stream will be stored in the passed dynamic
+ buffer, which may be used to perform the TLS handshake if the
+ detector returns true, or be otherwise consumed by the caller based
+ on the expected protocol.
+
+ @param stream The stream to read from. This type must meet the
+ requirements of <em>SyncReadStream</em>.
+
+ @param buffer The dynamic buffer to use. This type must meet the
+ requirements of <em>DynamicBuffer</em>.
+
+ @param ec Set to the error if any occurred.
+
+ @return `true` if the buffer contains a TLS client handshake and
+ no error occurred, otherwise `false`.
+*/
+template<
+ class SyncReadStream,
+ class DynamicBuffer>
+bool
+detect_ssl(
+ SyncReadStream& stream,
+ DynamicBuffer& buffer,
+ error_code& ec)
+{
+ namespace beast = boost::beast;
+
+ // Make sure arguments meet the requirements
+
+ static_assert(
+ is_sync_read_stream<SyncReadStream>::value,
+ "SyncReadStream type requirements not met");
+
+ static_assert(
+ net::is_dynamic_buffer<DynamicBuffer>::value,
+ "DynamicBuffer type requirements not met");
+
+ // Loop until an error occurs or we get a definitive answer
+ for(;;)
+ {
+ // There could already be data in the buffer
+ // so we do this first, before reading from the stream.
+ auto const result = detail::is_tls_client_hello(buffer.data());
+
+ // If we got an answer, return it
+ if(! boost::indeterminate(result))
+ {
+ // A definite answer is a success
+ ec = {};
+ return static_cast<bool>(result);
+ }
+
+ // Try to fill our buffer by reading from the stream.
+ // The function read_size calculates a reasonable size for the
+ // amount to read next, using existing capacity if possible to
+ // avoid allocating memory, up to the limit of 1536 bytes which
+ // is the size of a normal TCP frame.
+
+ std::size_t const bytes_transferred = stream.read_some(
+ buffer.prepare(beast::read_size(buffer, 1536)), ec);
+
+ // Commit what we read into the buffer's input area.
+ buffer.commit(bytes_transferred);
+
+ // Check for an error
+ if(ec)
+ break;
+ }
+
+ // error
+ return false;
+}
+
+//]
+
+//[example_core_detect_ssl_4
+
+/** Detect a TLS/SSL handshake asynchronously on a stream.
+
+ This function reads asynchronously from a stream to determine
+ if a client handshake message is being received.
+
+ This call always returns immediately. The asynchronous operation
+ will continue until one of the following conditions is true:
+
+ @li A TLS client opening handshake is detected,
+
+ @li The received data is invalid for a TLS client handshake, or
+
+ @li An error occurs.
+
+ The algorithm, known as a <em>composed asynchronous operation</em>,
+ is implemented in terms of calls to the next layer's `async_read_some`
+ function. The program must ensure that no other calls to
+ `async_read_some` are performed until this operation completes.
+
+ Bytes read from the stream will be stored in the passed dynamic
+ buffer, which may be used to perform the TLS handshake if the
+ detector returns true, or be otherwise consumed by the caller based
+ on the expected protocol.
+
+ @param stream The stream to read from. This type must meet the
+ requirements of <em>AsyncReadStream</em>.
+
+ @param buffer The dynamic buffer to use. This type must meet the
+ requirements of <em>DynamicBuffer</em>.
+
+ @param token The completion token used to determine the method
+ used to provide the result of the asynchronous operation. If
+ this is a completion handler, the implementation takes ownership
+ of the handler by performing a decay-copy, and the equivalent
+ function signature of the handler must be:
+ @code
+ void handler(
+ error_code const& error, // Set to the error, if any
+ bool result // The result of the detector
+ );
+ @endcode
+ Regardless of whether the asynchronous operation completes
+ immediately or not, the handler will not be invoked from within
+ this function. Invocation of the handler will be performed in a
+ manner equivalent to using `net::post`.
+*/
+template<
+ class AsyncReadStream,
+ class DynamicBuffer,
+ class CompletionToken>
+#if BOOST_BEAST_DOXYGEN
+BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
+#else
+auto
+#endif
+async_detect_ssl(
+ AsyncReadStream& stream,
+ DynamicBuffer& buffer,
+ CompletionToken&& token) ->
+ typename net::async_result<
+ typename std::decay<CompletionToken>::type, /*< `async_result` customizes the return value based on the completion token >*/
+ void(error_code, bool)>::return_type; /*< This is the signature for the completion handler >*/
+//]
+
+//[example_core_detect_ssl_5
+
+// These implementation details don't need to be public
+
+namespace detail {
+
+// The composed operation object
+template<
+ class DetectHandler,
+ class AsyncReadStream,
+ class DynamicBuffer>
+class detect_ssl_op;
+
+// This is a function object which `net::async_initiate` can use to launch
+// our composed operation. This is a relatively new feature in networking
+// which allows the asynchronous operation to be "lazily" executed (meaning
+// that it is launched later). Users don't need to worry about this, but
+// authors of composed operations need to write it this way to get the
+// very best performance, for example when using Coroutines TS (`co_await`).
+
+struct run_detect_ssl_op
+{
+ // The implementation of `net::async_initiate` captures the
+ // arguments of the initiating function, and then calls this
+ // function object later with the captured arguments in order
+ // to launch the composed operation. All we need to do here
+ // is take those arguments and construct our composed operation
+ // object.
+ //
+ // `async_initiate` takes care of transforming the completion
+ // token into the "real handler" which must have the correct
+ // signature, in this case `void(error_code, boost::tri_bool)`.
+
+ template<
+ class DetectHandler,
+ class AsyncReadStream,
+ class DynamicBuffer>
+ void operator()(
+ DetectHandler&& h,
+ AsyncReadStream* s, // references are passed as pointers
+ DynamicBuffer& b)
+ {
+ detect_ssl_op<
+ typename std::decay<DetectHandler>::type,
+ AsyncReadStream,
+ DynamicBuffer>(
+ std::forward<DetectHandler>(h), *s, b);
+ }
+};
+
+} // detail
+
+//]
+
+//[example_core_detect_ssl_6
+
+// Here is the implementation of the asynchronous initiation function
+template<
+ class AsyncReadStream,
+ class DynamicBuffer,
+ class CompletionToken>
+#if BOOST_BEAST_DOXYGEN
+BOOST_ASIO_INITFN_RESULT_TYPE(CompletionToken, void(error_code, bool))
+#else
+auto
+#endif
+async_detect_ssl(
+ AsyncReadStream& stream,
+ DynamicBuffer& buffer,
+ CompletionToken&& token)
+ -> typename net::async_result<
+ typename std::decay<CompletionToken>::type,
+ void(error_code, bool)>::return_type
+{
+ // Make sure arguments meet the type requirements
+
+ static_assert(
+ is_async_read_stream<AsyncReadStream>::value,
+ "SyncReadStream type requirements not met");
+
+ static_assert(
+ net::is_dynamic_buffer<DynamicBuffer>::value,
+ "DynamicBuffer type requirements not met");
+
+ // The function `net::async_initate` uses customization points
+ // to allow one asynchronous initiating function to work with
+ // all sorts of notification systems, such as callbacks but also
+ // fibers, futures, coroutines, and user-defined types.
+ //
+ // It works by capturing all of the arguments using perfect
+ // forwarding, and then depending on the specialization of
+ // `net::async_result` for the type of `CompletionToken`,
+ // the `initiation` object will be invoked with the saved
+ // parameters and the actual completion handler. Our
+ // initiating object is `run_detect_ssl_op`.
+ //
+ // Non-const references need to be passed as pointers,
+ // since we don't want a decay-copy.
+
+
+ return net::async_initiate<
+ CompletionToken,
+ void(error_code, bool)>(
+ detail::run_detect_ssl_op{},
+ token,
+ &stream, // pass the reference by pointer
+ buffer);
+}
+
+//]
+
+//[example_core_detect_ssl_7
+
+namespace detail {
+
+// Read from a stream, calling is_tls_client_hello on the data
+// data to determine if the TLS client handshake is present.
+//
+// This will be implemented using Asio's "stackless coroutines"
+// which are based on macros forming a switch statement. The
+// operation is derived from `coroutine` for this reason.
+//
+// The library type `async_base` takes care of all of the
+// boilerplate for writing composed operations, including:
+//
+// * Storing the user's completion handler
+// * Maintaining the work guard for the handler's associated executor
+// * Propagating the associated allocator of the handler
+// * Propagating the associated executor of the handler
+// * Deallocating temporary storage before invoking the handler
+// * Posting the handler to the executor on an immediate completion
+//
+// `async_base` needs to know the type of the handler, as well
+// as the executor of the I/O object being used. The metafunction
+// `executor_type` returns the type of executor used by an
+// I/O object.
+//
+template<
+ class DetectHandler,
+ class AsyncReadStream,
+ class DynamicBuffer>
+class detect_ssl_op
+ : public boost::asio::coroutine
+ , public async_base<
+ DetectHandler, executor_type<AsyncReadStream>>
+{
+ // This composed operation has trivial state,
+ // so it is just kept inside the class and can
+ // be cheaply copied as needed by the implementation.
+
+ AsyncReadStream& stream_;
+
+ // The callers buffer is used to hold all received data
+ DynamicBuffer& buffer_;
+
+ // We're going to need this in case we have to post the handler
+ error_code ec_;
+
+ boost::tribool result_ = false;
+
+public:
+ // Completion handlers must be MoveConstructible.
+ detect_ssl_op(detect_ssl_op&&) = default;
+
+ // Construct the operation. The handler is deduced through
+ // the template type `DetectHandler_`, this lets the same constructor
+ // work properly for both lvalues and rvalues.
+ //
+ template<class DetectHandler_>
+ detect_ssl_op(
+ DetectHandler_&& handler,
+ AsyncReadStream& stream,
+ DynamicBuffer& buffer)
+ : beast::async_base<
+ DetectHandler_,
+ beast::executor_type<AsyncReadStream>>(
+ std::forward<DetectHandler_>(handler),
+ stream.get_executor())
+ , stream_(stream)
+ , buffer_(buffer)
+ {
+ // This starts the operation. We pass `false` to tell the
+ // algorithm that it needs to use net::post if it wants to
+ // complete immediately. This is required by Networking,
+ // as initiating functions are not allowed to invoke the
+ // completion handler on the caller's thread before
+ // returning.
+ (*this)({}, 0, false);
+ }
+
+ // Our main entry point. This will get called as our
+ // intermediate operations complete. Definition below.
+ //
+ // The parameter `cont` indicates if we are being called subsequently
+ // from the original invocation
+ //
+ void operator()(
+ error_code ec,
+ std::size_t bytes_transferred,
+ bool cont = true);
+};
+
+} // detail
+
+//]
+
+//[example_core_detect_ssl_8
+
+namespace detail {
+
+// This example uses the Asio's stackless "fauxroutines", implemented
+// using a macro-based solution. It makes the code easier to write and
+// easier to read. This include file defines the necessary macros and types.
+#include <boost/asio/yield.hpp>
+
+// detect_ssl_op is callable with the signature void(error_code, bytes_transferred),
+// allowing `*this` to be used as a ReadHandler
+//
+template<
+ class AsyncStream,
+ class DynamicBuffer,
+ class Handler>
+void
+detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
+operator()(error_code ec, std::size_t bytes_transferred, bool cont)
+{
+ namespace beast = boost::beast;
+
+ // This introduces the scope of the stackless coroutine
+ reenter(*this)
+ {
+ // Loop until an error occurs or we get a definitive answer
+ for(;;)
+ {
+ // There could already be a hello in the buffer so check first
+ result_ = is_tls_client_hello(buffer_.data());
+
+ // If we got an answer, then the operation is complete
+ if(! boost::indeterminate(result_))
+ break;
+
+ // Try to fill our buffer by reading from the stream.
+ // The function read_size calculates a reasonable size for the
+ // amount to read next, using existing capacity if possible to
+ // avoid allocating memory, up to the limit of 1536 bytes which
+ // is the size of a normal TCP frame.
+ //
+ // `async_read_some` expects a ReadHandler as the completion
+ // handler. The signature of a read handler is void(error_code, size_t),
+ // and this function matches that signature (the `cont` parameter has
+ // a default of true). We pass `std::move(*this)` as the completion
+ // handler for the read operation. This transfers ownership of this
+ // entire state machine back into the `async_read_some` operation.
+ // Care must be taken with this idiom, to ensure that parameters
+ // passed to the initiating function which could be invalidated
+ // by the move, are first moved to the stack before calling the
+ // initiating function.
+
+ yield stream_.async_read_some(buffer_.prepare(
+ read_size(buffer_, 1536)), std::move(*this));
+
+ // Commit what we read into the buffer's input area.
+ buffer_.commit(bytes_transferred);
+
+ // Check for an error
+ if(ec)
+ break;
+ }
+
+ // If `cont` is true, the handler will be invoked directly.
+ //
+ // Otherwise, the handler cannot be invoked directly, because
+ // initiating functions are not allowed to call the handler
+ // before returning. Instead, the handler must be posted to
+ // the I/O context. We issue a zero-byte read using the same
+ // type of buffers used in the ordinary read above, to prevent
+ // the compiler from creating an extra instantiation of the
+ // function template. This reduces compile times and the size
+ // of the program executable.
+
+ if(! cont)
+ {
+ // Save the error, otherwise it will be overwritten with
+ // a successful error code when this read completes
+ // immediately.
+ ec_ = ec;
+
+ // Zero-byte reads and writes are guaranteed to complete
+ // immediately with succcess. The type of buffers and the
+ // type of handler passed here need to exactly match the types
+ // used in the call to async_read_some above, to avoid
+ // instantiating another version of the function template.
+
+ yield stream_.async_read_some(buffer_.prepare(0), std::move(*this));
+
+ // Restore the saved error code
+ ec = ec_;
+ }
+
+ // Invoke the final handler.
+ // At this point, we are guaranteed that the original initiating
+ // function is no longer on our stack frame.
+
+ this->complete_now(ec, static_cast<bool>(result_));
+ }
+}
+
+// Including this file undefines the macros used by the stackless fauxroutines.
+#include <boost/asio/unyield.hpp>
+
+} // detail
+
+//]
+
+} // beast
+} // boost
+
+#endif