Home > Enterprise >  Boost ASIO SSL handshake failure
Boost ASIO SSL handshake failure

Time:06-30

When attempting to securely connect to a remote IMAP server using Boost ASIO, the server handshake fails on every connection. The exception message reads:

handshake: unregistered scheme (STORE routines) [asio.ssl:369098857]

My code is below (url is a std::string_view containing the host URL):

using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;

boost::asio::io_context context;
ssl::context ssl_context(ssl::context::tls);
SSLSocket socket(context, ssl_context);

ssl_context.set_default_verify_paths();

tcp::resolver resolver(context);
auto endpoints = resolver.resolve(url, "993");
boost::asio::connect(socket.next_layer(), endpoints);
socket.set_verify_mode(ssl::verify_peer);
socket.set_verify_callback(ssl::host_name_verification(url.data()));
socket.handshake(SSLSocket::client);

The code immediately throws an exception on the final line, which is a blocking synchronous handshake.

The prior two lines set up host name verification, similar to how it's done in the official ASIO tutorial. These checks seem to be causing an issue, however, because when they are removed the handshake succeeds. Obviously, this is not a good solution.

After stepping through some of ASIO's internals, I found that the last three lines of the above snippet could be replaced by:

SSL_set_verify(socket.native_handle(), SSL_VERIFY_PEER, nullptr);
socket.handshake(SSLSocket::client);

and the same error occurs. SSL_set_verify is an OpenSSL function, and the fact that setting a null callback directly causes the same issue makes me think that the issue is with my system's OpenSSL environment and not ASIO or the host name verification callback. However, I have not been able to determine what exactly the error means and what could be causing it.

Here is a list of things I have tried while troubleshooting:

  1. Load the system's certificate (.pem) file explicitly Thinking maybe ASIO and/or OpenSSL's were not able to load the right certificates to do the validation, I found my system's (a Mac) certificate file at /private/etc/ssl/cert.pem. I then inserted the following line:

    ssl_context.load_verify_file("/private/etc/ssl/cert.pem");
    

    directly after set_default_verify_paths() is called. My program loads this certificate file without complaining, but it doesn't fix the handshake error.

  2. Use a different version of OpenSSL At first I was using Apple's system version of OpenSSL (which is really LibreSSL 2.8.3). I then rebuilt my code using the Homebrew package manager's version of OpenSSL (OpenSSL 3.0.4). This also did not fix the issue, even when I tried calling load_verify_file as in point 1.

  3. Sanity check using the OpenSSL command-line tool To make sure my network connection and URL/port number were correct, I tried connecting to the IMAP server over SSL using the following command:

     openssl s_client -connect my.url.com:993 -crlf -verify 1
    

    and it worked fine, connecting to the IMAP server and enabling me to send/receive IMAP responses.

Has anyone encountered similar issues when using OpenSSL and ASIO? I'm not very familiar with setting up an SSL/TLS connection, but I don't see what could be causing the problem.

Thanks for your help!

CodePudding user response:

Given that openssl s_client -connect my.url.com:993 -crlf -verify 1 succeeds there is not a lot that seems wrong. One thing catches my eye: I'd configure the context before constructing an SSL stream from it:

ssl::context ssl_context(ssl::context::tls);

ssl_context.set_default_verify_paths();

SSLSocket socket(context, ssl_context);

Also, openssl likely uses SNI extensions:

// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
{
    throw boost::system::system_error(
        ::ERR_get_error(), boost::asio::error::get_ssl_category());
}

Finally, make sure the url string view contains correct data, notably that it's a valid hostname and null-terminated string. In this case I'd prefer to use a string representation that guarantees null-termination:

Summary

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;


int main() {
    boost::asio::io_context context;
    ssl::context ssl_context(ssl::context::tls);

    ssl_context.set_default_verify_paths();

    SSLSocket socket(context, ssl_context);

    tcp::resolver r(context);
    std::string hostname = "www.example.com";
    auto endpoints = r.resolve(hostname, "443");
    boost::asio::connect(socket.next_layer(), endpoints);
    socket.set_verify_mode(ssl::verify_peer);
    socket.set_verify_callback(ssl::host_name_verification(hostname));

    // Set SNI Hostname (many hosts need this to handshake successfully)
    if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
    {
        throw boost::system::system_error(
            ::ERR_get_error(), boost::asio::error::get_ssl_category());
    }

    socket.handshake(SSLSocket::client);
}
  • Related