Boost ASIO SSL handshake failure - c++

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:
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.
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.
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!

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);
}

In my case I manged to make it work with this:
ssl_context.load_verify_file("/etc/ssl/certs/ca-bundle.crt");
so pointing to the ca-bundle file instead of the cert file.

Related

Boost Asio SSL not able to receive data for 2nd time onwards (1st time OK)

I'm working on Boost Asio and Boost Beast for simple RESTful server. For normal HTTP and TCP socket, it works perfectly. I put it under load test with JMeter, everything works fine.
I tried to add the SSL socket. I set the 'ssl::context' and also called the 'async_handshake()' - additional steps for SSL compared to normal socket. It works for the first time only. Client can connected with me (server) and I also able to receive the data via 'boost::beast::http::async_read()'.
Because this is RESTful, so the connection will drop after the request & respond. I call 'SSL_Socket.shutdown()' and follow by 'SSL_Socket.lowest_layer().close()' to close the SSL socket.
When the next incoming request, the client able to connect with me (server). I called 'SSL_Socket.async_handshake()' and then follow by 'boost::beast::http::async_read()'. But this time I not able to receive any data. But the connection is successfully established.
Anyone has any clue what i missed?
Thank you very much!
If you want to reuse the stream instance, you need to manipulate SSL_Socket.native_handle() with openssl lib function. After ssl shutdown, use SSL_clear() before start a new ssl handshake.
please read(pay attention to warnings) link for detail
SSL_clear() resets the SSL object to allow for another connection. The reset operation however keeps several settings of the last sessions (some of these settings were made automatically during the last handshake)
.........
WARNINGS
SSL_clear() resets the SSL object to allow for another connection. The reset operation however keeps several settings of the last sessions (some of these settings were made automatically during the last handshake). It only makes sense for a new connection with the exact same peer that shares these settings, and may fail if that peer changes its settings between connections. Use the sequence SSL_get_session(3); SSL_new(3); SSL_set_session(3); SSL_free(3) instead to avoid such failures (or simply SSL_free(3); SSL_new(3) if session reuse is not desired).
In regard to ssl shutdown issue, link explain how boost asio ssl shutdown work.
In Boost.Asio, the shutdown() operation is considered complete upon error or if the party has sent and received a close_notify message.
If you look at boost.asio (1.68) source code boost\asio\ssl\detail\impl\engine.ipp, it shows how boost.asio do ssl shutdown and stream_truncated happens when there is data to be read or ssl shutdown expected from peer not received.
int engine::do_shutdown(void*, std::size_t)
{
int result = ::SSL_shutdown(ssl_);
if (result == 0)
result = ::SSL_shutdown(ssl_);
return result;
}
const boost::system::error_code& engine::map_error_code(
boost::system::error_code& ec) const
......
// If there's data yet to be read, it's an error.
if (BIO_wpending(ext_bio_))
{
ec = boost::asio::ssl::error::stream_truncated;
return ec;
}
......
// Otherwise, the peer should have negotiated a proper shutdown.
if ((::SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) == 0)
{
ec = boost::asio::ssl::error::stream_truncated;
}
}
Also you can see boost.asio ssl shutdown routine may call openssl SSL_shutdown() twice if first return 0, openssl document allows it but advice call SSL_read() to do a bidirectional shutdown if first SSL_shutdown() returns 0.
Read link for details.
I had a similar issue, the 2nd time onward my asynchonous accept always failed with session id uninitialized.
I solved this problem calling SSL_CTX_set_session_id_context on context or
setting context cache mode with SSL_SESS_CACHE_OFF and SSL_OP_NO_TICKET on context options.
This is my cents to someone else's problem.
I managed to resolve the problem by switching 'ssl::stream' socket to 'boost::optional' and then added 'SSL_Socket.emplace(io_context, oSSLContext)' each time the socket is shutdown and closed.
Big credit to sehe at 'Can't implement boost::asio::ssl::stream<boost::asio::ip::tcp::socket> reconnect to server'. His statement "the purest solution would be to not reuse the stream/socket objects" rocks! Save my time.
Thanks.

C++ Boost asio (OpenSSL) get cipher and TLS/SSL version of active connection

We have some SSL communication using Boost asio. Connecting and communication works fine, but now we'd like to show the actual TLS version and cipher used by the connection.
The Boost asio objects we use for the SSL connection are defined like this:
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
boost::shared_ptr<ssl_socket> m_psslsocket;
boost::asio::ssl::context* m_pcontext;
It looks like Boost asio doesn't really have an interface to query the connection for the cipher once it's set up.
I can get the OpenSSL SSL_CTX* pointer by calling this:
SSL_CTX* ctx = pContext->impl();
Or the OpenSSL SSL* by calling this:
m_psslsocket->native_handle()
But from the OpenSSL documentation I can't figure out how to get the used cipher and TLS version from this.
OpenSSL documentation says:
SSL_client_version() returns the numeric protocol version advertised by the client in the legacy_version field of the ClientHello when initiating the connection. Note that, for TLS, this value will never indicate a version greater than TLSv1.2 even if TLSv1.3 is subsequently negotiated. SSL_get_version() returns the name of the protocol used for the connection. SSL_version() returns the numeric protocol version used for the connection. They should only be called after the initial handshake has been completed. Prior to that the results returned from these functions may be unreliable.
So the answer is:
SSL_get_version(SSL*)

