diff --git a/doc/qbk/03_core/9_ssl-tls-shutdown.qbk b/doc/qbk/03_core/9_ssl-tls-shutdown.qbk new file mode 100644 index 00000000..a47fe62b --- /dev/null +++ b/doc/qbk/03_core/9_ssl-tls-shutdown.qbk @@ -0,0 +1,69 @@ +[/ + Copyright (c) 2024 Mohammad Nejati + + 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 +] + +[section SSL/TLS Shutdown] +[block''''''] + +A secure SSL/TLS connection requires a proper shutdown process to securely +indicate the [@https://en.wikipedia.org/wiki/End-of-file ['EOF]] condition. +This process prevents a type of attack known as a +[@https://en.wikipedia.org/wiki/Transport_Layer_Security#Truncation_attack ['truncation attack]] +in which an attacker can close the underlying transport layer and control +the length of the last message in the SSL/TLS connection. A shutdown process +consists of exchanging `close_notify` message between two parties. In __Asio__ +these steps happen by calling `shutdown()` or `async_shutdown()` on +`ssl::stream` object. + +[/-----------------------------------------------------------------------------] + +[section error::stream_truncated] + +There are SSL/TLS implementations that don't perform a proper shutdown process +and simply close the underlying transport layer instead. As a result, the EOF +condition in these applications is not cryptographically secure and should not +be relied upon. However, there are scenarios where an HTTPS client or server +doesn't need EOF for determining the end of the last message: + +* The HTTP message has a `Content-Length` header, and the body is fully + received (a known body length). + +* The HTTP message uses chunked transfer encoding, and the final chunk is + received. + +* The HTTP message doesn't contain a body, such as any response with a 1xx + (Informational), 204 (No Content), or 304 (Not Modified) status code. + +In such scenarios, `http::read` or `http::async_read` operations succeed as +they don't need EOF to complete. However, the next operation on the stream +would fail with an +[@boost:/doc/html/boost_asio/reference/ssl__error__stream_errors.html `net::ssl::error::stream_truncated`] +error. + +For example, let's assume we are using Beast for communicating with an HTTPS +server that doesn't perform a proper SSL/TLS shutdown: + +[snippet_core_4] + +[/-----------------------------------------------------------------------------] + +[heading Non-Compliant Peers and Unknown Body Length] + +This is a rare case and indeed a security issue when HTTPS servers don't +perform a proper SSL/TLS shutdown procedure and send an HTTP response message +that relies on EOF to determine the end of the body. This is a security concern +because without an SSL/TLS shutdown procedure, the EOF is not cryptographically +secure, leaving the message body vulnerable to truncation attacks. + +The following is an example that can read an HTTP response from such a server: + +[snippet_core_5] + +[endsect] + +[endsect] diff --git a/doc/qbk/03_core/_core.qbk b/doc/qbk/03_core/_core.qbk index 37e9d011..13934fca 100644 --- a/doc/qbk/03_core/_core.qbk +++ b/doc/qbk/03_core/_core.qbk @@ -96,6 +96,7 @@ effect: [include 5_buffers.qbk] [include 6_files.qbk] [include 7_composed.qbk] +[include 9_ssl-tls-shutdown.qbk] [endsect] [section:config Configuration] diff --git a/test/doc/core_snippets.cpp b/test/doc/core_snippets.cpp index 50dcf49e..54a86207 100644 --- a/test/doc/core_snippets.cpp +++ b/test/doc/core_snippets.cpp @@ -17,6 +17,8 @@ namespace boost { namespace asio { namespace ssl { } } } //[snippet_core_1a #include +#include +#include #include #include #include @@ -83,4 +85,63 @@ void write_string(SyncWriteStream& stream, string_view s) //] +void ssl_tls_shutdown() +{ + net::io_context ioc; + net::ssl::context ctx(net::ssl::context::tlsv12); + ssl_stream stream(ioc, ctx); + flat_buffer buffer; + http::response res; + auto log = [](error_code){}; + + { +//[snippet_core_4 +// Receive the HTTP response +http::read(stream, buffer, res); + +// Gracefully shutdown the SSL/TLS connection +error_code ec; +stream.shutdown(ec); +// Non-compliant servers don't participate in the SSL/TLS shutdown process and +// close the underlying transport layer. This causes the shutdown operation to +// complete with a `stream_truncated` error. One might decide not to log such +// errors as there are many non-compliant servers in the wild. +if(ec != net::ssl::error::stream_truncated) + log(ec); +//] + } + + { +//[snippet_core_5 +// Use an HTTP response parser to have more control +http::response_parser parser; + +error_code ec; +// Receive the HTTP response until the end or until an error occurs +http::read(stream, buffer, parser, ec); + +// Try to manually commit the EOF, the resulting message body would be +// vulnerable to truncation attacks. +if(parser.need_eof() && ec == net::ssl::error::stream_truncated) + parser.put_eof(ec); // Override the error_code + +if(ec) + throw system_error{ec}; + +// Access the HTTP response inside the parser +std::cout << parser.get() << std::endl; + + +// Gracefully shutdown the SSL/TLS connection +stream.shutdown(ec); // Override the error_code +// Non-compliant servers don't participate in the SSL/TLS shutdown process and +// close the underlying transport layer. This causes the shutdown operation to +// complete with a `stream_truncated` error. One might decide not to log such +// errors as there are many non-compliant servers in the wild. +if(ec != net::ssl::error::stream_truncated) + log(ec); +//] + } +} + } // doc_core_snippets