|
||||
File indexing completed on 2025-01-18 09:29:27
0001 // 0002 // Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com) 0003 // 0004 // Distributed under the Boost Software License, Version 1.0. (See accompanying 0005 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 0006 // 0007 // Official repository: https://github.com/boostorg/beast 0008 // 0009 0010 #ifndef BOOST_BEAST_CORE_DETECT_SSL_HPP 0011 #define BOOST_BEAST_CORE_DETECT_SSL_HPP 0012 0013 #include <boost/beast/core/detail/config.hpp> 0014 #include <boost/beast/core/async_base.hpp> 0015 #include <boost/beast/core/error.hpp> 0016 #include <boost/beast/core/read_size.hpp> 0017 #include <boost/beast/core/stream_traits.hpp> 0018 #include <boost/logic/tribool.hpp> 0019 #include <boost/asio/async_result.hpp> 0020 #include <boost/asio/coroutine.hpp> 0021 #include <type_traits> 0022 0023 namespace boost { 0024 namespace beast { 0025 0026 //------------------------------------------------------------------------------ 0027 // 0028 // Example: Detect TLS client_hello 0029 // 0030 // This is an example and also a public interface. It implements 0031 // an algorithm for determining if a "TLS client_hello" message 0032 // is received. It can be used to implement a listening port that 0033 // can handle both plain and TLS encrypted connections. 0034 // 0035 //------------------------------------------------------------------------------ 0036 0037 //[example_core_detect_ssl_1 0038 0039 // By convention, the "detail" namespace means "not-public." 0040 // Identifiers in a detail namespace are not visible in the documentation, 0041 // and users should not directly use those identifiers in programs, otherwise 0042 // their program may break in the future. 0043 // 0044 // Using a detail namespace gives the library writer the freedom to change 0045 // the interface or behavior later, and maintain backward-compatibility. 0046 0047 namespace detail { 0048 0049 /** Return `true` if the buffer contains a TLS Protocol client_hello message. 0050 0051 This function analyzes the bytes at the beginning of the buffer 0052 and compares it to a valid client_hello message. This is the 0053 message required to be sent by a client at the beginning of 0054 any TLS (encrypted communication) session, including when 0055 resuming a session. 0056 0057 The return value will be: 0058 0059 @li `true` if the contents of the buffer unambiguously define 0060 contain a client_hello message, 0061 0062 @li `false` if the contents of the buffer cannot possibly 0063 be a valid client_hello message, or 0064 0065 @li `boost::indeterminate` if the buffer contains an 0066 insufficient number of bytes to determine the result. In 0067 this case the caller should read more data from the relevant 0068 stream, append it to the buffers, and call this function again. 0069 0070 @param buffers The buffer sequence to inspect. 0071 This type must meet the requirements of <em>ConstBufferSequence</em>. 0072 0073 @return `boost::tribool` indicating whether the buffer contains 0074 a TLS client handshake, does not contain a handshake, or needs 0075 additional bytes to determine an outcome. 0076 0077 @see 0078 0079 <a href="https://tools.ietf.org/html/rfc2246#section-7.4">7.4. Handshake protocol</a> 0080 (RFC2246: The TLS Protocol) 0081 */ 0082 template <class ConstBufferSequence> 0083 boost::tribool 0084 is_tls_client_hello (ConstBufferSequence const& buffers); 0085 0086 } // detail 0087 0088 //] 0089 0090 //[example_core_detect_ssl_2 0091 0092 namespace detail { 0093 0094 template <class ConstBufferSequence> 0095 boost::tribool 0096 is_tls_client_hello (ConstBufferSequence const& buffers) 0097 { 0098 // Make sure buffers meets the requirements 0099 static_assert( 0100 net::is_const_buffer_sequence<ConstBufferSequence>::value, 0101 "ConstBufferSequence type requirements not met"); 0102 0103 /* 0104 The first message on a TLS connection must be the client_hello, 0105 which is a type of handshake record, and it cannot be compressed 0106 or encrypted. A plaintext record has this format: 0107 0108 0 byte record_type // 0x16 = handshake 0109 1 byte major // major protocol version 0110 2 byte minor // minor protocol version 0111 3-4 uint16 length // size of the payload 0112 5 byte handshake_type // 0x01 = client_hello 0113 6 uint24 length // size of the ClientHello 0114 9 byte major // major protocol version 0115 10 byte minor // minor protocol version 0116 11 uint32 gmt_unix_time 0117 15 byte random_bytes[28] 0118 ... 0119 */ 0120 0121 // Flatten the input buffers into a single contiguous range 0122 // of bytes on the stack to make it easier to work with the data. 0123 unsigned char buf[9]; 0124 auto const n = net::buffer_copy( 0125 net::mutable_buffer(buf, sizeof(buf)), buffers); 0126 0127 // Can't do much without any bytes 0128 if(n < 1) 0129 return boost::indeterminate; 0130 0131 // Require the first byte to be 0x16, indicating a TLS handshake record 0132 if(buf[0] != 0x16) 0133 return false; 0134 0135 // We need at least 5 bytes to know the record payload size 0136 if(n < 5) 0137 return boost::indeterminate; 0138 0139 // Calculate the record payload size 0140 std::uint32_t const length = (buf[3] << 8) + buf[4]; 0141 0142 // A ClientHello message payload is at least 34 bytes. 0143 // There can be multiple handshake messages in the same record. 0144 if(length < 34) 0145 return false; 0146 0147 // We need at least 6 bytes to know the handshake type 0148 if(n < 6) 0149 return boost::indeterminate; 0150 0151 // The handshake_type must be 0x01 == client_hello 0152 if(buf[5] != 0x01) 0153 return false; 0154 0155 // We need at least 9 bytes to know the payload size 0156 if(n < 9) 0157 return boost::indeterminate; 0158 0159 // Calculate the message payload size 0160 std::uint32_t const size = 0161 (buf[6] << 16) + (buf[7] << 8) + buf[8]; 0162 0163 // The message payload can't be bigger than the enclosing record 0164 if(size + 4 > length) 0165 return false; 0166 0167 // This can only be a TLS client_hello message 0168 return true; 0169 } 0170 0171 } // detail 0172 0173 //] 0174 0175 //[example_core_detect_ssl_3 0176 0177 /** Detect a TLS client handshake on a stream. 0178 0179 This function reads from a stream to determine if a client 0180 handshake message is being received. 0181 0182 The call blocks until one of the following is true: 0183 0184 @li A TLS client opening handshake is detected, 0185 0186 @li The received data is invalid for a TLS client handshake, or 0187 0188 @li An error occurs. 0189 0190 The algorithm, known as a <em>composed operation</em>, is implemented 0191 in terms of calls to the next layer's `read_some` function. 0192 0193 Bytes read from the stream will be stored in the passed dynamic 0194 buffer, which may be used to perform the TLS handshake if the 0195 detector returns true, or be otherwise consumed by the caller based 0196 on the expected protocol. 0197 0198 @param stream The stream to read from. This type must meet the 0199 requirements of <em>SyncReadStream</em>. 0200 0201 @param buffer The dynamic buffer to use. This type must meet the 0202 requirements of <em>DynamicBuffer</em>. 0203 0204 @param ec Set to the error if any occurred. 0205 0206 @return `true` if the buffer contains a TLS client handshake and 0207 no error occurred, otherwise `false`. 0208 */ 0209 template< 0210 class SyncReadStream, 0211 class DynamicBuffer> 0212 bool 0213 detect_ssl( 0214 SyncReadStream& stream, 0215 DynamicBuffer& buffer, 0216 error_code& ec) 0217 { 0218 namespace beast = boost::beast; 0219 0220 // Make sure arguments meet the requirements 0221 0222 static_assert( 0223 is_sync_read_stream<SyncReadStream>::value, 0224 "SyncReadStream type requirements not met"); 0225 0226 static_assert( 0227 net::is_dynamic_buffer<DynamicBuffer>::value, 0228 "DynamicBuffer type requirements not met"); 0229 0230 // Loop until an error occurs or we get a definitive answer 0231 for(;;) 0232 { 0233 // There could already be data in the buffer 0234 // so we do this first, before reading from the stream. 0235 auto const result = detail::is_tls_client_hello(buffer.data()); 0236 0237 // If we got an answer, return it 0238 if(! boost::indeterminate(result)) 0239 { 0240 // A definite answer is a success 0241 ec = {}; 0242 return static_cast<bool>(result); 0243 } 0244 0245 // Try to fill our buffer by reading from the stream. 0246 // The function read_size calculates a reasonable size for the 0247 // amount to read next, using existing capacity if possible to 0248 // avoid allocating memory, up to the limit of 1536 bytes which 0249 // is the size of a normal TCP frame. 0250 0251 std::size_t const bytes_transferred = stream.read_some( 0252 buffer.prepare(beast::read_size(buffer, 1536)), ec); 0253 0254 // Commit what we read into the buffer's input area. 0255 buffer.commit(bytes_transferred); 0256 0257 // Check for an error 0258 if(ec) 0259 break; 0260 } 0261 0262 // error 0263 return false; 0264 } 0265 0266 //] 0267 0268 //[example_core_detect_ssl_4 0269 0270 /** Detect a TLS/SSL handshake asynchronously on a stream. 0271 0272 This function reads asynchronously from a stream to determine 0273 if a client handshake message is being received. 0274 0275 This call always returns immediately. The asynchronous operation 0276 will continue until one of the following conditions is true: 0277 0278 @li A TLS client opening handshake is detected, 0279 0280 @li The received data is invalid for a TLS client handshake, or 0281 0282 @li An error occurs. 0283 0284 The algorithm, known as a <em>composed asynchronous operation</em>, 0285 is implemented in terms of calls to the next layer's `async_read_some` 0286 function. The program must ensure that no other calls to 0287 `async_read_some` are performed until this operation completes. 0288 0289 Bytes read from the stream will be stored in the passed dynamic 0290 buffer, which may be used to perform the TLS handshake if the 0291 detector returns true, or be otherwise consumed by the caller based 0292 on the expected protocol. 0293 0294 @param stream The stream to read from. This type must meet the 0295 requirements of <em>AsyncReadStream</em>. 0296 0297 @param buffer The dynamic buffer to use. This type must meet the 0298 requirements of <em>DynamicBuffer</em>. 0299 0300 @param token The completion token used to determine the method 0301 used to provide the result of the asynchronous operation. If 0302 this is a completion handler, the implementation takes ownership 0303 of the handler by performing a decay-copy, and the equivalent 0304 function signature of the handler must be: 0305 @code 0306 void handler( 0307 error_code const& error, // Set to the error, if any 0308 bool result // The result of the detector 0309 ); 0310 @endcode 0311 If the handler has an associated immediate executor, 0312 an immediate completion will be dispatched to it. 0313 Otherwise, the handler will not be invoked from within 0314 this function. Invocation of the handler will be performed in a 0315 manner equivalent to using `net::post`. 0316 */ 0317 template< 0318 class AsyncReadStream, 0319 class DynamicBuffer, 0320 class CompletionToken = 0321 net::default_completion_token_t<beast::executor_type<AsyncReadStream>> 0322 > 0323 BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool)) 0324 async_detect_ssl( 0325 AsyncReadStream& stream, 0326 DynamicBuffer& buffer, 0327 CompletionToken&& token = net::default_completion_token_t< 0328 beast::executor_type<AsyncReadStream>>{}); 0329 //] 0330 0331 //[example_core_detect_ssl_5 0332 0333 // These implementation details don't need to be public 0334 0335 namespace detail { 0336 0337 // The composed operation object 0338 template< 0339 class DetectHandler, 0340 class AsyncReadStream, 0341 class DynamicBuffer> 0342 class detect_ssl_op; 0343 0344 // This is a function object which `net::async_initiate` can use to launch 0345 // our composed operation. This is a relatively new feature in networking 0346 // which allows the asynchronous operation to be "lazily" executed (meaning 0347 // that it is launched later). Users don't need to worry about this, but 0348 // authors of composed operations need to write it this way to get the 0349 // very best performance, for example when using Coroutines TS (`co_await`). 0350 0351 struct run_detect_ssl_op 0352 { 0353 // The implementation of `net::async_initiate` captures the 0354 // arguments of the initiating function, and then calls this 0355 // function object later with the captured arguments in order 0356 // to launch the composed operation. All we need to do here 0357 // is take those arguments and construct our composed operation 0358 // object. 0359 // 0360 // `async_initiate` takes care of transforming the completion 0361 // token into the "real handler" which must have the correct 0362 // signature, in this case `void(error_code, boost::tri_bool)`. 0363 0364 template< 0365 class DetectHandler, 0366 class AsyncReadStream, 0367 class DynamicBuffer> 0368 void operator()( 0369 DetectHandler&& h, 0370 AsyncReadStream* s, // references are passed as pointers 0371 DynamicBuffer* b) 0372 { 0373 detect_ssl_op< 0374 typename std::decay<DetectHandler>::type, 0375 AsyncReadStream, 0376 DynamicBuffer>( 0377 std::forward<DetectHandler>(h), *s, *b); 0378 } 0379 }; 0380 0381 } // detail 0382 0383 //] 0384 0385 //[example_core_detect_ssl_6 0386 0387 // Here is the implementation of the asynchronous initiation function 0388 template< 0389 class AsyncReadStream, 0390 class DynamicBuffer, 0391 class CompletionToken> 0392 BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool)) 0393 async_detect_ssl( 0394 AsyncReadStream& stream, 0395 DynamicBuffer& buffer, 0396 CompletionToken&& token) 0397 { 0398 // Make sure arguments meet the type requirements 0399 0400 static_assert( 0401 is_async_read_stream<AsyncReadStream>::value, 0402 "SyncReadStream type requirements not met"); 0403 0404 static_assert( 0405 net::is_dynamic_buffer<DynamicBuffer>::value, 0406 "DynamicBuffer type requirements not met"); 0407 0408 // The function `net::async_initate` uses customization points 0409 // to allow one asynchronous initiating function to work with 0410 // all sorts of notification systems, such as callbacks but also 0411 // fibers, futures, coroutines, and user-defined types. 0412 // 0413 // It works by capturing all of the arguments using perfect 0414 // forwarding, and then depending on the specialization of 0415 // `net::async_result` for the type of `CompletionToken`, 0416 // the `initiation` object will be invoked with the saved 0417 // parameters and the actual completion handler. Our 0418 // initiating object is `run_detect_ssl_op`. 0419 // 0420 // Non-const references need to be passed as pointers, 0421 // since we don't want a decay-copy. 0422 0423 return net::async_initiate< 0424 CompletionToken, 0425 void(error_code, bool)>( 0426 detail::run_detect_ssl_op{}, 0427 token, 0428 &stream, // pass the reference by pointer 0429 &buffer); 0430 } 0431 0432 //] 0433 0434 //[example_core_detect_ssl_7 0435 0436 namespace detail { 0437 0438 // Read from a stream, calling is_tls_client_hello on the data 0439 // data to determine if the TLS client handshake is present. 0440 // 0441 // This will be implemented using Asio's "stackless coroutines" 0442 // which are based on macros forming a switch statement. The 0443 // operation is derived from `coroutine` for this reason. 0444 // 0445 // The library type `async_base` takes care of all of the 0446 // boilerplate for writing composed operations, including: 0447 // 0448 // * Storing the user's completion handler 0449 // * Maintaining the work guard for the handler's associated executor 0450 // * Propagating the associated allocator of the handler 0451 // * Propagating the associated executor of the handler 0452 // * Deallocating temporary storage before invoking the handler 0453 // * Posting the handler to the executor on an immediate completion 0454 // 0455 // `async_base` needs to know the type of the handler, as well 0456 // as the executor of the I/O object being used. The metafunction 0457 // `executor_type` returns the type of executor used by an 0458 // I/O object. 0459 // 0460 template< 0461 class DetectHandler, 0462 class AsyncReadStream, 0463 class DynamicBuffer> 0464 class detect_ssl_op 0465 : public boost::asio::coroutine 0466 , public async_base< 0467 DetectHandler, executor_type<AsyncReadStream>> 0468 { 0469 // This composed operation has trivial state, 0470 // so it is just kept inside the class and can 0471 // be cheaply copied as needed by the implementation. 0472 0473 AsyncReadStream& stream_; 0474 0475 // The callers buffer is used to hold all received data 0476 DynamicBuffer& buffer_; 0477 0478 // We're going to need this in case we have to post the handler 0479 error_code ec_; 0480 0481 boost::tribool result_ = false; 0482 0483 public: 0484 // Completion handlers must be MoveConstructible. 0485 detect_ssl_op(detect_ssl_op&&) = default; 0486 0487 // Construct the operation. The handler is deduced through 0488 // the template type `DetectHandler_`, this lets the same constructor 0489 // work properly for both lvalues and rvalues. 0490 // 0491 template<class DetectHandler_> 0492 detect_ssl_op( 0493 DetectHandler_&& handler, 0494 AsyncReadStream& stream, 0495 DynamicBuffer& buffer) 0496 : beast::async_base< 0497 DetectHandler, 0498 beast::executor_type<AsyncReadStream>>( 0499 std::forward<DetectHandler_>(handler), 0500 stream.get_executor()) 0501 , stream_(stream) 0502 , buffer_(buffer) 0503 { 0504 // This starts the operation. We pass `false` to tell the 0505 // algorithm that it needs to use net::post if it wants to 0506 // complete immediately. This is required by Networking, 0507 // as initiating functions are not allowed to invoke the 0508 // completion handler on the caller's thread before 0509 // returning. 0510 (*this)({}, 0, false); 0511 } 0512 0513 // Our main entry point. This will get called as our 0514 // intermediate operations complete. Definition below. 0515 // 0516 // The parameter `cont` indicates if we are being called subsequently 0517 // from the original invocation 0518 // 0519 void operator()( 0520 error_code ec, 0521 std::size_t bytes_transferred, 0522 bool cont = true); 0523 }; 0524 0525 } // detail 0526 0527 //] 0528 0529 //[example_core_detect_ssl_8 0530 0531 namespace detail { 0532 0533 // This example uses the Asio's stackless "fauxroutines", implemented 0534 // using a macro-based solution. It makes the code easier to write and 0535 // easier to read. This include file defines the necessary macros and types. 0536 #include <boost/asio/yield.hpp> 0537 0538 // detect_ssl_op is callable with the signature void(error_code, bytes_transferred), 0539 // allowing `*this` to be used as a ReadHandler 0540 // 0541 template< 0542 class AsyncStream, 0543 class DynamicBuffer, 0544 class Handler> 0545 void 0546 detect_ssl_op<AsyncStream, DynamicBuffer, Handler>:: 0547 operator()(error_code ec, std::size_t bytes_transferred, bool cont) 0548 { 0549 namespace beast = boost::beast; 0550 0551 // This introduces the scope of the stackless coroutine 0552 reenter(*this) 0553 { 0554 // Loop until an error occurs or we get a definitive answer 0555 for(;;) 0556 { 0557 // There could already be a hello in the buffer so check first 0558 result_ = is_tls_client_hello(buffer_.data()); 0559 0560 // If we got an answer, then the operation is complete 0561 if(! boost::indeterminate(result_)) 0562 break; 0563 0564 // Try to fill our buffer by reading from the stream. 0565 // The function read_size calculates a reasonable size for the 0566 // amount to read next, using existing capacity if possible to 0567 // avoid allocating memory, up to the limit of 1536 bytes which 0568 // is the size of a normal TCP frame. 0569 // 0570 // `async_read_some` expects a ReadHandler as the completion 0571 // handler. The signature of a read handler is void(error_code, size_t), 0572 // and this function matches that signature (the `cont` parameter has 0573 // a default of true). We pass `std::move(*this)` as the completion 0574 // handler for the read operation. This transfers ownership of this 0575 // entire state machine back into the `async_read_some` operation. 0576 // Care must be taken with this idiom, to ensure that parameters 0577 // passed to the initiating function which could be invalidated 0578 // by the move, are first moved to the stack before calling the 0579 // initiating function. 0580 0581 yield 0582 { 0583 // This macro facilitates asynchrnous handler tracking and 0584 // debugging when the preprocessor macro 0585 // BOOST_ASIO_CUSTOM_HANDLER_TRACKING is defined. 0586 0587 BOOST_ASIO_HANDLER_LOCATION(( 0588 __FILE__, __LINE__, 0589 "async_detect_ssl")); 0590 0591 stream_.async_read_some(buffer_.prepare( 0592 read_size(buffer_, 1536)), std::move(*this)); 0593 } 0594 0595 // Commit what we read into the buffer's input area. 0596 buffer_.commit(bytes_transferred); 0597 0598 // Check for an error 0599 if(ec) 0600 break; 0601 } 0602 0603 // If `cont` is true, the handler will be invoked directly. 0604 // 0605 // Otherwise, the handler cannot be invoked directly, because 0606 // initiating functions are not allowed to call the handler 0607 // before returning. Instead, the handler must be posted to 0608 // the I/O context. We issue a zero-byte read using the same 0609 // type of buffers used in the ordinary read above, to prevent 0610 // the compiler from creating an extra instantiation of the 0611 // function template. This reduces compile times and the size 0612 // of the program executable. 0613 0614 if(! cont) 0615 { 0616 // Save the error, otherwise it will be overwritten with 0617 // a successful error code when this read completes 0618 // immediately. 0619 ec_ = ec; 0620 0621 // Zero-byte reads and writes are guaranteed to complete 0622 // immediately with succcess. The type of buffers and the 0623 // type of handler passed here need to exactly match the types 0624 // used in the call to async_read_some above, to avoid 0625 // instantiating another version of the function template. 0626 0627 yield 0628 { 0629 BOOST_ASIO_HANDLER_LOCATION(( 0630 __FILE__, __LINE__, 0631 "async_detect_ssl")); 0632 0633 stream_.async_read_some(buffer_.prepare(0), std::move(*this)); 0634 } 0635 0636 // Restore the saved error code 0637 BOOST_BEAST_ASSIGN_EC(ec, ec_); 0638 } 0639 0640 // Invoke the final handler. 0641 // At this point, we are guaranteed that the original initiating 0642 // function is no longer on our stack frame. 0643 0644 this->complete_now(ec, static_cast<bool>(result_)); 0645 } 0646 } 0647 0648 // Including this file undefines the macros used by the stackless fauxroutines. 0649 #include <boost/asio/unyield.hpp> 0650 0651 } // detail 0652 0653 //] 0654 0655 } // beast 0656 } // boost 0657 0658 #endif
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |