Простейший кросcплатформенный сервер с поддержкой ssl

от автора

Не так давно передо мной встала задача: написать кроссплатформенный сервер для обработки запросов по протоколу ssl. До этого я писал сервера для обычных, не шифрованных протоколов, но с ssl столкнулся впервые.
Беглый обзор интернета показал, что лучшим решением будет не велосипедостроение, а использование библиотеки OpenSSL.
В этой статье я не хочу рассматривать процесс установки OpenSSL на Linux и Windows, замечу лишь, что для Windows процесс этот оказался нетривиальным. А рассказать я хочу о том, как мне удалось скомпилировать в Visual Studio пример простейшего сервера, входящий в состав исходников OpenSSL.
Неискушенному читателю может показаться: «что тут особенного — создал проект, включил в него готовый исходник, запустил»… Однако обо всем по порядку.

Мне, как программисту проще рассказывать действия по шагам:

1. Создаем директорию для экспериментов:
Пусть это будет например С:\testssl

2. Скачиваем исходный код OpenSSL
Пусть этот код хранится нипример в директории C:\openssl-1.0.1c
3. Компилируем OpenSSL
После компиляции у вас будет директория с библиотечными и заголовочными файлами. Скопируйте эту директорию в нашу тестовую. В результате должна получиться такая структура файлов и директорий:
С:\testssl
С:\testssl\openssl
С:\testssl\openssl\bin
С:\testssl\openssl\bin\openssl.exe
С:\testssl\openssl\include
С:\testssl\openssl\include\openssl
С:\testssl\openssl\include\openssl\aes.h
…..(тут много заголовочных файлов)
С:\testssl\openssl\include\openssl\x509v3.h
С:\testssl\openssl\lib
С:\testssl\openssl\lib\libeay32.lib
С:\testssl\openssl\lib\ssleay32.lib
С:\testssl\openssl\ssl
С:\testssl\openssl\ssl\openssl

4. Скопируем нужный пример из C:\openssl-1.0.1c\demos\ssl\serv.cpp в С:\testssl\serv.cpp

5. Для работы примера потребуется файл с секретным ключем. Этот файл можно тоже взять из исходников.
Скопируем его из C:\openssl-1.0.1c\certs\demo\ca-cert.pem в С:\testssl\ca-cert.pem

5. Создаем пустой консольный проект в Visual Studio и добавляем в него файл serv.cpp.
6. В свойствах проекта добавляем путь для заголовков: С:\testssl\openssl\include и путь для библиотек: С:\testssl\openssl\lib. А также сами библиотеки libeay32.lib, ssleay32.lib

7. Теперь в коде исправляем

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

на

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

8. Казалось бы вот и все, но код конечно не скомпилируется. Дело в том, что в Windows и Linux нужно включать разные стандартные заголовочные файлы.

Нужно исправить

#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>  

на

#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> 

Как видно, для Windows нужно включить файл «Winsock2.h», а так же на всякий случай библиотеку сокетов.

9. Но и это еще не все! Если теперь попытаться скомпилировать проект, то будут выданы ошибки:
error C2440: ‘=’: cannot convert from ‘const SSL_METHOD *’ to ‘SSL_METHOD *’
error C2664: ‘accept’: cannot convert parameter 3 from ‘size_t *’ to ‘int *’

Эти ошибки легко находятся и исправляются, но если вы хотите кросплотформенности, то исправлять надо аккуратно:

Для первой ошибки надо

  meth = SSLv23_server_method(); 

исправить на

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

Для второй ошибки надо

  sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len); 

исправить на

#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. Вот теперь код скомпилируется и запустится, но выдаст ошибку в строке
«listen_sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR(listen_sd, „socket“);»
Да. В Windows, чтобы работать с сокетами, надо сначала вызвать функцию WSAStartup()!
Добавим ее в начало программы:

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. Запусаем программу, Windows просит разрешения чтобы приложение могло открыть порт. Разрешаем.
Приложение доходит до строки

sd = accept (listen_sd, (struct sockaddr*) &sa_cli, (int *)&client_len); 

и подвисает.
Все правильно, наш сервер ждет пока к нему кто-нибудь подсоединится.
На этом этапе соединиться можно из командной строки: «telnet localhost 1111». Как только соединение произойдет, программа продолжит работу до строки

close (listen_sd); 

На этой строке Visual Studio выдаст непонятную ошибку.
Но если разобраться то окажется, что в Windows сокеты закрывают другой функцией: «closesocket()».
Чтобы избежать ошибок — меняем везде close на closesocket, а для кросплатформенности добавляем код:

#ifndef WIN32 #define closesocket  close #endif 

12. Теперь приложение запустилось и отработало до строки

err = SSL_accept (ssl);                        CHK_SSL(err); 

Здесь оно опять повисло, очевидно ожидая от клиента обмена сообщениями по протоколу ssl.
Как обмениваться этими сообщениями вручную телнетом, я лично не знаю. Но чтобы проверить работу программы до конца можно воспользоваться обычным браузером!

13. Для проверки работоспособности нашего сервера запускаем его, а вместо командной строки запускаем браузер.
В адресной строке браузера набираем: https://localhost:1111
Браузер предупредит о небезопасном сертификате. Нужно принять риск. Однако пока мы общаемся с браузером, сервер может опять завершиться с ошибкой. Это нормально, запустите его еще раз.

14. Теперь, когда сервер запущен, а браузер помнит что мы этому серверу доверяем, наша программа наконец отработает до конца и примет от браузера букву «G» (начало запроса «GET»), по зашифрованному соединению!

15. На этом можно было бы закончить, но я решил для чистоты эксперимента скомпилировать получившийся файл на Linux. Оказалось, что и тут не без сюрпризов.
На этапе компиляции мне выдалась ошибка:
serv.cpp:51: error: ‘::main’ must return ‘int’

Вот так вот. Оказывается пример из исходников OpenSSL не компилируется не только в Windows, но и в Linux.
Конечно, я исправил «void main()» на «int main()» и везде вместо «return» написал «return 0», но осадок остался.

И если вдруг интересно, то компилируется в Linux следующей строкой: «g++ -L/usr/lib -lssl -lcrypto serv.cpp».

ПС: это мой первый пост на хабре, поэтому прошу прощения у сообщества, если получилось длинно и путанно. Проект для Visual Studio 2012 доступен в архиве: s0.3s3s.org

ссылка на оригинал статьи http://habrahabr.ru/post/211474/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *