Boost SSL verifies expired and self-signed certifi

2019-03-31 13:21发布

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?

2条回答
我想做一个坏孩纸
2楼-- · 2019-03-31 13:57

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:

SHA1 Thumbprint = 079B3259D07C4DE2A1CE0EF4A5B5599D3B2D62EA

However, when I tried from my shell using e.g.

 openssl s_client -connect self-signed.badssl.com:443 -debug |&
      openssl x509 -text -noout

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:

SHA1 Fingerprint=C8:67:8E:DB:FD:BB:30:B5:3F:2D:7B:F9:66:B8:14:C6:2E:95:92:CE

When I added a callback to your code snippet:

auto cb = [](bool preverified, boost::asio::ssl::verify_context& ctx) {
    char subject_name[256];
    X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
    X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);

    std::cout << "SSL Verify: " << subject_name << "\n";

    return preverified;
};
socket.set_verify_callback(cb);

asio::connect(socket.lowest_layer(), endpointIterator);
std::cout << "Connected: " << socket.lowest_layer().remote_endpoint() << "\n";

system::error_code ec;
socket.handshake(asio::ssl::stream_base::client, ec);

std::cout << "Shook hands: " << ec.message() << "\n";

I saw this was indeed the certificate ASIO was handling for me:

Connected: 104.154.89.105:443
SSL Verify: /C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
SSL Verify: /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
SSL Verify: /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
SSL Verify: /OU=Domain Control Validated/OU=PositiveSSL Wildcard/CN=*.badssl.com
Shook hands: Success

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.

查看更多
Bombasti
3楼-- · 2019-03-31 14:02

The issue here is Server Name Indication (SNI):

Server Name Indication (SNI) is an extension to the TLS computer networking protocol by which a client indicates which hostname it is attempting to connect to at the start of the handshaking process. This allows a server to present multiple certificates on the same IP address and TCP port number and hence allows multiple secure (HTTPS) websites (or any other Service over TLS) to be served off the same IP address without requiring all those sites to use the same certificate.

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:

openssl s_client -connect self-signed.badssl.com:443 -showcerts
openssl s_client -connect self-signed.badssl.com:443 -servername self-signed.badssl.com -showcerts

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:

SSL_set_tlsext_host_name(socket.native_handle(), "self-signed.badssl.com");

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.

查看更多
登录 后发表回答