Search code examples
c++sslboost-asiosslhandshakeexception

Wrong version number error on *some* websites during handshake with Asio and OpenSSL


I'm trying to send https web requests with Asio and OpenSSL. My code is working fine on most of the sites I tried it on, but on some others, I get an error during the handshake handshake: wrong version number (SSL routines, ssl3_get_record).

I've found some people having this issue because they were behind proxies or because they tried to connect to port 80 instead of port 443, but this is not the case here (as far as I know) because the exact same code (see below for minimal example) works for most of the sites I tried it on.

I've tried to check with wireshark to see if I could spot the difference between a case with and one without the error. Here what I found:

  • when it works, TLSv1.2 or 1.3 is used, when it doesn't, it's TLSv1
  • when it doesn't work, the DNS query shows a cloudfront cname redirection, but the endpoint used for the socket matches the redirection

Based on these observations, I know my code is capable of using TLSv1.3, and I thought that using TLSv1 was the issue. So I tried to force asio to use a version > 1 for TLS with asio::ssl::context::tlsv13_client when creating the context, or by adding asio::ssl::context::no_tlsv1 to set_options, but wireshark still showed that a TLSv1 protocol was used.

For the second point, I'm not too familiar with web stuff, so I'm not sure what conclusion I can make about that, or even if it's relevant to the issue.

Minimal working example:

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

#include <iostream>

int main(int argc, char* argv[])
{
    try
    {
        asio::io_context io_context;
        asio::ip::tcp::resolver resolver(io_context);
        asio::ip::tcp::resolver::results_type endpoints = resolver.resolve("google.com", "https"); //not working with "api.minecraftservices.com" for example

        asio::ssl::context ctx(asio::ssl::context::sslv23); // also tried tlsv13_client to force v1.3, without success
        ctx.set_default_verify_paths();
        ctx.set_options(asio::ssl::context::default_workarounds | asio::ssl::context::verify_none);

        asio::ssl::stream<asio::ip::tcp::socket> socket(io_context, ctx);
        socket.set_verify_mode(asio::ssl::verify_none);
        socket.set_verify_callback([](bool, asio::ssl::verify_context&) {return true; });
        asio::connect(socket.lowest_layer(), endpoints);
        socket.handshake(socket.client);
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << std::endl;
        return -1;
    }

    return 0;
}

Solution

  • You need to be more specific about the server you are trying to connect to:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    #include <iostream>
    
    namespace ssl = boost::asio::ssl;
    using boost::asio::ip::tcp;
    
    int main() {
        try {
            boost::asio::io_context io_context;
            tcp::resolver           resolver(io_context);
    
            ssl::context ctx(ssl::context::sslv23); // also tried tlsv13_client to
                                                    // force v1.3, without success
            ctx.set_default_verify_paths();
            ctx.set_options(ssl::context::default_workarounds |
                            ssl::context::verify_none);
    
            ssl::stream<tcp::socket> socket(io_context, ctx);
            socket.set_verify_mode(ssl::verify_none);
            socket.set_verify_callback([](auto&&...) { return true; });
    
    #ifndef COLIRU
            connect(socket.lowest_layer(), resolver.resolve("d7uri8nf7uskq.cloudfront.net", "https"));
    #else
            socket.lowest_layer().connect({boost::asio::ip::address_v4::from_string("65.9.84.220"), 443});
    #endif
            std::cout << "Connected to " << socket.lowest_layer().remote_endpoint() << "\n";
            socket.handshake(socket.client);
        } catch (std::exception const& e) {
            std::cout << e.what() << std::endl;
            return -1;
        }
    }
    

    Prints

    Connected to 65.9.84.220:443
    

    UPDATE

    Indeed, using api.minecraftservices.com:443 does print

    Connected to 65.9.78.95:443
    handshake: wrong version number (SSL routines, ssl3_get_record) [asio.ssl:336130315]
    

    It turns out you need SNI:

    SSL_set_tlsext_host_name(socket.native_handle(), "api.minecraftservices.com");
    

    And then it works (with varying IP resolutions)

    Connected to 65.9.78.23:443