I am trying to expand examples in boost asio library.
What I have been successful at so far.
1) Running the echo client and server.
http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/example/cpp11/echo/blocking_tcp_echo_client.cpp
http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio/example/cpp11/echo/blocking_tcp_echo_server.cpp
2) Expanding the echo server to read messages until end of file of standard input.
3) Running the ssl client server example.
http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/ssl/client.cpp
http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/ssl/server.cpp
What I need help on:
I would like to be able to put the messages send/receive in a loop in the client. I do not want to put a loop around
client c(io_service, ctx, iterator);
io_service.run();
because that would verify the certificate for every message. I tried putting a loop around handle_handshake and other functions, but they didn't work. The send would not occur until the end of the function. I would never receive a response.
I am willing to avoid using asynchronous IO, but I still want the encryption.
I had to start with the blocking client, and add what I needed from the ssl client and ssl example using https:
This works with the ssl server unmodified.
//
// ssl_blocking_tcp_echo_client.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2015 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
using boost::asio::ip::tcp;
enum { max_length = 1024 };
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.
// In this example we will simply print the certificate's subject name.
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 << "Verifying " << subject_name << "\n";
return preverified;
}
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: blocking_tcp_echo_client <host> <port>\n";
return 1;
}
boost::asio::io_service io_service;
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.load_verify_file("server.crt");
ctx.set_verify_mode(boost::asio::ssl::verify_peer);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(io_service, ctx);
tcp::resolver resolver(io_service);
boost::asio::connect(socket.lowest_layer(), resolver.resolve({argv[1], argv[2]}) );
socket.set_verify_callback(
boost::bind(&verify_certificate, _1, _2));
socket.handshake(boost::asio::ssl::stream<boost::asio::ip::tcp::socket>::client);
while(std::cin)
{
std::cout << "Enter message: ";
char request[max_length];
std::cin.getline(request, max_length);
size_t request_length = std::strlen(request);
boost::asio::write(socket, boost::asio::buffer(request, request_length));
char reply[max_length];
size_t reply_length = boost::asio::read(socket,
boost::asio::buffer(reply, request_length));
std::cout << "Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\n";
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Before starting this, I modified server and client based on Shootfast's answer to this question. I had to modify it slightly because something was too short. I changed this command to 2048 instead of the original 512.
openssl dhparam -out dh512.pem 2048
Quoted below.
OK, for anyone finding this in the future, you need to create your certificates and sign them appropriately.
Here are the commands for linux:
//Generate a private key
openssl genrsa -des3 -out server.key 1024
//Generate Certificate signing request
openssl req -new -key server.key -out server.csr
//Sign certificate with private key
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
//Remove password requirement (needed for example)
cp server.key server.key.secure
openssl rsa -in server.key.secure -out server.key
//Generate dhparam file
openssl dhparam -out dh512.pem 2048
Once you've done that, you need to change the filenames in server.cpp and client.cpp.
server.cpp
context_.use_certificate_chain_file("server.crt");
context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh512.pem");
client.cpp
ctx.load_verify_file("server.crt");
Then it should all work!
Related
I am trying to use beast::websocket for my Raspberry pi project where Rpi is an IoT websocket client and Microsoft Azure WebPubSub is server-side.
I am new to C++ and Unix system. Sadly, I was unable to find that many useful reference projects using the C++ Websocket.
The first thing I wanted to do was to use the beast::websocket client example to successfully connect to a test websocket server.
Below is the example I used. beast::websocket example 1.67
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
namespace websocket = boost::beast::websocket; // from <boost/beast/websocket.hpp>
// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4)
{
std::cerr <<
"Usage: websocket-client-sync <host> <port> <text>\n" <<
"Example:\n" <<
" websocket-client-sync echo.websocket.org 80 \"Hello, world!\"\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const text = argv[3];
// The io_context is required for all I/O
boost::asio::io_context ioc;
// These objects perform our I/O
tcp::resolver resolver{ioc};
websocket::stream<tcp::socket> ws{ioc};
// Look up the domain name
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
boost::asio::connect(ws.next_layer(), results.begin(), results.end());
// Perform the websocket handshake
ws.handshake(host, "/");
// Send the message
ws.write(boost::asio::buffer(std::string(text)));
// This buffer will hold the incoming message
boost::beast::multi_buffer buffer;
// Read a message into our buffer
ws.read(buffer);
// Close the WebSocket connection
ws.close(websocket::close_code::normal);
// If we get here then the connection is closed gracefully
// The buffers() function helps print a ConstBufferSequence
std::cout << boost::beast::buffers(buffer.data()) << std::endl;
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Because echo.websocket.org is closed now. I used different test servers.
These are the command line for two different websocket test servers.
sudo websocket-client-sync streamer.finance.yahoo.com 80 "Hello, world!"
Returned with Error: The WebSocket stream was gracefully closed at both endpoints
sudo websocket-client-sync demo.piesocket.com/v3/channel_1?api_key=oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm¬ify_self 80 "Hello, world!"
Returned with Error: resolve: Host not found (authoritative)
I was able to connect both URLs with a websocket test client.
But unable to connect or keep connected through this beast example.
Especially, for the second server with an API key and forward slashes, I wasn't even able to find a host.
I thought this could be something to do with those special characters not recognized as literal?
So so far these are what I have tried.
I tried replacing / with /.
I specified the host in the cpp file
(ex. auto const host = "demo.piesocket.com/v3/channel_1?api_key=oCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm¬ify_self";)
Used encoded URL
(ex. demo.piesocket.com%2Fv3%2Fchannel_1%3Fapi_key%3DoCdCMcMPQpbvNjUIzqtvF1d2X2okWpDQj4AwARJuAgtjhzKxVEjQU6IdCjwm%26notify_self)
used port 443 instead of 80
tried secure beast::websocket example over SSL for wss servers.
Nothing worked...
In the end, I need to use an Azure websocket URL with an access token which looks like this
wss://lupo.webpubsub.azure.com/client/hubs/Hub?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ3c3M6Ly9sdXBvLndlYnB1YnN1Yi5henVyZS5jb20vY2xpZW50L2h1YnMvSHViIiwiaWF0IjoxNjMzNTc3ODA1LCJleHAiOjE2MzM1ODE0MDV9.1xGRvCsyc1QDTBWJ01PcTarx0judpa6ZuQ8
Because this is the secure websocket, I used this example
Successfully compiled with sudo g++ -v websocket_client_sync_ssl.o -o wsstest -lpthread -lboost_system -lcrypto -lssl
But no hope. Same result.. Error: resolve: Host not found (authoritative)
I feel like I am missing something pretty simple here. Could you help me out?!
Thanks!
I'm using nghttp2_asio. I compiled it using ./configure --enable-asio-lib. Then, I added /usr/local/lib to /etc/ld.so.conf file. The code is as follows:
#include "bits/stdc++.h"
#include "nghttp2/asio_http2_server.h"
using namespace std;
using namespace nghttp2::asio_http2;
using namespace nghttp2::asio_http2::server;
int main(int argc, char **argv) {
http2 srv;
srv.num_threads(4);
srv.handle("/", [](const request &req, const response &res) {
cout << req.uri().path << endl;
header_map headers;
headers.emplace("content-type", header_value{ "text/html", false });
res.write_head(200, headers);
res.end(file_generator("index.html"));
});
boost::system::error_code ec;
if (srv.listen_and_serve(ec, "localhost", "8080")) cerr << ec.message() << endl;
return 0;
}
When I try to open the browser (Chrome or Firefox) on http://localhost:8080, it give me the following error:
This page isn't working
localhost didn't send any data.
ERR_EMPTY_RESPONSE
Even if I try with curl, it gives me the error:
curl: (52) Empty reply from server
The only thing that works is curl http://localhost:8080 --http2-prior-knowledge.
Is there a solution for this?
It looks like your browser refuses to do HTTP/2 over an unencrypted connection. The Wikipedia page has the following to say:
Although the standard itself does not require usage of encryption,[51] all major client implementations (Firefox,[52] Chrome, Safari, Opera, IE, Edge) have stated that they will only support HTTP/2 over TLS, which makes encryption de facto mandatory.[53]
cURL has a different problem: it defaults to HTTP/1 which your HTTP/2 server does not understand. Adding the flag makes it use the HTTP/2 binary protocol directly. Alternatively, connecting to an HTTPS endpoint will automatically turn on HTTP/2.
See the libnghttp2_asio documentation for an example on how to serve with encryption:
int main(int argc, char *argv[]) {
boost::system::error_code ec;
boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23);
tls.use_private_key_file("server.key", boost::asio::ssl::context::pem);
tls.use_certificate_chain_file("server.crt");
configure_tls_context_easy(ec, tls);
http2 server;
// add server handlers here
if (server.listen_and_serve(ec, tls, "localhost", "3000")) {
std::cerr << "error: " << ec.message() << std::endl;
}
}
I recently upgraded from Boost 1.67.0 to Boost 1.75.0 and immediately ran into issues with boost beast in code that talks to a REST API.
The code was previously working, but now it appears to be sending garbage to the server for the content and I have absolutely no clue why.
Here is the code, which posts a JSON string to the Kubernetes API to specify a custom resource. The specifics of the REST API are immaterial as the Kubernetes API server can't even read the boost POST request:
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <boost/beast/core.hpp>
#include <boost/beast/version.hpp>
#include <boost/beast/http.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace bip = boost::asio::ip;
namespace bhttp = boost::beast::http;
namespace ssl = boost::asio::ssl;
void postServiceEndpoint(std::string topicName, std::string url,
std::string host, std::string port, std::string discoveryNamespace)
{
boost::asio::io_context context;
boost::asio::ip::tcp::resolver resolver(context);
ssl::context sslCtx({ssl::context::sslv23_client});
boost::asio::ssl::stream<boost::beast::tcp_stream> sslStream(context, sslCtx);
auto const results = resolver.resolve(host, port);
SSL_set_tlsext_host_name(sslStream.native_handle(), host.c_str());
boost::beast::get_lowest_layer(sslStream).connect(results);
sslStream.handshake(ssl::stream_base::client);
//Load the bearer token for authenticating with K8s...
std::ifstream t("/var/run/secrets/kubernetes.io/serviceaccount/token");
std::string str((std::istreambuf_iterator<char>(t)),
std::istreambuf_iterator<char>());
std::string bearerToken = str;
std::string target = "/apis/sdsendpoints.net/v1/namespaces/" + discoveryNamespace + "sdsendpoints";
//Because the endpoint hasn't been created yet, we cant use it in the target
//string, but if we want to retrieve the endpoint later, we have to use its name
//in the target string... Kubernetes's REST API be weird like that.
bhttp::request<bhttp::string_body> request(bhttp::verb::post, target, HTTPV1DOT1);
request.set(bhttp::field::host, host);
request.set("Content-Type", "application/json");
request.set("Authorization", "Bearer " + bearerToken);
boost::property_tree::ptree requestTree;
requestTree.put("apiVersion", "sdsendpoints.net/v1");
requestTree.put("kind", "SdsEndpoint");
requestTree.put("metadata.name", topicName);
requestTree.put("spec.endpointURL", url);
std::stringstream jsonStream;
boost::property_tree::write_json(jsonStream, requestTree);
request.body() = jsonStream.str();
request.prepare_payload();
std::cout << "REQUEST: \n" << request << std::endl;
bhttp::write(sslStream, request);
boost::beast::flat_buffer buffer;
bhttp::response<bhttp::string_body> response;
bhttp::read(sslStream, buffer, response);
if(response.result_int() >= 400)
{
std::cout << "Got failure on post endpoint: " << response.result_int() << ": " << response.result() << " : " << response.body() << std::endl;
}
//Cleanup the SSL socket...
boost::system::error_code ec;
sslStream.shutdown(ec);
if(ec == boost::asio::error::eof)
{
//This is fine. I am okay with the events that are unfolding currently.
ec.assign(0, ec.category());
}
if(ec)
{
std::cout << "Got error code: " << ec << " on socket cleanup in SSL shutdown" << std::endl;
}
sslStream.lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
if(ec)
{
std::cout << "Got error code: " << ec << " on socket cleanup in TCP socket shutdown." << std::endl;
}
}
Running this code gives the following output for the boost HTTP request:
POST apis/sdsendpoints.net/v1/namespaces/sds-test/sdsendpoints HTTP/1.1
Host: kubernetes
Authorization: Bearer <REDACTED>
Content-Type: application/json
Content-length: 133
{"apiVersion" : "sdsendpoints.net/v1", "kind":"SdsEndpoint","metadata":{"name":"sds-tester"},"spec":{"endpointURL":"tcp://test-host:31337"}}
Which appears to be a completely valid HTTP request.
However, what I get back from the server now (under 1.75.0 that I did not get under 1.67.0) is:
Got failure on post endpoint: 400: Bad Request : {"kind":"Status","apiVersion":"v1","metadata":{},"status":"Failure","message":"the object provided is unrecognized (must be of type SdsEndpoint): couldn't get version/kind; json parse error: unexpected end of JSON input (\u003cempty\u003e)","reason":"BadRequest","code":400}
Which seems to indicate the actual HTTP request is getting scrambled somehow.
Additionally, while cleaning up the SSL socket I get the error code asio.ssl:2, which makes me wonder if the scrambling is due to some kind of error in setting up the connection. However, the code appears to follow the boost 1.75.0 example for synchronous HTTP SSL connections, and both this version and the 1.67.0 form that uses a TCP socket in the ssl stream instead of a boost tcp stream fail in the same way (with the 400 error).
Thinking it might be an issue in boost property tree, I rewrote that section using boost JSON and still got the same 400 error, suggesting that the problem is not in the JSON string itself but whatever boost beast is doing in this newer version with the request.
As a final sanity check I manually http POST'd using curl:
curl -x POST -h "Host: kubernetes" -H "Authorization: Bearer <REDACTED>" -H "Content-Type: application/json" --data '{"apiVersion" : "sdsendpoints.net/v1", "kind":"SdsEndpoint","metadata":{"name":"sds-tester"},"spec":{"endpointURL":"tcp://test-host:31337"}}' https://kubernetes:6443/apis/sdsendpoints.net/v1/namespaces/sds-test/sdsendpoints/ --insecure
And curl had no problem successfully posting to the kubernetes API server (and using the JSON output generated by boost property tree no less).
So right now the focus is clearly on some kind of change in boost::beast between 1.67.0 and 1.75.0. I'm left absolutely scratching my head here wondering if there is some kind of new regression that was introduced in 1.75.0...
I have tried two different compilers for this code: GCC 4.8.5 and Intel icpc 19.1.0.166 20191121. The code is being compiled and run on RHEL 7.9.
The problem, as Yuri pointed out, turned out to be due to a spurious newline at the end of the bearer token file that was being read in.
I generated by self signed certificate (with my own CA) and now I'm trying to have a boost ASIO client verify the identity of the server. I verified these with openssl and the verification seems to work.
The server and client code is respectively here and here.
I only modified the following parts:
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(boost::asio::ssl::context::tlsv12_server)
{
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
// Use the certificate for my website that I had generated context_.use_certificate_file("/home/paul/ca/intermediate/certs/mywebsite.net.cert.pem", boost::asio::ssl::context::pem);
// Not sure if I need this, probably not. I do have an intermediate CA though
//context_.use_certificate_chain_file("/home/paul/ca/intermediate/certs/ca-chain.cert.pem");
// Use website private key context_.use_private_key_file("/home/paul/ca/intermediate/private/mywebsite.net.key.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("/home/paul/SSLTest/dh512.pem");
start_accept();
}
and in the client:
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.
// In this example we will simply print the certificate's subject name.
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 << "Verifying " << subject_name << "\n";
std::cout << "preverified: " << std::boolalpha << preverified << "\n";
return preverified;
}
...
int main(int argc, char* argv[])
{
try
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query("localhost", "3232");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client);
std::ifstream ca_file("/home/paul/ca/certs/ca.cert.pem", std::ios::binary | std::ios::ate);
std::vector<char> data;
auto size = ca_file.tellg();
data.resize(size);
ca_file.seekg(0, std::ios::beg);
ca_file.read(data.data(), size);
ca_file.close();
// Have my own CA added to the list of known CAs
ctx.add_certificate_authority(boost::asio::buffer(data, data.size()));
// Not sure if I need something here, the CA should be enough to
// validate the server's certificate prompted (even if signed by the
// intermediate CA)
//ctx.load_verify_file("/home/paul/ca/private/ca.key.pem");
//ctx.load_verify_file("/home/paul/ca/intermediate/private/intermediate.key.pem");
client c(io_service, ctx, iterator);
However this is not working and the client is returning
Verifying /C=IT/ST=Italy/L=Milan/O=MyCompanyLtd/OU=MyCompanyLtd Auth/CN=mywebsite.net/emailAddress=info#mywebsite.net
preverified: false
Handshake failed: certificate verify failed
I'm aware that there's no verification being performed in the callback but I thought that the callback set in set_verification_callback would be called after a pre-verification (hence the preverified parameter).
Where am I getting wrong?
It's not going to work with just the data you provided. There's not enough information to validiate the certificate. You should generally call these two.
ctx.use_certificate_chain_file("path");
ctx.use_private_key_file("path", boost::asio::ssl::context::pem);
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;
}
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
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.