Давно хотел написать полезную статью и вот наконец нашёл подходящий информационный повод.
Речь в этой статье пойдёт о создании примитивного web сервера работающего по https протоколу. Мы на пишем серверную часть, а в качестве клиентской части будет выступать любой из браузеров.
В итоге мы получим максимально упрощённый пример примитивного web сервера который можно будет улучшать и затачивать под свои задачи.
Для начала нам потребуются файлы сертификатов. Процесс генерации сертификата прост.
На вопрос «Enter PEM pass phrase:» отвечаем паролем, подтверждаем и запоминаем.
На вопрос «Common Name (eg, YOUR name) []:» отвечаем именем сайта, для которого создаем сертификат.
Все остальные ответы не особо важны.
После ответов в текущей появятся два новых файла — server.key и server.pem (ключ и сертификат, соответственно).
Для удобства снимаем пароль с ключа:
Теперь код. Код основан на примерах с официального сайта OpenSSL, но только я его постарался упросить. Да и простое копирование и компиляция примера у меня по какой то из причин не работало.
Подключение библиотек затруднений вызвать не должно.
Функция создания сокета тоже простая. Тем более не содержит пока ничего из OpenSSL.
Функция инициализации ssl. Буду тщательно комментировать.
Функция initialize_ctx один раз в начале загружает наши файлы и создаёт контекст SSL_CTX. Он будет использоваться при создании отдельных соединений. Это позволяет не загружать файлы ключей для каждого нового соединения.
Теперь рассмотрим функцию работы с соединением.
Думаю тут всё очевидно.
И последний этап, соединяем всё вместе.
Из кода я постарался убрать всё то без чего будет работать. Но не смотря на это, как я и ожидал, кода получилось больше чем текста. Но я очень надеюсь что кому то эта статья поможет.
Я всё писал под Ubuntu, возможно при компиляции в windows придётся внести незначительные правки.
В довесок добавлю ссылку на примеры по OpenSSL
Речь в этой статье пойдёт о создании примитивного 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]%s\n", r, e, buf);
/* Запись ответа */
r = SSL_write(ssl,"HTTP/1.0 200 OK\r\nServer: EKRServer\r\n\r\nServer test page\r\n",strlen("HTTP/1.0 200 OK\r\nServer: EKRServer\r\n\r\nServer test page\r\n"));
if(r<=0) // Если r < 0 то произошла ошибка
{
printf("Write error %d\n",r);
}
else
{
printf("Write ok %d\n",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 accepting\n");
}
else
{
printf("Accepting %d\n",s);
}
ssl=SSL_new(ctx);
SSL_set_fd(ssl, s);
r=SSL_accept(ssl);
if( r < 0 )
{
printf("SSL accept error %d\n",r);
printf("SSL accept error code %d\n",SSL_get_error(ssl,r) );
exit(0);
}
else
{
printf("SSL accept %d\n",r);
}
http_serve(ssl,s);
printf("\n");
}
SSL_CTX_free(ctx);
}
Из кода я постарался убрать всё то без чего будет работать. Но не смотря на это, как я и ожидал, кода получилось больше чем текста. Но я очень надеюсь что кому то эта статья поможет.
Я всё писал под Ubuntu, возможно при компиляции в windows придётся внести незначительные правки.
В довесок добавлю ссылку на примеры по OpenSSL