SSL certificates and Boost asio
Asked Answered
T

2

13

Hello I'm trying to download content from webpage that uses https via C++. My very basic client program taken from the Boost asio examples compiles and runs fine, but when I test it eg with Google: www.google.co.uk/?gws_rd=ssl, it gives me the error "handshake: certificate verify failed".

I think this is because ctx.set_default_verify_paths() doesn't contain a path with a certificate for Google (I'm on Windows).

I'm very new to SSL, please can you help me with the following questions:

1) When I installed openSSL, did it stick a list of trusted certifying authorities on my computer? If it did, what would cause Google's certificate not to be verified?

2) Is there anyway of saying I don't care about verification, proceed to connect anyway, like when you add an exception manually in firefox? I'm not particularly interested in whether the connection is trusted as I am not transmitting anything that needs to be secure.

Answers to either would be greatly appreciated!

#include <iostream>
#include <istream>
#include <ostream>
#include <fstream>
#include <string>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

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

int main(int argc, char* argv[])
{
  try
  {
if (argc != 3)
{
  std::cout << argc;
  std::cout << "Usage: sync_client <server> <path>\n";
  std::cout << "Example:\n";
  std::cout << "  sync_client www.boost.org /LICENSE_1_0.txt\n";
  return 1;
}

boost::asio::io_service io_service;

// Create a context that uses the default paths for
// finding CA certificates.
ssl::context ctx(ssl::context::sslv23);
ctx.set_default_verify_paths();

// Get a list of endpoints corresponding to the server name.
tcp::resolver resolver(io_service);
tcp::resolver::query query(argv[1], "https");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);

// Try each endpoint until we successfully establish a connection.
ssl_socket socket(io_service, ctx);
boost::asio::connect(socket.lowest_layer(), endpoint_iterator);
socket.lowest_layer().set_option(tcp::no_delay(true));

// Perform SSL handshake and verify the remote host's
// certificate.
socket.set_verify_mode(ssl::verify_peer);
socket.set_verify_callback(ssl::rfc2818_verification("host.name"));
socket.handshake(ssl_socket::client);

// Form the request. We specify the "Connection: close" header so that the
// server will close the socket after transmitting the response. This will
// allow us to treat all data up until the EOF as the content.
boost::asio::streambuf request;
std::ostream request_stream(&request);
request_stream << "GET " << argv[2] << " HTTP/1.0\r\n";
request_stream << "Host: " << argv[1] << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";

// Send the request.
boost::asio::write(socket, request);

// Read the response status line. The response streambuf will automatically
// grow to accommodate the entire line. The growth may be limited by passing
// a maximum size to the streambuf constructor.
boost::asio::streambuf response;
boost::asio::read_until(socket, response, "\r\n");

// Check that response is OK.
std::istream response_stream(&response);
std::string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
std::string status_message;
std::getline(response_stream, status_message);

if (!response_stream || http_version.substr(0, 5) != "HTTP/")
{
  std::cout << "Invalid response\n";
  return 1;
}
if (status_code != 200)
{
    std::cout << "Response returned with status code " << status_code << "\n";
    std::cout << status_message << "\n";
    // Read the response headers, which are terminated by a blank line.
    boost::asio::read_until(socket, response, "\r\n\r\n");

    // Process the response headers.
    std::string header;
    while (std::getline(response_stream, header) && header != "\r")
        std::cout << header << "\n";
    std::cout << "\n";
      return 1;
}
//code to read the data goes here, which works fine for http pages    
  }
  catch (std::exception& e)
  {
    std::cout << "Exception: " << e.what() << "\n";
  }
  return 0;
}
Time answered 1/2, 2015 at 15:24 Comment(0)
C
26

Trusted certificates are often installed or updated via the OS, browsers, or individual packages. For instance, in the *nix world, the certificates are often available through the ca-certificates package, and the certificates are installed to locations that boost::asio::ssl::context::set_default_verify_paths() will find.

The certification verification is failing because the the client is attempting to verify the peer's certificates with hostname verification (rfc2818), and is checking for the literal "host.name" to be in the certificate, and the server's certificates do not list "host.name" as a name. Try changing:

socket.set_verify_callback(ssl::rfc2818_verification("host.name"));

to:

socket.set_verify_callback(ssl::rfc2818_verification(argv[1]));

To disable peer verification, provide boost::asio::ssl::verify_none to the boost::asio::ssl::stream::set_verify_mode():

socket.set_verify_mode(boost::asio::ssl::verify_none);

