Давно хотел написать полезную статью и вот наконец нашёл подходящий информационный повод.
Речь в этой статье пойдёт о создании примитивного web сервера работающего по https протоколу. Мы на пишем серверную часть, а в качестве клиентской части будет выступать любой из браузеров.
В итоге мы получим максимально упрощённый пример примитивного web сервера который можно будет улучшать и затачивать под свои задачи.
Для начала нам потребуются файлы сертификатов. Процесс генерации сертификата прост.
$ openssl req -new -x509 -days 30 -keyout server.key -out server.pem
На вопрос «Enter PEM pass phrase:» отвечаем паролем, подтверждаем и запоминаем.
На вопрос «Common Name (eg, YOUR name) []:» отвечаем именем сайта, для которого создаем сертификат.
Все остальные ответы не особо важны.
После ответов в текущей появятся два новых файла — server.key и server.pem (ключ и сертификат, соответственно).
Для удобства снимаем пароль с ключа:
$ cp server.key server.key.orig
$ openssl rsa -in server.key.orig -out server.key
$ rm server.key.orig
Теперь код. Код основан на примерах с официального сайта OpenSSL, но только я его постарался упросить. Да и простое копирование и компиляция примера у меня по какой то из причин не работало.
Подключение библиотек затруднений вызвать не должно.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
extern BIO *bio_err;
BIO *bio_err=0;
#define PEM_FILE "server.pem"
#define KEY_FILE "server.key"
#define PORT 4333
Функция создания сокета тоже простая. Тем более не содержит пока ничего из OpenSSL.
/**
* Создаёт самый обычный сокет
*/
int tcp_listen()
{
int sock;
struct sockaddr_in sin;
int val=1;
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
printf("Couldn't make socket");
memset(&sin,0,sizeof(sin));
sin.sin_addr.s_addr=INADDR_ANY;
sin.sin_family=AF_INET;
sin.sin_port=htons(PORT);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&val,sizeof(val));
if(bind(sock,(struct sockaddr *)&sin,
sizeof(sin))<0)
printf("Couldn't bind");
listen(sock,5);
return(sock);
}
Функция инициализации ssl. Буду тщательно комментировать.
/* Функция для обработки сигнала SIGPIPE который нам может послать ОС при попытки записи в закрытое соединение*/
static void sigpipe_handle(int x){}
SSL_CTX *initialize_ctx(const char *key_file,const char *pem_file)
{
if(!bio_err)
{
/* Глобальная инициализация алгоритмов OpenSSL, без неё не обходится не один пример по OpenSSL */
SSL_library_init();
SSL_load_error_strings();
/* An error write context */
bio_err=BIO_new_fp(stderr,BIO_NOCLOSE);
}
/* Set up a SIGPIPE handler */
/* Назначаем обработчик сигнала SIGPIPE который нам может послать ОС при попытки записи в закрытое соединение*/
signal(SIGPIPE,sigpipe_handle);
/* Create our context*/
SSL_CTX* ctx=SSL_CTX_new(SSLv23_method());
/* Load our keys and certificates*/
if(!(SSL_CTX_use_certificate_file(ctx,pem_file,SSL_FILETYPE_PEM)))
{
printf("Не удалось загрузить файл сертификатаn");
}
if(!(SSL_CTX_use_PrivateKey_file(ctx, key_file,SSL_FILETYPE_PEM)))
{
printf("Не удалось загрузить файл ключейn");
}
return ctx;
}
Функция initialize_ctx один раз в начале загружает наши файлы и создаёт контекст SSL_CTX. Он будет использоваться при создании отдельных соединений. Это позволяет не загружать файлы ключей для каждого нового соединения.
Теперь рассмотрим функцию работы с соединением.
#define BUFSIZZ 4048
static int http_serve(SSL *ssl,int s)
{
char buf[BUFSIZZ];
int r = 0;
int e;
bzero(buf,BUFSIZZ-1); // Очистка буфера
r=SSL_read(ssl,buf,BUFSIZZ-1); // Чтение данных
if(r<0) // Если r < 0 то произошла ошибка
{
e = SSL_get_error(ssl,r);
}
printf("[Длина принятого текста %d, Error:%d]%sn", r, e, buf);
/* Запись ответа */
r = SSL_write(ssl,"HTTP/1.0 200 OKrnServer: EKRServerrnrnServer test pagern",strlen("HTTP/1.0 200 OKrnServer: EKRServerrnrnServer test pagern"));
if(r<=0) // Если r < 0 то произошла ошибка
{
printf("Write error %dn",r);
}
else
{
printf("Write ok %dn",r);
}
/* Закрытие соединения */
shutdown(s,1);
SSL_shutdown(ssl);
SSL_free(ssl);
close(s);
return(0);
}
Думаю тут всё очевидно.
И последний этап, соединяем всё вместе.
int main(int argc, char *argv[])
{
int sock,s;
SSL_CTX *ctx;
SSL *ssl;
int r;
// Build our SSL context
ctx=initialize_ctx(KEY_FILE,PEM_FILE);
sock=tcp_listen();
printf("n");
while(1)
{
if((s=accept(sock,0,0))<0)
{
printf("Problem acceptingn");
}
else
{
printf("Accepting %dn",s);
}
ssl=SSL_new(ctx);
SSL_set_fd(ssl, s);
r=SSL_accept(ssl);
if( r < 0 )
{
printf("SSL accept error %dn",r);
printf("SSL accept error code %dn",SSL_get_error(ssl,r) );
exit(0);
}
else
{
printf("SSL accept %dn",r);
}
http_serve(ssl,s);
printf("n");
}
SSL_CTX_free(ctx);
}
Из кода я постарался убрать всё то без чего будет работать. Но не смотря на это, как я и ожидал, кода получилось больше чем текста. Но я очень надеюсь что кому то эта статья поможет.
Я всё писал под Ubuntu, возможно при компиляции в windows придётся внести незначительные правки.
В довесок добавлю ссылку на примеры по OpenSSL
Автор: Levhav