📜 ⬆️ ⬇️

Secure TLS connection using Boost.Asio and OpenSSL under Windows

Introduction


Once I needed to create a secure communication channel between my server and my application. I remembered that the documentation for Boost Asio mentioned that it can work with secure connections using OpenSSL. I began to look for information on this topic, but, alas, I did not find much, especially under Windows. So now, having understood this question, I decided to write this instruction, so that it was easier for other people to understand.

The task is to build a server and client for Windows using Boost Asio and OpenSSL so that the client and server exchange information via a secure TLS channel. For example, I decided to take this client and server from the official website of Boost.

In order to solve this problem, we need to build OpenSSL, prepare keys and certificates, and collect both examples using Boost Asio, OpenSSL.

Installing OpenSSL under Windows


I took OpenSSL from the official repository: github.com/openssl/openssl
')
To install OpenSSL we need:


To build OpenSSL, I used MS Visual Studio 2013, and I built a static library.

The assembly sequence is as follows:
First you need to configure OpenSSL using a Perl script, under Win32. Below, I will assume that OpenSSL is in C: \ Work \ OpenSSL. You should go to this directory and call the configuration script:

cd C:\Work\OpenSSL perl Configure VC-WIN32 --prefix=C:\Work\OpenSSL\output enable-deprecated -I$(SRC_D) 

Note the following:

Next you need to prepare assembly source code for the assembly. It is necessary to call the build script from the same directory:

 ms\do_nasm 

At this point, you need to close the usual command line, and run the MS Visual Studio command line, which defines additional file paths and additional environment variables. You can find the MS Visual Studio command line in the C: \ Program Files (x86) \ Microsoft Visual Studio 12.0 \ Common7 \ Tools \ Shortcuts directory .

From the MS Visual Studio command line, go to the C: \ Work \ OpenSSL directory and start the build using nmake:

 nmake -f ms\nt.mak 

This is a command to build a static library, if you want to build a dynamic library, then you need to run ntdll.mak.

After executing this command, a lengthy build procedure should begin. If the build fails, then here are possible solutions to this problem:

Another problem is possible during the assembly process. The compiler will complain that it could not find the tmp32 / x86cpuid.obj file or other files that should be compiled from * .asm sources. In my case, the problem was resolved after I added the path to nasm to the PATH environment variable. Another solution is to manually compile nasm all assembly files, there are only 22 of them.

After the build is complete, you need to copy the libraries and source files to the new directory:

 nmake -f ms\nt.mak install 

This completes the OpenSSL build for Windows.

Build client and server


As I said before, for example, I decided to take this client and server from the Boost Asio documentation. However, when I tried to build, I ran into some problems, and as a result I had to modify the sources.

So:

After all the above mentioned manipulations, I managed to build and run a project with Boost Asio and OpenSSL under Windows using Visual Studio 2013.

Server source code:
server.cpp
 #include <cstdlib> #include <iostream> #include <boost/bind.hpp> #include <boost/asio.hpp> #define SSL_R_SHORT_READ 219 #include "ssl/ssl_locl.h" #include <boost/asio/ssl.hpp> typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket; class session { public: session(boost::asio::io_service& io_service, boost::asio::ssl::context& context) : socket_(io_service, context) { } ssl_socket::lowest_layer_type& socket() { return socket_.lowest_layer(); } void start() { socket_.async_handshake(boost::asio::ssl::stream_base::server, boost::bind(&session::handle_handshake, this, boost::asio::placeholders::error)); } void handle_handshake(const boost::system::error_code& error) { if (!error) { socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else { delete this; } } void handle_read(const boost::system::error_code& error, size_t bytes_transferred) { if (!error) { boost::asio::async_write(socket_, boost::asio::buffer(data_, bytes_transferred), boost::bind(&session::handle_write, this, boost::asio::placeholders::error)); } else { delete this; } } void handle_write(const boost::system::error_code& error) { if (!error) { socket_.async_read_some(boost::asio::buffer(data_, max_length), boost::bind(&session::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else { delete this; } } private: ssl_socket socket_; enum { max_length = 1024 }; char data_[max_length]; }; 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::sslv23) { 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)); context_.use_certificate_chain_file("user.crt"); context_.use_private_key_file("user.key", boost::asio::ssl::context::pem); context_.use_tmp_dh_file("dh2048.pem"); start_accept(); } std::string get_password() const { return ""; } void start_accept() { session* new_session = new session(io_service_, context_); acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, boost::asio::placeholders::error)); } void handle_accept(session* new_session, const boost::system::error_code& error) { if (!error) { new_session->start(); } else { delete new_session; } start_accept(); } private: boost::asio::io_service& io_service_; boost::asio::ip::tcp::acceptor acceptor_; boost::asio::ssl::context context_; }; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: server <port>\n"; return 1; } boost::asio::io_service io_service; using namespace std; // For atoi. server s(io_service, atoi(argv[1])); io_service.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; } 



