SSL and TLS

265

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.

Lesson 29
Share this page:

Learning plan

How to deal with read and write functions properly to gain desired I/O behavior
How to deal with Boost.Asio I/O free functions: async_read, async_read_until and async_write
Several additional tips on dealing with Boost.Asio I/O free functions
28. SSL and TLS
How to deal with secure connections with Boost.Asio and OpenSSL
A short break before we go into Boost.Asio application design principles
A short notes on Boost.Asio server application quality issues
Simple straightforward implementation and discussion of TCP echo server