mirror of
https://github.com/Icinga/icinga2.git
synced 2025-08-24 19:18:15 +02:00
480 lines
16 KiB
C++
480 lines
16 KiB
C++
//
|
|
// Copyright (c) 2020 Kasper Laudrup (laudrup at stacktrace dot dk)
|
|
//
|
|
// 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)
|
|
//
|
|
|
|
#ifndef BOOST_WINTLS_STREAM_HPP
|
|
#define BOOST_WINTLS_STREAM_HPP
|
|
|
|
#include <boost/wintls/error.hpp>
|
|
#include <boost/wintls/handshake_type.hpp>
|
|
|
|
#include <boost/wintls/detail/async_handshake.hpp>
|
|
#include <boost/wintls/detail/async_read.hpp>
|
|
#include <boost/wintls/detail/async_shutdown.hpp>
|
|
#include <boost/wintls/detail/async_write.hpp>
|
|
#include <boost/wintls/detail/sspi_stream.hpp>
|
|
|
|
#include <boost/asio/compose.hpp>
|
|
#include <boost/asio/io_context.hpp>
|
|
#include <boost/system/error_code.hpp>
|
|
|
|
#include <memory>
|
|
|
|
namespace boost {
|
|
namespace wintls {
|
|
|
|
/** Provides stream-oriented functionality using Windows SSPI/Schannel.
|
|
*
|
|
* The stream class template provides asynchronous and blocking
|
|
* stream-oriented functionality using Windows SSPI/Schannel.
|
|
*
|
|
* @tparam NextLayer The type representing the next layer, to which
|
|
* data will be read and written during operations. For synchronous
|
|
* operations, the type must support the <em>SyncStream</em> concept.
|
|
* For asynchronous operations, the type must support the
|
|
* <em>AsyncStream</em> concept.
|
|
*/
|
|
template<class NextLayer>
|
|
class stream {
|
|
public:
|
|
/// The type of the next layer.
|
|
using next_layer_type = typename std::remove_reference<NextLayer>::type;
|
|
|
|
/// The type of the executor associated with the object.
|
|
using executor_type = typename std::remove_reference<next_layer_type>::type::executor_type;
|
|
|
|
/** Construct a stream.
|
|
*
|
|
* This constructor creates a stream and initialises the underlying
|
|
* stream object.
|
|
*
|
|
* @param arg The argument to be passed to initialise the
|
|
* underlying stream.
|
|
* @param ctx The wintls @ref context to be used for the stream.
|
|
*/
|
|
template <class Arg>
|
|
stream(Arg&& arg, context& ctx)
|
|
: next_layer_(std::forward<Arg>(arg))
|
|
, sspi_stream_(std::make_unique<detail::sspi_stream>(ctx)) {
|
|
}
|
|
|
|
/** Get the executor associated with the object.
|
|
*
|
|
* This function may be used to obtain the executor object that the
|
|
* stream uses to dispatch handlers for asynchronous operations.
|
|
*
|
|
* @return A copy of the executor that stream will use to dispatch
|
|
* handlers.
|
|
*/
|
|
executor_type get_executor() {
|
|
return next_layer().get_executor();
|
|
}
|
|
|
|
/** Get a reference to the next layer.
|
|
*
|
|
* This function returns a reference to the next layer in a stack of
|
|
* stream layers.
|
|
*
|
|
* @return A reference to the next layer in the stack of stream
|
|
* layers. Ownership is not transferred to the caller.
|
|
*/
|
|
const next_layer_type& next_layer() const {
|
|
return next_layer_;
|
|
}
|
|
|
|
/** Get a reference to the next layer.
|
|
*
|
|
* This function returns a reference to the next layer in a stack of
|
|
* stream layers.
|
|
*
|
|
* @return A reference to the next layer in the stack of stream
|
|
* layers. Ownership is not transferred to the caller.
|
|
*/
|
|
next_layer_type& next_layer() {
|
|
return next_layer_;
|
|
}
|
|
|
|
/** Set SNI hostname
|
|
*
|
|
* Sets the SNI hostname the client will use for requesting and
|
|
* validating the server certificate.
|
|
*
|
|
* Only used when handshake is performed as @ref
|
|
* handshake_type::client
|
|
*
|
|
* @param hostname The hostname to use in certificate validation
|
|
*/
|
|
void set_server_hostname(const std::string& hostname) {
|
|
sspi_stream_->handshake.set_server_hostname(hostname);
|
|
}
|
|
|
|
/** Set revocation checking
|
|
*
|
|
* Enable revocation checking for remote certificates.
|
|
*
|
|
* Enabling this also causes the TLS certificate status request extension
|
|
* to be sent during the handshake. (I.e. we request OCSP stapling from the remote.)
|
|
*
|
|
* @param check Whether to enable revocation checking
|
|
*/
|
|
void set_certificate_revocation_check(bool check) {
|
|
sspi_stream_->handshake.set_certificate_revocation_check(check);
|
|
}
|
|
|
|
/** Perform TLS handshaking.
|
|
*
|
|
* This function is used to perform TLS handshaking on the
|
|
* stream. The function call will block until handshaking is
|
|
* complete or an error occurs.
|
|
*
|
|
* @param type The @ref handshake_type to be performed, i.e. client
|
|
* or server.
|
|
* @param ec Set to indicate what error occurred, if any.
|
|
*/
|
|
void handshake(handshake_type type, boost::system::error_code& ec) {
|
|
sspi_stream_->handshake(type);
|
|
|
|
detail::sspi_handshake::state state;
|
|
while((state = sspi_stream_->handshake()) != detail::sspi_handshake::state::done) {
|
|
switch (state) {
|
|
case detail::sspi_handshake::state::data_needed: {
|
|
std::size_t size_read = next_layer_.read_some(sspi_stream_->handshake.in_buffer(), ec);
|
|
if (ec) {
|
|
return;
|
|
}
|
|
sspi_stream_->handshake.size_read(size_read);
|
|
continue;
|
|
}
|
|
case detail::sspi_handshake::state::data_available: {
|
|
std::size_t size_written = net::write(next_layer_, sspi_stream_->handshake.out_buffer(), ec);
|
|
if (ec) {
|
|
return;
|
|
}
|
|
sspi_stream_->handshake.size_written(size_written);
|
|
continue;
|
|
}
|
|
case detail::sspi_handshake::state::error:
|
|
ec = sspi_stream_->handshake.last_error();
|
|
return;
|
|
case detail::sspi_handshake::state::done_with_data:{
|
|
std::size_t size_written = net::write(next_layer_, sspi_stream_->handshake.out_buffer(), ec);
|
|
if (ec) {
|
|
return;
|
|
}
|
|
sspi_stream_->handshake.size_written(size_written);
|
|
return;
|
|
}
|
|
case detail::sspi_handshake::state::error_with_data:{
|
|
std::size_t size_written = net::write(next_layer_, sspi_stream_->handshake.out_buffer(), ec);
|
|
if (ec) {
|
|
return;
|
|
}
|
|
sspi_stream_->handshake.size_written(size_written);
|
|
return;
|
|
}
|
|
case detail::sspi_handshake::state::done:
|
|
BOOST_UNREACHABLE_RETURN(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Perform TLS handshaking.
|
|
*
|
|
* This function is used to perform TLS handshaking on the
|
|
* stream. The function call will block until handshaking is
|
|
* complete or an error occurs.
|
|
*
|
|
* @param type The @ref handshake_type to be performed, i.e. client
|
|
* or server.
|
|
*
|
|
* @throws boost::system::system_error Thrown on failure.
|
|
*/
|
|
void handshake(handshake_type type) {
|
|
boost::system::error_code ec{};
|
|
handshake(type, ec);
|
|
if (ec) {
|
|
detail::throw_error(ec);
|
|
}
|
|
}
|
|
|
|
/** Start an asynchronous TLS handshake.
|
|
*
|
|
* This function is used to asynchronously perform an TLS
|
|
* handshake on the stream. This function call always returns
|
|
* immediately.
|
|
*
|
|
* @param type The @ref handshake_type to be performed, i.e. client
|
|
* or server.
|
|
* @param handler The handler to be called when the operation
|
|
* completes. The implementation takes ownership of the handler by
|
|
* performing a decay-copy. The handler must be invocable with this
|
|
* signature:
|
|
* @code
|
|
* void handler(
|
|
* boost::system::error_code // Result of operation.
|
|
* );
|
|
* @endcode
|
|
*
|
|
* @note 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 CompletionToken>
|
|
auto async_handshake(handshake_type type, CompletionToken&& handler) {
|
|
return boost::asio::async_compose<CompletionToken, void(boost::system::error_code)>(
|
|
detail::async_handshake<next_layer_type>{next_layer_, sspi_stream_->handshake, type}, handler);
|
|
}
|
|
|
|
/** Read some data from the stream.
|
|
*
|
|
* This function is used to read data from the stream. The function
|
|
* call will block until one or more bytes of data has been read
|
|
* successfully, or until an error occurs.
|
|
*
|
|
* @param ec Set to indicate what error occurred, if any.
|
|
* @param buffers The buffers into which the data will be read.
|
|
*
|
|
* @returns The number of bytes read.
|
|
*
|
|
* @note The `read_some` operation may not read all of the requested
|
|
* number of bytes. Consider using the `net::read` function if you
|
|
* need to ensure that the requested amount of data is read before
|
|
* the blocking operation completes.
|
|
*/
|
|
template <class MutableBufferSequence>
|
|
size_t read_some(const MutableBufferSequence& buffers, boost::system::error_code& ec) {
|
|
detail::sspi_decrypt::state state;
|
|
while((state = sspi_stream_->decrypt(buffers)) == detail::sspi_decrypt::state::data_needed) {
|
|
std::size_t size_read = next_layer_.read_some(sspi_stream_->decrypt.input_buffer, ec);
|
|
if (ec) {
|
|
return 0;
|
|
}
|
|
sspi_stream_->decrypt.size_read(size_read);
|
|
continue;
|
|
}
|
|
|
|
if (state == detail::sspi_decrypt::state::error) {
|
|
ec = sspi_stream_->decrypt.last_error();
|
|
return 0;
|
|
}
|
|
|
|
return sspi_stream_->decrypt.size_decrypted;
|
|
}
|
|
|
|
/** Read some data from the stream.
|
|
*
|
|
* This function is used to read data from the stream. The function
|
|
* call will block until one or more bytes of data has been read
|
|
* successfully, or until an error occurs.
|
|
*
|
|
* @param buffers The buffers into which the data will be read.
|
|
*
|
|
* @returns The number of bytes read.
|
|
*
|
|
* @throws boost::system::system_error Thrown on failure.
|
|
*
|
|
* @note The `read_some` operation may not read all of the requested
|
|
* number of bytes. Consider using the `net::read` function if you
|
|
* need to ensure that the requested amount of data is read before
|
|
* the blocking operation completes.
|
|
*/
|
|
template <class MutableBufferSequence>
|
|
size_t read_some(const MutableBufferSequence& buffers) {
|
|
boost::system::error_code ec{};
|
|
auto read = read_some(buffers, ec);
|
|
if (ec) {
|
|
detail::throw_error(ec);
|
|
}
|
|
return read;
|
|
}
|
|
|
|
/** Start an asynchronous read.
|
|
*
|
|
* This function is used to asynchronously read one or more bytes of
|
|
* data from the stream. The function call always returns
|
|
* immediately.
|
|
*
|
|
* @param buffers The buffers into which the data will be
|
|
* read. Although the buffers object may be copied as necessary,
|
|
* ownership of the underlying buffers is retained by the caller,
|
|
* which must guarantee that they remain valid until the handler is
|
|
* called.
|
|
* @param handler The handler to be called when the read operation
|
|
* completes. Copies will be made of the handler as required. The
|
|
* equivalent function signature of the handler must be:
|
|
* @code
|
|
* void handler(
|
|
* const boost::system::error_code& error, // Result of operation.
|
|
* std::size_t bytes_transferred // Number of bytes read.
|
|
* ); @endcode
|
|
*
|
|
* @note The `async_read_some` operation may not read all of the
|
|
* requested number of bytes. Consider using the `net::async_read`
|
|
* function if you need to ensure that the requested amount of data
|
|
* is read before the asynchronous operation completes.
|
|
*/
|
|
template <class MutableBufferSequence, class CompletionToken>
|
|
auto async_read_some(const MutableBufferSequence& buffers, CompletionToken&& handler) {
|
|
return boost::asio::async_compose<CompletionToken, void(boost::system::error_code, std::size_t)>(
|
|
detail::async_read<next_layer_type, MutableBufferSequence>{next_layer_, buffers, sspi_stream_->decrypt}, handler);
|
|
}
|
|
|
|
/** Write some data to the stream.
|
|
*
|
|
* This function is used to write data on the stream. The function
|
|
* call will block until one or more bytes of data has been written
|
|
* successfully, or until an error occurs.
|
|
*
|
|
* @param buffers The data to be written.
|
|
* @param ec Set to indicate what error occurred, if any.
|
|
*
|
|
* @returns The number of bytes written.
|
|
*
|
|
* @note The `write_some` operation may not transmit all of the data
|
|
* to the peer. Consider using the `net::write` function if you need
|
|
* to ensure that all data is written before the blocking operation
|
|
* completes.
|
|
*/
|
|
template <class ConstBufferSequence>
|
|
std::size_t write_some(const ConstBufferSequence& buffers, boost::system::error_code& ec) {
|
|
std::size_t bytes_consumed = sspi_stream_->encrypt(buffers, ec);
|
|
if (ec) {
|
|
return 0;
|
|
}
|
|
|
|
net::write(next_layer_, sspi_stream_->encrypt.buffers, ec);
|
|
if (ec) {
|
|
return 0;
|
|
}
|
|
|
|
return bytes_consumed;
|
|
}
|
|
|
|
/** Write some data to the stream.
|
|
*
|
|
* This function is used to write data on the stream. The function
|
|
* call will block until one or more bytes of data has been written
|
|
* successfully, or until an error occurs.
|
|
*
|
|
* @param buffers The data to be written.
|
|
*
|
|
* @returns The number of bytes written.
|
|
*
|
|
* @throws boost::system::system_error Thrown on failure.
|
|
*
|
|
* @note The `write_some` operation may not transmit all of the data
|
|
* to the peer. Consider using the `net::write` function if you need
|
|
* to ensure that all data is written before the blocking operation
|
|
* completes.
|
|
*/
|
|
template <class ConstBufferSequence>
|
|
std::size_t write_some(const ConstBufferSequence& buffers) {
|
|
boost::system::error_code ec{};
|
|
auto wrote = write_some(buffers, ec);
|
|
if (ec) {
|
|
detail::throw_error(ec);
|
|
}
|
|
return wrote;
|
|
}
|
|
|
|
|
|
/** Start an asynchronous write.
|
|
*
|
|
* This function is used to asynchronously write one or more bytes
|
|
* of data to the stream. The function call always returns
|
|
* immediately.
|
|
*
|
|
* @param buffers The data to be written to the stream. Although the
|
|
* buffers object may be copied as necessary, ownership of the
|
|
* underlying buffers is retained by the caller, which must
|
|
* guarantee that they remain valid until the handler is called.
|
|
* @param handler The handler to be called when the write operation
|
|
* completes. Copies will be made of the handler as required. The
|
|
* equivalent function signature of the handler must be:
|
|
* @code
|
|
* void handler(
|
|
* const boost::system::error_code& error, // Result of operation.
|
|
* std::size_t bytes_transferred // Number of bytes written.
|
|
* );
|
|
* @endcode
|
|
*
|
|
* @note The `async_write_some` operation may not transmit all of
|
|
* the data to the peer. Consider using the `net::async_write`
|
|
* function if you need to ensure that all data is written before
|
|
* the asynchronous operation completes.
|
|
*/
|
|
template <class ConstBufferSequence, class CompletionToken>
|
|
auto async_write_some(const ConstBufferSequence& buffers, CompletionToken&& handler) {
|
|
return boost::asio::async_compose<CompletionToken, void(boost::system::error_code, std::size_t)>(
|
|
detail::async_write<next_layer_type, ConstBufferSequence>{next_layer_, buffers, sspi_stream_->encrypt}, handler);
|
|
}
|
|
|
|
/** Shut down TLS on the stream.
|
|
*
|
|
* This function is used to shut down TLS on the stream. The
|
|
* function call will block until TLS has been shut down or an
|
|
* error occurs.
|
|
*
|
|
* @param ec Set to indicate what error occurred, if any.
|
|
*/
|
|
void shutdown(boost::system::error_code& ec) {
|
|
ec = sspi_stream_->shutdown();
|
|
if (ec) {
|
|
return;
|
|
}
|
|
std::size_t size_written = net::write(next_layer_, sspi_stream_->shutdown.buffer(), ec);
|
|
if (!ec) {
|
|
sspi_stream_->shutdown.size_written(size_written);
|
|
}
|
|
}
|
|
|
|
/** Shut down TLS on the stream.
|
|
*
|
|
* This function is used to shut down TLS on the stream. The
|
|
* function call will block until TLS has been shut down or an error
|
|
* occurs.
|
|
*
|
|
* @throws boost::system::system_error Thrown on failure.
|
|
*/
|
|
void shutdown() {
|
|
boost::system::error_code ec{};
|
|
shutdown(ec);
|
|
if (ec) {
|
|
detail::throw_error(ec);
|
|
}
|
|
}
|
|
|
|
/** Asynchronously shut down TLS on the stream.
|
|
*
|
|
* This function is used to asynchronously shut down TLS on the
|
|
* stream. This function call always returns immediately.
|
|
*
|
|
* @param handler The handler to be called when the handshake
|
|
* operation completes. Copies will be made of the handler as
|
|
* required. The equivalent function signature of the handler must
|
|
* be:
|
|
* @code void handler(
|
|
* const boost::system::error_code& error // Result of operation.
|
|
*);
|
|
* @endcode
|
|
*/
|
|
template <class CompletionToken>
|
|
auto async_shutdown(CompletionToken&& handler) {
|
|
return boost::asio::async_compose<CompletionToken, void(boost::system::error_code)>(
|
|
detail::async_shutdown<next_layer_type>{next_layer_, sspi_stream_->shutdown}, handler);
|
|
}
|
|
|
|
private:
|
|
NextLayer next_layer_;
|
|
std::unique_ptr<detail::sspi_stream> sspi_stream_;
|
|
};
|
|
|
|
} // namespace wintls
|
|
} // namespace boost
|
|
|
|
#endif // BOOST_WINTLS_STREAM_HPP
|