I'm using Boost's asio to connect to a site via HTTPS. I want this to only succeed if the certificate is valid, not expired, not self-signed, etc. Unfortunately it seems to always work regardless. Here is my code:
try
{
asio::io_service ioService;
asio::ssl::context sslContext(asio::ssl::context::sslv3_client);
sslContext.load_verify_file("cacert.pem");
asio::ip::tcp::resolver resolver(ioService);
asio::ip::tcp::resolver::query query("self-signed.badssl.com", "443");
asio::ip::tcp::resolver::iterator endpointIterator = resolver.resolve(query);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(ioService, sslContext);
ioService.run();
// Enable SSL peer verification.
socket.set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
asio::connect(socket.lowest_layer(), endpointIterator);
socket.handshake(asio::ssl::stream_base::client);
boost::asio::streambuf request;
std::ostream requestStream(&request);
requestStream << "GET / HTTP/1.0\r\n";
requestStream << "Host: self-signed.badssl.com\r\n";
requestStream << "Accept: */*\r\n";
requestStream << "Connection: close\r\n\r\n";
asio::write(socket, request);
And so on. If I use set_verify_callback()
and return false
from the callback, the connection does fail, but preverified
always seems to be true, even for https://self-signed.badssl.com/. Surely that isn't right?
Well this is weird.
Obviously that server should be serving a self-signed certificate.
When checking with a browser or this online tool: https://www.digicert.com/help/ this is even confirmed. It shows the certificate is:
However, when I tried from my shell using e.g.
We get a clearly different cert with a "real" issuer:
Issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA
.We clearly get a different cert:
When I added a callback to your code snippet:
I saw this was indeed the certificate ASIO was handling for me:
I honestly have no idea how the two can be disagree - even when the IP address appears to resolve to the same. But this certainly seems to be relevant to the symptoms.
The issue here is Server Name Indication (SNI):
The badssl.com server is sending a certificate with a proper chain when you connect with no SNI. If you connect with SNI then the self-signed certificate will be sent. You can verify this with OpenSSL on the command line by observing the difference between the two commands:
boost::asio
has no API to add SNI, but I think you can do it by using the underlying OpenSSL API and the native_handle() method on your stream. It should be something like this:I do note that you are configuring your context with
sslv3_client
. As SNI is a TLS extension (i.e. not SSLv3), this may not work without configuring a TLS context.