Client source code:
client.cpp
 #include <cstdlib> #include <iostream> #include <boost/bind.hpp> #include <boost/asio.hpp> #define SSL_R_SHORT_READ 219 #include "ssl/ssl_locl.h" #include <boost/asio/ssl.hpp> enum { max_length = 1024 }; class client { public: client(boost::asio::io_service& io_service, boost::asio::ssl::context& context, boost::asio::ip::tcp::resolver::iterator endpoint_iterator) : socket_(io_service, context) { socket_.set_verify_mode(boost::asio::ssl::verify_peer); socket_.set_verify_callback( boost::bind(&client::verify_certificate, this, _1, _2)); boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error)); } 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; } void handle_connect(const boost::system::error_code& error) { if (!error) { socket_.async_handshake(boost::asio::ssl::stream_base::client, boost::bind(&client::handle_handshake, this, boost::asio::placeholders::error)); } else { std::cout << "Connect failed: " << error.message() << "\n"; } } void handle_handshake(const boost::system::error_code& error) { if (!error) { std::cout << "Enter message: "; std::cin.getline(request_, max_length); size_t request_length = strlen(request_); boost::asio::async_write(socket_, boost::asio::buffer(request_, request_length), boost::bind(&client::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else { std::cout << "Handshake failed: " << error.message() << "\n"; } } void handle_write(const boost::system::error_code& error, size_t bytes_transferred) { if (!error) { boost::asio::async_read(socket_, boost::asio::buffer(reply_, bytes_transferred), boost::bind(&client::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); } else { std::cout << "Write failed: " << error.message() << "\n"; } } void handle_read(const boost::system::error_code& error, size_t bytes_transferred) { if (!error) { std::cout << "Reply: "; std::cout.write(reply_, bytes_transferred); std::cout << "\n"; } else { std::cout << "Read failed: " << error.message() << "\n"; } } private: boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_; char request_[max_length]; char reply_[max_length]; }; int main(int argc, char* argv[]) { try { if (argc != 3) { std::cerr << "Usage: client <host> <port>\n"; return 1; } boost::asio::io_service io_service; boost::asio::ip::tcp::resolver resolver(io_service); boost::asio::ip::tcp::resolver::query query(argv[1], argv[2]); boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query); boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); ctx.load_verify_file("rootca.crt"); client c(io_service, ctx, iterator); io_service.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; } 



Creating keys and certificates


At this stage, the client and server are started, now you need to check their work. To do this, create a root certificate and sign a certificate for the server.

After building in the directory C: \ Work \ OpenSSL \ output \ bin will lie openssl.exe, you need to use it to generate keys and certificates.

First, create a private key for the root certificate:

 openssl genrsa -out rootca.key 2048 

Then, based on this key, we create a root certificate valid for 20,000 days:

 openssl req -x509 -new -nodes -key rootca.key -days 20000 -out rootca.crt 

In the interactive menu, you will be asked to enter the two-letter country code, province, city, organization, division, Common Name and e-mail address. You need to fill in all the fields at your discretion.

Now you need to create another certificate signed by the root certificate.

Create another key:

 openssl genrsa -out user.key 2048 

Create a signature request:

 openssl req -new -key user.key -out user.csr 

In the interactive menu you will need to answer the same questions as when creating the root certificate. It is necessary that the Common Name you entered was different from the Common Name of the root certificate, this is important!

Now we sign this request with a root certificate:

 openssl x509 -req -in user.csr -CA rootca.crt -CAkey rootca.key -CAcreateserial -out user.crt -days 20000 

Just in case, you can check that everything is signed correctly:

 openssl verify -CAfile rootca.crt rootca.crt openssl verify -CAfile rootca.crt user.crt openssl verify -CAfile user.crt user.crt 

The first command should return OK, because the root certificate is self-signed.

The second command should return OK, because user.crt is signed with a root certificate.

The last command should return an error, because user.crt is not self-signed. If the last command returns OK, then something went wrong. In my case, for correction, it was only necessary to make the Common Name of both certificates different.

And finally, we still need the DH parameters that are needed for the Diffie-Hellman Protocol , we need to generate them. Generation will take some time:

 openssl dhparam -out dh2048.pem 2048 

That's all, now it is enough to register the path to these files for the client and server, and you can establish a secure connection between them.

Source: https://habr.com/ru/post/271203/


All Articles