SSL and TLS
To enclose this part of lessons which is dedicated to buffers and I/O operations entirely, let's take a very brief look on how to deal with encrypted I/O in Boost.Asio.
So, SSL or TLS?
When people say “SSL” they usually mean data encryption based on SSL-family standards in general. However SSL itself, which stands for “Secure Sockets Layer”, is in fact a particular standard which had three major revisions, and all of them are depreceted long time ago. TLS is the next standard which pre-1.2 versions are already deprecated as well. As of 2020, TLS 1.2 is still in use, but I believe it also will be deprecated very soon. TLS 1.3 is available since 2018, so currently it's still pretty fresh. More information on standards stuff can be found on Wikipedia: Transport Layer Security
However if you're not an encryption library developer but rather its user, you don't have to bother yourself on all these versions stuff. Usually all you need is to make sure that you're up-to-date and aren't using some deprecated version of the standard. So in this tutorial set when I'll write “SSL” I'll mean SSL-family standard which is currently in use, but not some particular deprecated version of the standard.
SSL I/O
In Boost.Asio SSL is provided as a higher level abstraction over the OpenSSL library. OpenSSL is quite a big fish, and it deserves its own book, not just a single tutorial lesson.
In Boost.Asio stream is a concept. One stream can be wrapped into another. TCP socket is a stream. SSL is a stream template. To deal with SSL in Boost.Asio you should wrap TCP socket into SSL stream:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
using ssl_socket = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
Now an instance of this stream can be used with async_read
, async_read_until
and async_write
free functions in the same manner as we've just learned in the previous lessons. You can always access wrapped socket with next_layer
member function.
A slightly different part is how you create sockets, connect them or accept incoming connections.
Whenever you deal with SSL in Boost.Asio, first thing you need is SSL context class instance:
boost::asio::ssl::context ssl_context(boost::asio::ssl::context::tls);
SSL context is used to construct SSL sockets and to configure and control underlying OpenSSL behavior. We will learn how to work with SSL in Boost.Asio in details some later. Now we'll take a brief look. Having boost::asio::io_context
and boost::asio::ssl::context
classes instances we can construct SSL sockets:
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
using ssl_socket = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
int main()
{
boost::asio::io_context io_context;
boost::asio::ssl::context ssl_context(boost::asio::ssl::context::tls);
ssl_socket socket(io_context, ssl_context);
return 0;
}
Client approach
First thing you need is to connect your socket somewhere. That should be done in the same way you do with usual TCP sockets:
ssl_socket socket(io_context, ssl_context);
boost::asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("google.com", "443");
// We use next_layer member function to get wrapped TCP socket reference
boost::asio::connect(socket.next_layer(), endpoints);
Next thing you have to do is perform SSL handshake operation:
// Sychronous approach
socket.handshake(boost::asio::ssl::stream_base::client);
// Asychronous approach
socket.async_handshake(boost::asio::ssl::stream_base::client, [&] (boost::system::error_code error)
{
// Handshake is done
});
You should specify handshake type with handshake
function argument, meaning which role does your applicaion play, a client or a server. There are two possible values for this parameter: stream_base::client
and stream_base::server
. In the example above we used stream_base::client
argument value.
After handshake operation is completed, you can use Boost.Asio I/O functions in the same way you do with regular TCP sockets.
Server approach
Similar to regular TCP server, you should start with accepting of incoming TCP connection:
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 8088);
boost::asio::ip::tcp::acceptor acceptor(io_context, endpoint);
ssl_socket socket(io_context, ssl_context);
acceptor.accept(socket.next_layer());
After you accepted incoming connection, you should perform SSL handshake operation and pass stream_base::server
as handshake type argument:
socket.handshake(boost::asio::ssl::stream_base::server);
After that you can do I/O as usual.
Complete example
Very simple yet complete example of a client which performs HTTP GET request to Google and writes the response into stdout looks like that:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
namespace io = boost::asio;
namespace ip = io::ip;
using tcp = ip::tcp;
using error_code = boost::system::error_code;
// Here we go
namespace ssl = io::ssl;
using ssl_socket = ssl::stream<tcp::socket>;
int main(int argc, char* argv[])
{
io::io_context io_context;
ssl::context ssl_context(ssl::context::tls);
ssl_socket socket(io_context, ssl_context);
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("google.com", "443");
io::connect(socket.next_layer(), endpoints);
socket.handshake(ssl::stream_base::client);
char request[] =
"GET / HTTP/1.1\n"
"Host: www.google.com\n"
"Connection: close\n\n";
io::write(socket, io::buffer(request));
io::streambuf response;
error_code ec;
io::read(socket, response, ec);
std::cout << std::istream(&response).rdbuf() << "\n";
return 0;
}
For the next time
To use SSL in production code you should do additional SSL context setup for both client and server, such as SSL version restriction, remote peer validation, dealing with SSL certificates, and more. That will take some additional lessons. We will get back to SSL in Boost.Asio some later.