Салимóненко Дмитрий Александрович

ЗАДАНИЕ 9: Простой https-клиент (С++, LINUX)
СОДЕРЖАНИЕ
Введение
↑ К содержанию ↑В этом задании Вы можете потренироваться направлять сетевой запрос на какой-нибудь сайт по протоколу https. Ранее, например в задании 5. Вы уже пробовали делать сетевой запрос, но он осуществлялся в формате протокола HTTP. Сейчас же будет использоваться протокол HTTPS. Его отличие от НТТР состоит в том, что передаваемые данные зашифровываются при помощи того или иного алгоритма криптошифрования.
openssl
. Она, как правило, входит в дистрибутивы Linux, в частности, в Ubuntu.
Исходный текст (код) программы на языке С++
:
- // https.cpp
- // Компиляция: g++ -Wall -o https https.cpp -L/usr/lib -lssl -lcrypto -std=c++11
- #include <stdio.h>
- #include <unistd.h>
- #include <sys/socket.h>
- #include <netdb.h>
- #include <netinet/in.h>
- #include <openssl/ssl.h>
- int main() {
- char hostname[]="site.ru";
- int port = 443; // Если бы был протокол HTTP, использовался бы (как правило) порт 80
- SSL_library_init(); // Инициализируем библиотеку openssl
- OpenSSL_add_all_algorithms(); // Загружаем алгоритмы криптошифрования для этой библиотеки
- SSL_load_error_strings(); // Регистрируем сообщения об ошибках
- const SSL_METHOD *method = SSLv23_client_method(); // Создает новый метод для клиента
- SSL_CTX *ctx = SSL_CTX_new(method); // Создает новый контекст
- if (ctx == NULL)
- return -1;
- struct hostent *host;
- struct sockaddr_in addr;
- if ((host = gethostbyname(hostname)) == NULL)
- return -1;
- int sock = socket(PF_INET, SOCK_STREAM, 0); // Как обычно, будем использовать потоковый протокол транспортного уровня ТСР
- // bzero(&addr, sizeof(addr)); // Можно использовать, для дополнительной уверенности, очистив структуру
addr
; но, необязательно - addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- addr.sin_addr.s_addr = *(long *)(host->h_addr);
- if (connect(sock, (struct sockaddr*)&addr, sizeof addr))
- return -1;
- SSL *ssl = SSL_new(ctx); // Создает новое SSL-соединение
- SSL_set_fd(ssl, sock); // Прикрепляет дескриптор сокета
- if (SSL_connect(ssl) == -1) // Осуществляет соединение
- return -1;
- char msg[] = "GET https://site.ru HTTP/1.0\n\n";
- char buf[1024];
- char* buf_sum;
- buf_sum = (char*)malloc(sizeof(char) * 2000);
- int i=0;
- SSL_write(ssl, msg, strlen(msg));
- FILE* fp;
- if((fp=fopen("file_tmp", "wb"))==NULL)
- { // Вначале уничтожаем этот файл (во избежание возможных остатков старого содержимого), если он есть, попутно проверяя, можно ли его создать и открыть
- printf("Ошибка при открытии файла.\n");
- exit(1);
- }
- fclose(fp);
- // Записываем во временный файл (туда запишется весь html-код страницы сервера, вызываемой по протоколу https
- fp=fopen("file_tmp", "ab"); // Записываем в файл с добавлением, каждый раз начиная с конца данных, записанных в предыдущий раз
- fputs("\nServer response from site:\n", fp);
- long bytes_sum = 0, bytes;
- buf[bytes] = 0;
- printf(" strlen=%lu; ", strlen(buf_sum));
- if((buf_sum = (char*)realloc(buf_sum, strlen(buf_sum) + bytes +1))==NULL){ // Увеличиваем выделенную память на число байтов, полученных на этой итерации и добавляем еще 1 байт (для чего, кстати, добавлен этот байт?)
- perror("Allocation error2.");
- exit (0);
- }
- strncat(buf_sum, buf, bytes);
- i++;
- printf("%d ", i);
- fwrite(buf, strlen(buf), 1, fp); // Записываем в файл
- fflush(fp); // Принудительно делаем запись файла на жесткий диск - для надежности: если вдруг программа даст сбой и возникнет ошибка, в файле останутся хотя бы те байты, полученные на предыдущих итерациях. А иначе, если запись на жесткий диск не будет сделана до момента возникновения ошибки (сегментации), эти данные могут быть потеряны.
- }
- fclose(fp);
- SSL_free(ssl); // Освобождает SSL-соединение
- close(sock); // Закрывает сокет
- SSL_CTX_free(ctx); // Освобождает контекст
- printf("%s", buf_sum);
- free(buf_sum);
- printf("\nEnd.\n");
- return 0;
- }
Компилировать программу следует так:
g++ -Wall -o https https.cpp -L/usr/lib -lssl -lcrypto -std=c++11
Вместо site.ru
следует использовать имя какого-нибудь сайта, работающего по протоколу HTTPS.
yandex.ru, google.com
и некоторые другие, по всей видимости, требуют, чтобы запрос был корректным и содержал ВСЕ необходимые протоколы; поэтому при попытке доступа при помощи этой программы они выдадут что-то типа ошибки доступа. Поэтому для тестирования программы следует использовать другие сайты или сформировать полностью корректный GET-запрос.
Как видим, добавились опции -lssl
и -lcrypto
, необходимые для подключения библиотеки openssl
. Кроме того, указан путь к этим модулям, так как по умолчанию компилятор не ищет библиотеки в каталоге usr/lib
.
Как видим, программа весьма похожа на предыдущие сетевые программы, в которых использовались сокеты. Жирным шрифтом выделены места, которые добавлены для реализации криптошифрования на основе библиотеки openssl
.
Что здесь нового?
Вначале задается порт номер 443. Это порт, который на подавляющем большинстве серверов в интернете открыт на прослушивание для соединений по протоколу HTTPS. Затем инициализируется библиотека openssl
, загружаются алгоритмы криптошифрования и т.д. (строчки 12-18).
После этого, как обычно, создается ТСР-сокет, задаются его параметры и делается попытка установления соединения (строчки 23-29).
А вот далее (это также является новым) создается SSL-соединение (строчки 30-33).
В строчке 34, как обычно, задается строка GET-запроса. Конечно, такой «запрос» не является полным, его лучше бы дополнить недостающими заголовками. Откуда их взять? Наиболее простой способ - из заголовков запроса браузера к соответствующему сайту, как это делалось, к примеру, в задании 5.
Так как некоторые HTTPS-сайты отдают достаточно большой объем html-кода, вывод нашей программы в консоль может быть весьма объемным; поэтому целесообразно записать все, что получено от сервера, в файл (под названием file_tmp
), что делается в строчках 61-62.
Также новыми являются строчки 65, 67, которые осуществляют завершение работы с библиотекой openssl
.
Выводы
Итак, как видим, все достаточно просто и отличается от программ, работающих по протоколу НТТР, в основном лишь наличием функций библиотеки openssl
. А вот она уже, в свою очередь (как говорится, «за кулисами») и осуществляет тот самый обмен открытыми ключами, генерацию закрытых ключей на их основе, само шифрование сообщений нашей программы, а также расшифровку сообщений, полученных от сервера. При этом браузеру подается уже готовый, расшифрованный html-код, который он, как обычно, синтаксически анализирует, строит дерево DOM и отображает его на экране.
openssl
. В этом случае придется обновить эту биллиотеку и/или саму операционную систему.
Самостоятельная разработка
↑ К содержанию ↑- Изучите, скомпилируйте предложенный выше код, предварительно выбрав вместо
site.ru
какое-нибудь другие доменное имя. - Весьма полезным будет просмотр зашифрованного трафика, который поступает в сеть от нашей программы (при осуществлении GET-запроса) и который поступает из сети от сервера. Для этого следует использовать утилиту tcpdump, которую нужно запускать с опцией
-A
. - Сравните этот трафик с «обычным» НЕзашифрованным трафиком, который получается, например, в результате взаимодействия браузера и прокси-сервера Можете попробовать расшифровать https-трафик. После чего Вам станет более понятным назначение протокола HTTPS.
С уважением, Салимоненко Д.А.