C++ OpenSSL Fails to perform handshake when accepting in non-blocking mode. What is the proper way?

I'm trying to implement OpenSSL into my application which uses raw C sockets and the only issue I'm having is the SSL_accept / SSL_connect part of the code which starts the KeyExchange phase but does not seem to complete it on the serverside.
I've had a look at countless websites and Q&A's here on StackOverflow to get myself through the OpenSSL API since this is basically the first time I'm attempting to implement SSL into an application but the only thing I could not find yet was how to properly manage failed handshakes.
Basically, running process A which serves as a server will listen for incoming connections. Once I run process B, which acts as a client, it will successfully connect to process A but SSL_accept (on the server) fails with error code -2 SSL_ERROR_WANT_READ.
According to openssl handshake failed, the problem is "easily" worked around by calling SSL_accept within a loop until it finally returns 1 (It successfully connects and completes the handshake). However, I do not believe that this is the proper way of doing things as it looks like a dirty trick. The reason for why I believe it is a dirty trick is because I tried to run a small application I found on https://www.cs.utah.edu/~swalton/listings/articles/ (ssl_client and ssl_server) and magically, everything works just fine. There are no multiple calls to SSL_accept and the handshake is completed right away.
Here's some code where I'm accepting the SSL connection on the server:
if (SSL_accept(conn.ssl) == -1)
{
fprintf(stderr, "Connection failed.\n");
fprintf(stderr, "SSL State: %s [%d]\n", SSL_state_string_long(conn.ssl), SSL_state(conn.ssl));
ERR_print_errors_fp(stderr);
PrintSSLError(conn.ssl, -1, "SSL_accept");
return -1;
}
else
{
fprintf(stderr, "Connection accepted.\n");
fprintf(stderr, "Server -> Client handshake completed");
}
This is the output of PrintSSLError:
SSL State: SSLv3 read client hello B [8465]
[DEBUG] SSL_accept : Failed with return -1
[DEBUG] SSL_get_error() returned : 2
[DEBUG] Error string : error:00000002:lib(0):func(0):system lib
[DEBUG] ERR_get_error() returned : 0
[DEBUG] errno returned : Resource temporarily unavailable
And here's the client side snippet which connects to the server:
if (SSL_connect(conn.ssl) == -1)
{
fprintf(stderr, "Connection failed.\n");
ERR_print_errors_fp(stderr);
PrintSSLError(conn.ssl, -1, "SSL_connect");
return -1;
}
else
{
fprintf(stderr, "Connection established.\n");
fprintf(stderr, "Client -> Server handshake completed");
PrintSSLInfo(conn.ssl);
}
The connection is successfully enstablished client-side (SSL_connect does not return -1) and PrintSSLInfo outputs:
Connection established.
Cipher: DHE-RSA-AES256-GCM-SHA384
SSL State: SSL negotiation finished successfully [3]
And this is how I wrap the C Socket into SSL:
SSLConnection conn;
conn.fd = fd;
conn.ctx = sslContext;
conn.ssl = SSL_new(conn.ctx);
SSL_set_fd(conn.ssl, conn.fd);
The code snippet here resides within a function that takes a file-descriptor of the accepted incoming connection on the raw socket and the SSL Context to use.
To initialize the SSL Contexts I use TLSv1_2_server_method() and TLSv1_2_client_method(). Yes, I know that this will prevent clients from connecting if they do not support TLS 1.2 but this is exactly what I want. Whoever connects to my application will have to do it through my client anyway.
Either way, what am I doing wrong? I'd like to avoid loops in the authentication phase to avoid possible hang ups/slow downs of the application due to unexpected infinite loops since OpenSSL does not specify how many attempts it might take.
The workaround that worked, but that I'd like to avoid, is this:
while ((accept = SSL_accept(conn.ssl)) != 1)
And inside the while loop I check for the return code stored inside accept.
Things I've tried to workaround the SSL_ERROR_WANT_READ error:
Added usleep(50) inside the while loop (still takes several cycles to complete)
Added SSL_do_handshake(conn.ssl) after SSL_connect and SSL_accept (didn't change anything on the end-result)
Had a look at the code shown on roxlu.com (search on Google for "Using OpenSSL with memory BIOs - Roxlu") to guide me through the handshaking phase but since I'm new to this, and I don't directly use BIOs in my code but simply wrap my native C sockets into SSL, it was kind of confusing. I'm also unable to re-write the Networking part of the application as it'd would be too much work for me right now.
I've done some tests with the openssl command-line as well to troubleshoot the issue but it gives no error. The handshake appears to be successful as no errors such as:
24069864:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:656
appear. Here's the whole output of the command
openssl s_client -connect IP:Port -tls1_2 -prexit -msg
http://pastebin.com/9u1bfuf4
Things to note:
1. I'm using the latest OpenSSL version 1.0.2h
2. Application runs on a Unix system
3. Using self-signed certificates to encrypt the network traffic
Thanks everyone who's going to help me out.
Edit:
I forgot to mention that the sockets are in non-blocking mode since the application serves multiple clients in one-go. Though, client-side they are in blocking mode.
Edit2:
Leaving this here for future reference: jmarshall.com/stuff/handling-nbio-errors-in-openssl.html
You have clarified that the socket question is non-blocking.
Well, that's your answer. Obviously, when the socket is in a non-blocking mode, the handshake cannot be immediately completed. The handshake involves an exchange of protocol packets between the client and the server, with each one having to wait to receive the response from its peer. This works fine when the socket is in its default blocking mode. The library simply read()s and write()s, which blocks and waits until the message gets succesfully read or written. This obviously can't happen when the socket is in the non-blocking mode. Either the read() or write() immediately succeeds, or fails, if there's nothing to read or if the socket's output buffer is full.
The manual pages for SSL_accept() and SSL-connect() explain the procedure you must implement to execute the SSL handshake when the underlying socket is in a non-blocking mode. Rather than repeating the whole thing here, you should read the manual pages yourself. The capsule summary is to use SSL_get_error() to determine if the handshake actually failed, or if the library wants to read or write to/from the socket; and in that eventuality call poll() or select(), accordingly, then call SSL_accept() and SSL_connect() again.
Any other approach, like sprinkling silly sleep() calls, here and there, will result in an unreliable house of cards, that will fail randomly.

BIO_do_connect() fails seemingly because SSL v3 is used, is there a way to get more diagnostics?

I wrote a class to handle SSL connections that I use to communicate with Paypal.
It worked just fine for about a year and now it fails right away. From the error I get, it would seem that this happens because they (finally) turned off the SSLv3 cipher. Yet, I thought it was turned off on my end already.
There is the error I get when attempting my connection with my code:
139673112286976:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:s3_pkt.c:1262:SSL alert number 40
I get the exact same error if I try to connect with -ssl3 using the command line tool:
openssl s_client -connect api.sandbox.paypal.com:443 -ssl3
Note that the command line without the -ssl3 option works as expected.
And I have seen a couple of posts that say that the error means there is a problem with the cipher used, leading me to think that is the problem I'm running into.
For those interested, the whole class is found in our snapcpp git (go up one to get the .h and other files from the snapwebsites library).
There is the relevant code. I removed the error checking happening before the one that triggers the current failure:
std::shared_ptr<SSL_CTX> ssl_ctx(SSL_CTX_new(TLSv1_client_method()), ssl_ctx_deleter);
SSL_CTX_set_verify_depth(ssl_ctx.get(), 4);
SSL_CTX_set_options(ssl_ctx.get(), SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_COMPRESSION);
SSL_CTX_set_cipher_list(ssl_ctx.get(), "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4");
SSL_CTX_load_verify_locations(ssl_ctx.get(), NULL, "/etc/ssl/certs");
std::shared_ptr<BIO> bio(BIO_new_ssl_connect(ssl_ctx.get()), bio_deleter);
SSL * ssl(nullptr);
BIO_get_ssl(bio.get(), &ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
BIO_set_conn_hostname(bio.get(), const_cast<char *>(addr.c_str()));
BIO_set_conn_int_port(bio.get(), &port);
if(BIO_do_connect(bio.get()) <= 0)
{
ERR_print_errors_fp(stderr);
throw tcp_client_server_initialization_error("failed connecting BIO object to server");
}
So my class throws because the BIO_do_connect() gets the error I mentioned earlier. Yet I would think that these options:
SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1
would be enough to avoid SSLv3.
What else could be the culprit?
You stated SSL_OP_NO_TLSv1 yet you use TLSv1 method.
Try changing TLSv1_client_method() to SSLv23_client_method(). This will alow usage of any method (in fact it will negotiate the highest available SSL/TLS version). You can limit it with SSL_CTX_set_options as you did in your code.
For some reasons the documentation says that SSLv23_method() is deprecated and one should use TLS_method(). Howerer, there isn't such thing in their newest (OpenSSL 1.0.2e) code.

Handshake failed: certificate verify failed (Boost ASIO)

Hello I'm trying to connect to a server:
argv[1] = "demo.demo.com"; // or httpbin.com
argv[2] = "39473"; // or 80
With this similar code:
http://www.boost.org/doc/libs/1_47_0/doc/html/boost_asio/example/ssl/client.cpp
The problem I am getting is this:
Handshake failed: certificate verify failed
I have tried this:
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::verify_none);
//ctx.set_default_verify_paths();
Is there a way to just connect without verifying certificate.
You could add a verification callback that returns true:
socket_.set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
Where
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
return true;
}
So the error indicates that the returned certificate does not match the one that was loaded. In the example code, the loaded certificate occurs here:
ctx.load_verify_file("ca.pem");
As a test, you might try the following. Issue this command in a CMD shell (I'm assuming you have openssl installed):
openssl s_client -connect demo.demo.com:39473 -showcerts
Examine the returned output, and compare it to your ca.pem file. I bet they are different. You could even try replacing the content of your ca.pem file with the returned text from opensll, and hopefully that works.
Your mileage may vary.