The simplest cross-platform server with ssl support

Published on February 05, 2014

The simplest cross-platform server with ssl support

Not so long ago, I faced a task: to write a cross-platform server for processing requests using the ssl protocol. Before that, I wrote servers for regular, not encrypted protocols, but I first encountered ssl.
A quick review of the Internet showed that the best solution would be not bike building, but the use of the OpenSSL library.
In this article I do not want to consider the process of installing OpenSSL on Linux and Windows, I only note that for Windows this process was not trivial. And I want to talk about how I managed to compile in Visual Studio an example of a simple server, which is part of the OpenSSL source code.
It may seem to an inexperienced reader: “what's so special - I created a project, included a ready-made source in it, started it” ... However, first things first.

It’s easier for me, as a programmer, to tell the steps:

1. Create a directory for experiments:
Let it be for example C: \ testssl

2. Download the OpenSSL source code
Let this code be stored as an example in the C: \ openssl-1.0.1c directory
3. Compile OpenSSL
After compilation, you will have a directory with library and header files. Copy this directory to our test directory. The result should be such a structure of files and directories:
C: \ testssl
C: \ testssl \ openssl
C: \ testssl \ openssl \ bin
C: \ testssl \ openssl \ bin \ openssl.exe
C: \ testssl \ openssl \ include
C: \ testssl \ openssl \ include \ openssl
C: \ testssl \ openssl \ include \ openssl \ aes.h
..... (there are a lot of header files)
C: \ testssl \ openssl \ include \ openssl \ x509v3.h
C: \ testssl \ openssl \ lib
C: \ testssl \ openssl \ lib \ libeay32.lib
C: \ testssl \ openssl \ lib \ ssleay32.lib
C: \ testssl \ openssl \ ssl
C: \ testssl \ openssl \ ssl \ openssl

4. Copy the desired example from C: \ openssl-1.0.1c \ demos \ ssl \ serv.cpp to C: \ testssl \ serv.cpp

5. For the example to work, you need a file with a private key. This file can also be taken from source.
Copy it from C: \ openssl-1.0.1c \ certs \ demo \ ca-cert.pem to C: \ testssl \ ca-cert.pem

5. Create an empty console project in Visual Studio and add the serv.cpp file to it.
6. In the project properties, add the path for the headers: C: \ testssl \ openssl \ include and the path for the libraries: C: \ testssl \ openssl \ lib . And also the libeay32.lib, ssleay32.lib

7 libraries themselves . Now we are fixing the code

#define CERTF  HOME "foo-cert.pem"
#define KEYF  HOME  "foo-cert.pem"


on
#define CERTF  HOME "ca-cert.pem"
#define KEYF  HOME  "ca-cert.pem


8. That would seem to be all, but of course the code will not compile. The fact is that on Windows and Linux you need to include different standard header files.

Need fix
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


on
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#else
#include <io.h>
#include <Winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#endif
#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>


As you can see, for Windows you need to include the "Winsock2.h" file, as well as a socket library just in case.

9. But that's not all! If now try to compile the project, then errors will be
generated : error C2440: '=': cannot convert from 'const SSL_METHOD *' to 'SSL_METHOD *'
error C2664: 'accept': cannot convert parameter 3 from 'size_t *' to 'int * '

These errors are easy to find and correct, but if you want cross-platform, you need to correct them carefully:

For the first error, you need
  meth = SSLv23_server_method();


fix on

#ifdef WIN32
  const SSL_METHOD *meth = SSLv23_server_method();
#else
  SSL_METHOD *meth = SSLv23_server_method();
#endif


For the second error, you need
  sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);


fix on

#ifdef WIN32
	sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len);
#else
	sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);
#endif  


10. Now the code will compile and run, but it will
throw an error in the line “listen_sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR (listen_sd, „socket“); ”
Yes. On Windows, to work with sockets, you must first call the WSAStartup () function!
Add it to the beginning of the program:
void main ()
{
  int err;
  int listen_sd;
  int sd;
  struct sockaddr_in sa_serv;
  struct sockaddr_in sa_cli;
  size_t client_len;
  SSL_CTX* ctx;
  SSL*     ssl;
  X509*    client_cert;
  char*    str;
  char     buf [4096];
#ifdef WIN32
	WSADATA wsaData;
	if ( WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) != 0 )
	{
		printf("Could not to find usable WinSock in WSAStartup\n");
		return;
	}
#endif


11. Run the program, Windows asks for permission so that the application can open the port. Allowed.
The application reaches the line
sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len);

and freezes.
That's right, our server is waiting for someone to connect to it.
At this point, you can connect from the command line: "telnet localhost 1111". As soon as the connection occurs, the program will continue to run to the line
close (listen_sd);

Visual Studio will display an incomprehensible error on this line.
But if you look, it turns out that on Windows sockets are closed with another function: "closesocket ()".
To avoid errors, we change everywhere close to closesocket, and for cross-platform add code:
#ifndef WIN32
#define closesocket  close
#endif


12. Now the application started and ran to the line
err = SSL_accept (ssl);                        CHK_SSL(err);

Here it hung again, apparently expecting an ssl messaging client.
How to manually exchange these messages with telnet, I personally do not know. But to check the operation of the program to the end, you can use a regular browser!

13. To test the health of our server, run it, and instead of the command line, launch the browser.
In the address bar of the browser, we type: localhost:1111
The browser will warn of an insecure certificate. Need to take the risk. However, while we are communicating with the browser, the server may fail again. This is normal, run it again.

14. Now that the server is started, and the browser remembers that we trust this server, our program will finally work through and receive the letter “G” from the browser (the beginning of the “GET” request), over an encrypted connection!

15. One could end on this, but I decided to compile the resulting file on Linux for the purity of the experiment. It turned out that there were some surprises.
At the compilation stage, I got an error:
serv.cpp: 51: error: ':: main' must return 'int' Like

this. It turns out an example from the source OpenSSL does not compile not only on Windows, but also on Linux.
Of course, I fixed "void main ()" to "int main ()" and everywhere instead of "return" I wrote "return 0", but the sediment remained.

And if it’s suddenly interesting, it compiles on Linux with the following line: “g ++ -L / usr / lib -lssl -lcrypto serv.cpp”.

PS: this is my first post on the hub, so I apologize to the community if it turned out long and confusing. The project for Visual Studio 2012 is available in the archive: e0.3s3s.org

PPP: I wrote the continuation of this article: habrahabr.ru/post/211661