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(x: 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 If the handler has an associated immediate executor,
312 an immediate completion will be dispatched to it.
313 Otherwise, the handler will not be invoked from within
314 this function. Invocation of the handler will be performed in a
315 manner equivalent to using `net::post`.
316*/
317template<
318 class AsyncReadStream,
319 class DynamicBuffer,
320 class CompletionToken =
321 net::default_completion_token_t<beast::executor_type<AsyncReadStream>>
322>
323BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool))
324async_detect_ssl(
325 AsyncReadStream& stream,
326 DynamicBuffer& buffer,
327 CompletionToken&& token = net::default_completion_token_t<
328 beast::executor_type<AsyncReadStream>>{});
329//]
330
331//[example_core_detect_ssl_5
332
333// These implementation details don't need to be public
334
335namespace detail {
336
337// The composed operation object
338template<
339 class DetectHandler,
340 class AsyncReadStream,
341 class DynamicBuffer>
342class detect_ssl_op;
343
344// This is a function object which `net::async_initiate` can use to launch
345// our composed operation. This is a relatively new feature in networking
346// which allows the asynchronous operation to be "lazily" executed (meaning
347// that it is launched later). Users don't need to worry about this, but
348// authors of composed operations need to write it this way to get the
349// very best performance, for example when using Coroutines TS (`co_await`).
350
351struct run_detect_ssl_op
352{
353 // The implementation of `net::async_initiate` captures the
354 // arguments of the initiating function, and then calls this
355 // function object later with the captured arguments in order
356 // to launch the composed operation. All we need to do here
357 // is take those arguments and construct our composed operation
358 // object.
359 //
360 // `async_initiate` takes care of transforming the completion
361 // token into the "real handler" which must have the correct
362 // signature, in this case `void(error_code, boost::tri_bool)`.
363
364 template<
365 class DetectHandler,
366 class AsyncReadStream,
367 class DynamicBuffer>
368 void operator()(
369 DetectHandler&& h,
370 AsyncReadStream* s, // references are passed as pointers
371 DynamicBuffer* b)
372 {
373 detect_ssl_op<
374 typename std::decay<DetectHandler>::type,
375 AsyncReadStream,
376 DynamicBuffer>(
377 std::forward<DetectHandler>(h), *s, *b);
378 }
379};
380
381} // detail
382
383//]
384
385//[example_core_detect_ssl_6
386
387// Here is the implementation of the asynchronous initiation function
388template<
389 class AsyncReadStream,
390 class DynamicBuffer,
391 class CompletionToken>
392BOOST_ASIO_INITFN_AUTO_RESULT_TYPE(CompletionToken, void(error_code, bool))
393async_detect_ssl(
394 AsyncReadStream& stream,
395 DynamicBuffer& buffer,
396 CompletionToken&& token)
397{
398 // Make sure arguments meet the type requirements
399
400 static_assert(
401 is_async_read_stream<AsyncReadStream>::value,
402 "SyncReadStream type requirements not met");
403
404 static_assert(
405 net::is_dynamic_buffer<DynamicBuffer>::value,
406 "DynamicBuffer type requirements not met");
407
408 // The function `net::async_initate` uses customization points
409 // to allow one asynchronous initiating function to work with
410 // all sorts of notification systems, such as callbacks but also
411 // fibers, futures, coroutines, and user-defined types.
412 //
413 // It works by capturing all of the arguments using perfect
414 // forwarding, and then depending on the specialization of
415 // `net::async_result` for the type of `CompletionToken`,
416 // the `initiation` object will be invoked with the saved
417 // parameters and the actual completion handler. Our
418 // initiating object is `run_detect_ssl_op`.
419 //
420 // Non-const references need to be passed as pointers,
421 // since we don't want a decay-copy.
422
423 return net::async_initiate<
424 CompletionToken,
425 void(error_code, bool)>(
426 detail::run_detect_ssl_op{},
427 token,
428 &stream, // pass the reference by pointer
429 &buffer);
430}
431
432//]
433
434//[example_core_detect_ssl_7
435
436namespace detail {
437
438// Read from a stream, calling is_tls_client_hello on the data
439// data to determine if the TLS client handshake is present.
440//
441// This will be implemented using Asio's "stackless coroutines"
442// which are based on macros forming a switch statement. The
443// operation is derived from `coroutine` for this reason.
444//
445// The library type `async_base` takes care of all of the
446// boilerplate for writing composed operations, including:
447//
448// * Storing the user's completion handler
449// * Maintaining the work guard for the handler's associated executor
450// * Propagating the associated allocator of the handler
451// * Propagating the associated executor of the handler
452// * Deallocating temporary storage before invoking the handler
453// * Posting the handler to the executor on an immediate completion
454//
455// `async_base` needs to know the type of the handler, as well
456// as the executor of the I/O object being used. The metafunction
457// `executor_type` returns the type of executor used by an
458// I/O object.
459//
460template<
461 class DetectHandler,
462 class AsyncReadStream,
463 class DynamicBuffer>
464class detect_ssl_op
465 : public boost::asio::coroutine
466 , public async_base<
467 DetectHandler, executor_type<AsyncReadStream>>
468{
469 // This composed operation has trivial state,
470 // so it is just kept inside the class and can
471 // be cheaply copied as needed by the implementation.
472
473 AsyncReadStream& stream_;
474
475 // The callers buffer is used to hold all received data
476 DynamicBuffer& buffer_;
477
478 // We're going to need this in case we have to post the handler
479 error_code ec_;
480
481 boost::tribool result_ = false;
482
483public:
484 // Completion handlers must be MoveConstructible.
485 detect_ssl_op(detect_ssl_op&&) = default;
486
487 // Construct the operation. The handler is deduced through
488 // the template type `DetectHandler_`, this lets the same constructor
489 // work properly for both lvalues and rvalues.
490 //
491 template<class DetectHandler_>
492 detect_ssl_op(
493 DetectHandler_&& handler,
494 AsyncReadStream& stream,
495 DynamicBuffer& buffer)
496 : beast::async_base<
497 DetectHandler,
498 beast::executor_type<AsyncReadStream>>(
499 std::forward<DetectHandler_>(handler),
500 stream.get_executor())
501 , stream_(stream)
502 , buffer_(buffer)
503 {
504 // This starts the operation. We pass `false` to tell the
505 // algorithm that it needs to use net::post if it wants to
506 // complete immediately. This is required by Networking,
507 // as initiating functions are not allowed to invoke the
508 // completion handler on the caller's thread before
509 // returning.
510 (*this)({}, 0, false);
511 }
512
513 // Our main entry point. This will get called as our
514 // intermediate operations complete. Definition below.
515 //
516 // The parameter `cont` indicates if we are being called subsequently
517 // from the original invocation
518 //
519 void operator()(
520 error_code ec,
521 std::size_t bytes_transferred,
522 bool cont = true);
523};
524
525} // detail
526
527//]
528
529//[example_core_detect_ssl_8
530
531namespace detail {
532
533// This example uses the Asio's stackless "fauxroutines", implemented
534// using a macro-based solution. It makes the code easier to write and
535// easier to read. This include file defines the necessary macros and types.
536#include <boost/asio/yield.hpp>
537
538// detect_ssl_op is callable with the signature void(error_code, bytes_transferred),
539// allowing `*this` to be used as a ReadHandler
540//
541template<
542 class AsyncStream,
543 class DynamicBuffer,
544 class Handler>
545void
546detect_ssl_op<AsyncStream, DynamicBuffer, Handler>::
547operator()(error_code ec, std::size_t bytes_transferred, bool cont)
548{
549 namespace beast = boost::beast;
550
551 // This introduces the scope of the stackless coroutine
552 reenter(*this)
553 {
554 // Loop until an error occurs or we get a definitive answer
555 for(;;)
556 {
557 // There could already be a hello in the buffer so check first
558 result_ = is_tls_client_hello(buffer_.data());
559
560 // If we got an answer, then the operation is complete
561 if(! boost::indeterminate(x: result_))
562 break;
563
564 // Try to fill our buffer by reading from the stream.
565 // The function read_size calculates a reasonable size for the
566 // amount to read next, using existing capacity if possible to
567 // avoid allocating memory, up to the limit of 1536 bytes which
568 // is the size of a normal TCP frame.
569 //
570 // `async_read_some` expects a ReadHandler as the completion
571 // handler. The signature of a read handler is void(error_code, size_t),
572 // and this function matches that signature (the `cont` parameter has
573 // a default of true). We pass `std::move(*this)` as the completion
574 // handler for the read operation. This transfers ownership of this
575 // entire state machine back into the `async_read_some` operation.
576 // Care must be taken with this idiom, to ensure that parameters
577 // passed to the initiating function which could be invalidated
578 // by the move, are first moved to the stack before calling the
579 // initiating function.
580
581 yield
582 {
583 // This macro facilitates asynchrnous handler tracking and
584 // debugging when the preprocessor macro
585 // BOOST_ASIO_CUSTOM_HANDLER_TRACKING is defined.
586
587 BOOST_ASIO_HANDLER_LOCATION((
588 __FILE__, __LINE__,
589 "async_detect_ssl"));
590
591 stream_.async_read_some(buffer_.prepare(
592 read_size(buffer_, 1536)), std::move(*this));
593 }
594
595 // Commit what we read into the buffer's input area.
596 buffer_.commit(bytes_transferred);
597
598 // Check for an error
599 if(ec)
600 break;
601 }
602
603 // If `cont` is true, the handler will be invoked directly.
604 //
605 // Otherwise, the handler cannot be invoked directly, because
606 // initiating functions are not allowed to call the handler
607 // before returning. Instead, the handler must be posted to
608 // the I/O context. We issue a zero-byte read using the same
609 // type of buffers used in the ordinary read above, to prevent
610 // the compiler from creating an extra instantiation of the
611 // function template. This reduces compile times and the size
612 // of the program executable.
613
614 if(! cont)
615 {
616 // Save the error, otherwise it will be overwritten with
617 // a successful error code when this read completes
618 // immediately.
619 ec_ = ec;
620
621 // Zero-byte reads and writes are guaranteed to complete
622 // immediately with succcess. The type of buffers and the
623 // type of handler passed here need to exactly match the types
624 // used in the call to async_read_some above, to avoid
625 // instantiating another version of the function template.
626
627 yield
628 {
629 BOOST_ASIO_HANDLER_LOCATION((
630 __FILE__, __LINE__,
631 "async_detect_ssl"));
632
633 stream_.async_read_some(buffer_.prepare(0), std::move(*this));
634 }
635
636 // Restore the saved error code
637 BOOST_BEAST_ASSIGN_EC(ec, ec_);
638 }
639
640 // Invoke the final handler.
641 // At this point, we are guaranteed that the original initiating
642 // function is no longer on our stack frame.
643
644 this->complete_now(ec, static_cast<bool>(result_));
645 }
646}
647
648// Including this file undefines the macros used by the stackless fauxroutines.
649#include <boost/asio/unyield.hpp>
650
651} // detail
652
653//]
654
655} // beast
656} // boost
657
658#endif
659

source code of boost/libs/beast/include/boost/beast/core/detect_ssl.hpp