Boost.Asio provides other peer verify_modes.


When peer verification is failing, it can be helpful to provide a custom callback to boost::asio::ssl::stream::set_verify_callback that provides diagnostic information. As noted in the documentation, the handler signature must be:

bool verify_callback(
  bool preverified, // True if the certificate passed pre-verification.
  verify_context& ctx // The peer certificate and other context.
);

Here is a custom functor that prints the certificate subject name:

///@brief Helper class that prints the current certificate's subject
///       name and the verification results.
template <typename Verifier>
class verbose_verification
{
public:
  verbose_verification(Verifier verifier)
    : verifier_(verifier)
  {}

  bool operator()(
    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);
    bool verified = verifier_(preverified, ctx);
    std::cout << "Verifying: " << subject_name << "\n"
                 "Verified: " << verified << std::endl;
    return verified;
  }
private:
  Verifier verifier_;
};

///@brief Auxiliary function to make verbose_verification objects.
template <typename Verifier>
verbose_verification<Verifier>
make_verbose_verification(Verifier verifier)
{
  return verbose_verification<Verifier>(verifier);
}

And its usage:

socket.set_verify_callback(make_verbose_verification(
  boost::asio::ssl::rfc2818_verification(argv[1])));

On my machine, when using it and set_default_verify_paths() is not invoked, I get the following output:

$ ./a.out www.google.co.uk /?gws_rd=ssl
Verifying: /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Verified: 0
Exception: handshake: certificate verify failed

And when set_default_verify_paths() is invoked:

$ ./a.out www.google.co.uk /?gws_rd=ssl
Verifying: /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
Verified: 1
Verifying: /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Verified: 1
Verifying: /C=US/O=Google Inc/CN=Google Internet Authority G2
Verified: 1
Verifying: /C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
Verified: 1

And when rfc2818_verification("host.name") is used:

$ ./a.out www.google.co.uk /?gws_rd=ssl
Verifying: /C=US/O=Equifax/OU=Equifax Secure Certificate Authority
Verified: 1
Verifying: /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
Verified: 1
Verifying: /C=US/O=Google Inc/CN=Google Internet Authority G2
Verified: 1
Verifying: /C=US/ST=California/L=Mountain View/O=Google Inc/CN=google.com
Verified: 0
Exception: handshake: certificate verify failed
Came answered 1/2, 2015 at 18:53 Comment(5)
Hello thank you very much for your help. When I put in the diagnostic function I get the first output that you find: Verifying: /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA Verified: 0 Exception: handshake: certificate verify failed ...after I remove the "host.name" error. Am I right in thinking this is indicating set_default_verify_paths() is not doing a very good job? ...When I do socket.set_verify_mode(boost::asio::ssl::verify_none); I get a short read exception which I guess is an unrelated problem. Thanks again for your helpTime
@Time It is possible that the installation did not include trusted certificates, the path is not being found via set_default_verify_paths(), or the application does not have permissions to read from the directory. Consult the installer's documentation or browse through the file system. If you find a path containing the certificates, add that path to the context via add_verify_path().Came
It turns out I didn't have the certificates installed as part of OpenSSL. I've got hold of the list of mozilla certificates in a single PEM file but when I drill down into what add_verify_path() actually is a wrapper for, it looks like I need each certificate to be in a separate file and the extension of these files needs to be .0 not .pem. Is this correct and if so how do I take my single PEM file and split it up? CheersTime
@Time The c_rehash utility will create the links necessary for a directory to serve as a CAPath. Create a directory, split the PEM file into multiple PEM files, storing each PEM file into the directory, then run c_rehash against the directory. Afterwards, add the directory to add_verify_path(). Alternatively, you could use the default verify path. For this particular cert chain, you may only need to split Equifax Secure CA into its own PEM file. If you feel this answers the original question, consider marking the answer as accepted.Came
My install of openssl didn't seem to include the c_rehash utility, but looking further I found that set_default_verify_paths() is just looking for an environmental variable called SSL_CERT_FILE which points to a single pem file with all the certificates in it. After setting this variable to point to Mozilla's cacert.pem file, everything worked as per your example. Woudn'tve got there without you, thanks for your help!Time
O
0

You said that" After setting this variable to point to Mozilla's cacert.pem file, everything worked as per your example". Can I know whether can use "load_verify_file(// here is the CA certificate path and file)" for your cert verification? Seems it's easier than change the environment variable points to single pem file.

Orpington answered 12/8, 2021 at 2:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.