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.
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 am trying to send a get request to acounts.google.com to be able to implement a library for C++ OAuth to learn it.
I get the following code from this post: Creating a HTTPS request using Boost Asio and OpenSSL and modified it as follow:
int main()
{
try
{
std::string request = "/o/oauth2/v2/auth";
boost::system::error_code ec;
using namespace boost::asio;
// what we need
io_service svc;
ssl::context ctx(svc, ssl::context::method::sslv23_client);
ssl::stream<ip::tcp::socket> ssock(svc, ctx);
ip::tcp::resolver resolver(svc);
auto it = resolver.resolve({ "accounts.google.com", "443" }); // https://accouts.google.com:443
boost::asio::connect(ssock.lowest_layer(), it);
ssock.handshake(ssl::stream_base::handshake_type::client);
// send request
std::string fullResuest = "GET " + request + " HTTP/1.1\r\n\r\n";
boost::asio::write(ssock, buffer(fullResuest));
// read response
std::string response;
do
{
char buf[1024];
size_t bytes_transferred = ssock.read_some(buffer(buf), ec);
if (!ec) response.append(buf, buf + bytes_transferred);
std::cout << "Response received: '" << response << "'\n"; // I add this to see what I am getting from the server, so it should not be here.
} while (!ec);
// print and exit
std::cout << "Response received: '" << response << "'\n";
}
catch (const std::exception& e)
{
std::cerr << e.what() << std::endl;
if (std::string const * extra = boost::get_error_info<my_tag_error_info>(e))
{
std::cout << *extra << std::endl;
}
}
}
The problem that I have is as follow:
1- The results that I am getting is not what I am getting when I visit https://accounts.google.com/o/oauth2/v2/auth using a web browser. I essentially getting a message that they can not find the requested URL /o/oauth2/v2/auth
<p>The requested URL <code>/o/oauth2/v2/auth</code> was not found on this server. <ins>ThatÔÇÖs all we know.</ins>
How should I setup the GET commend so I can get the same result that I am getting with a browser?
2- The application hangs getting data from server, apparently the following loop is not right:
do
{
char buf[1024];
size_t bytes_transferred = ssock.read_some(buffer(buf), ec);
if (!ec) response.append(buf, buf + bytes_transferred);
} while (!ec);
What is the correct way of reading responce from the web server which is fast and read all data?
Edit 1
For reference based on accepted answer, I fixed the problem using the correct GET header as shown below:
// send request
std::string fullResuest = "GET " + request + " HTTP/1.1\r\n";
fullResuest+= "Host: " + server + "\r\n";
fullResuest += "Accept: */*\r\n";
fullResuest += "Connection: close\r\n\r\n";
boost::asio::write(ssock, buffer(fullResuest));
A HTTP/1.1 request must have a Host header. A simple experiment with OpenSSL will show the problem, i.e. the missing header:
$ openssl s_client -connect accounts.google.com:443
...
GET /o/oauth2/v2/auth HTTP/1.1
... The requested URL <code>/o/oauth2/v2/auth</code> was not found on this server. <ins>That’s all we know.</ins>
When adding the Host header instead we get a different response:
$ openssl s_client -connect accounts.google.com:443
...
GET /o/oauth2/v2/auth HTTP/1.1
Host: accounts.google.com
... >Required parameter is missing: response_type<
Apart from that HTTP/1.1 implicitly uses HTTP keep-alive, i.e. server and client might keep the connection open after the response is done. This means you should not read until the end of connection but should instead properly parse the HTTP header, extract the Content-length header and/or Transfer-Encoding header and behave according to their values. Or if you want it simpler use HTTP/1.0 instead.
For more information see the HTTP/1.1 standard.
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);