Наш канал в telegram

Инструкция по созданию Telegram ботов. Дополнение. Как отправлять правильные https-запросы через curl и при чём здесь SSL-cертификаты

Вступление

В предыдущей части, для отправки get-запросов по https при помощи curl, использовалась функция вот такого вида:

Код под катом

	curl_setopt($ch, CURLOPT_CAINFO, "/path/to/certificate/cabundle.pem");

[свернуть]

При этом справа, в комментариях, указано, что опции CURLOPT_SSL_VERIFYPEER и CURLOPT_SSL_VERIFYHOST для примера установлены в false. Вот об этом и будет сегодняшняя статья. Я расскажу о том, как дописать эту функцию до правильного рабочего варианта и почему это важно. Для того, чтобы всё было понятно придётся сделать вступление в виде краткого описания базовых принципов функционирования https.

Как работает https (кратко)

Расширение протокола http для организации безопасных шифрованных сеансов связи называется https. Это по-сути всё тот же http трафик, только передаваемый в ходе защищённого сеанса связи. Защита сеанса обеспечивается криптографическим протоколом SSL. Cовременная стандартизированная версия протокола SSL называется TLS, но в обиходе все протоколы этой группы продолжают называть общим именем SSL. SSL работает на уровень ниже протокола http, то есть весь http-трафик оказывается как бы «завёрнут» внутрь SSL-трафика.

Защита соединения в протоколе SSL построена на трёх вещах:

  1. Аутентификация сторон
  2. Шифрование всей полезной информации (в том числе полезного http траффика)
  3. Контроль целостности передаваемой информации

Процесс аутентификации позволяет обоим участникам сеанса связи убедиться, что их партнёр именно тот, за кого он себя выдаёт. Этот процесс производится в самом начале установления https-соединения, в ходе так называемой процедуры рукопожатия (handshake), ещё до отправки самого запроса (до отправки полезного http-трафика). Он позволяет избежать атаки, условно называемой «человек посередине», когда между вами и вашим абонентом вклинивается посредник. Если этот посредник сможет выдать себя за вашего партнёра, то дальнейшее шифрование трафика не будет иметь никакого смысла, поскольку посредник сможет обмениваться с вами данными от имени вашего партнёра, а с вашим партнёром от вашего имени.

Для однозначной идентификации партнёра используется система так называемых SSL-сертификатов. SSL-сертификат — это такой электронный документ, выдаваемый специальными доверенными центрами и подтверждающий, что заявленный в сертификате открытый ключ шифрования принадлежит указанному владельцу. В сертификате, помимо открытого ключа, используемого для асимметричного шифрования данных в процессе аутентификации, и данных о владельце сертификата, указано кем и на какой срок выдан этот сертификат (то есть это примерно как паспорт у людей).

Для защиты от подделывания сертификат подписан электронной цифровой подписью. Электронная подпись формируется с помощью закрытого ключа центра сертификации, а расшифровывается с помощью их открытого ключа (то же самое асимметричное шифрование, только задом наперёд). Таким образом эту электронную подпись может проверить кто угодно (имея сертификат центра сертификации, который содержит их открытый ключ), а подделать никто не может, поскольку не имеет закрытого ключа центра сертификации.

Считается, что если вы доверяете центру сертификации (имеете копию их сертификата, которой доверяете), то можете доверять и всем выданным этим центром сертификатам.

Скажем, браузеры сразу содержат в составе пакета установки сертификаты всех основных центров сертификации, поэтому мы спокойно можем посещать с помощью этих браузеров сайты, сертификаты которых выданы этими центрами.

Эмитенты сертификатов могут иметь иерархическую структуру. Каждое звено в такой цепочке имеет свой сертификат, а для проверки конечного сертификата клиента нужно иметь сертификаты всех звеньев цепочки эмитентов, начиная с корневого. Документы, содержащие такие цепочки сертификатов, называются CA-бандлы (bundles).

Не нужно думать, что вклинивание посредника между вами и вашим партнёром связано с какими-то техническими сложностями. На самом деле такой посредник всегда есть, например, в лице провайдера. Так что технически, единственное, что останавливает, скажем, тот же РКН от онлайн-мониторинга вашего https-трафика при помощи провайдеров — это невозможность подделать SSL-сертификаты. Так что государство всегда сможет за вами подглядеть, разница только в том, что в одних странах по закону вершиной местных центров выдачи SSL-сертификатов должен являться специальный госорган (такие могут при желании мониторить трафик прямо налету), а в других приняты законы о хранении всего трафика и передаче спецслужбам ключей шифрования по требованиям разных инстанций (такие могут то, что им нужно, потом почитать). Ну ладно, это так, — небольшое лирическое отступление.

Резюмируя, — если вы хотите, чтобы сеансы связи по https были надёжно защищены — нужно обязательно проверять партнёров, с которыми вы связываетесь, при помощи предоставляемых ими SSL-сертификатов (чтобы убедиться, что эти партнёры именно те, за кого себя выдают).

Как работать с https через curl

Переходим к главному. В представленном для примера коде я отменил проверку сертификатов, установив опции CURLOPT_SSL_VERIFYHOST и CURLOPT_SSL_VERIFYPEER в false. Теперь вы знаете чем такая отмена грозит, поэтому далее давайте рассмотрим как правильно настроить указанные опции и провести проверку сертификатов сервера, которому вы отправляете запрос.

Итак, первая опция — CURLOPT_SSL_VERIFYHOST. Эта опция устанавливает, нужно ли проверять указанное в сертификате имя хоста. CURLOPT_SSL_VERIFYHOST можно установить в 0 или в 2 (в ранних версиях curl был ещё вариант 1, но сейчас его отменили). По-умолчанию стоит 2, но лучше всегда задавать это значение явно. Значение 0 отменяет проверку имени хоста, а вот при значении 2 curl будет извлекать имя хоста из полученного от сервера SSL-сертификата и сравнивать его с именем хоста, на который должен быть отправлен запрос. Если эти имена не совпадут — процесс установления соединения будет прерван с ошибкой и запрос не будет отправлен.

Вторая опция — CURLOPT_SSL_VERIFYPEER. Эта опция устанавливает, нужно ли проверять подлинность присланного сертификата сервера чтобы решить, можно ли этому сертификату доверять. CURLOPT_SSL_VERIFYPEER можно установить в false или true. Когда опция установлена в false — любой присланный сертификат считается подлинным и вызывает доверие. Если же опция установлена в true, то curl будет пытаться выполнить проверку подлинности присланного хостом сертификата. Чтобы выполнить эту проверку, курлу нужно иметь открытый ключ центра сертификации, выдавшего хосту сертификат. Для этого, в свою очередь, нужно скачать сертификат этого центра, положить к себе на диск и при выполнения запроса указывать курлу где он лежит. Это делается при помощи опции CURLOPT_CAINFO. Она имеет следующий синтаксис:

	curl_setopt($ch, CURLOPT_CAPATH, "/path/to/certificates");

Здесь path_to_certificate — это путь к папке (в доке написано, что для CAINFO может потребоваться абсолютный путь), а cabundle.pem — сам файл, содержащий сохранённый сертификат или цепочку сертификатов.

Иногда нам может понадобиться иметь для проверки несколько разных файлов с сертификатами, например, если мы хотим отправлять запросы к разным хостам, сертификаты которым выдавали разные центры сертификации. В этом случае вместо CURLOPT_CAINFO можно воспользоваться другой опцией, — CURLOPT_CAPATH. Эта опция позволяет ссылаться не на один файл, а на директорию, содержащую все нужные файлы сертификатов. Синтаксис:

	curl_setopt($ch, CURLOPT_CAPATH, "/path/to/certificates");

С последней опцией, правда, есть одна проблема, — все сертификаты в указанной директории, не имеющие специального вида имена, игнорируются. Для того, чтобы правильно подготовить папку с сертификатами к использованию опции CURLOPT_CAPATH — можно воспользоваться специальной утилитой c_rehash из пакета openssl-perl (она автоматически создаст копии файлов с нужными именами).

Учитывая всё вышеизложенное, правильный curl-запрос с использованием https-соединения должен выглядеть следующим образом:

Вариант 1

function execRequest($telegram_req_url){
$telegram_ch = curl_init();
curl_setopt($telegram_ch, CURLOPT_URL, $telegram_req_url); // адрес c https
curl_setopt($telegram_ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($telegram_ch, CURLOPT_HTTPGET, true);
curl_setopt($telegram_ch, CURLOPT_SSL_VERIFYHOST, 2);      // проверяем совпадение имени хоста в url и в сертификате
curl_setopt($telegram_ch, CURLOPT_SSL_VERIFYPEER, true);   // выполняем проверку подлинности сертификата
curl_setopt($telegram_ch, CURLOPT_CAINFO, "/path/to/certificate/cabundle.pem");	// бандл сертификатов (должен содержать сертификат центра, выдавшего сертификат хоста)
curl_setopt($telegram_ch, CURLOPT_MAXREDIRS, 10);
curl_setopt($telegram_ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($telegram_ch, CURLOPT_TIMEOUT, 20);
 
$telegram_ch_result = curl_exec($telegram_ch);
return $telegram_ch_result;
}

[свернуть]
Вариант 2

function execRequest($telegram_req_url){
$telegram_ch = curl_init();
curl_setopt($telegram_ch, CURLOPT_URL, $telegram_req_url); // адрес c https
curl_setopt($telegram_ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($telegram_ch, CURLOPT_HTTPGET, true);
curl_setopt($telegram_ch, CURLOPT_SSL_VERIFYHOST, 2);      // проверяем совпадение имени хоста в url и в сертификате
curl_setopt($telegram_ch, CURLOPT_SSL_VERIFYPEER, true);   // выполняем проверку подлинности сертификата
curl_setopt($telegram_ch, CURLOPT_CAPATH, "/path/to/certificates"); // папка с сертификатами (должна содержать сертификат центра, выдавшего сертификат хоста)
curl_setopt($telegram_ch, CURLOPT_MAXREDIRS, 10);
curl_setopt($telegram_ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($telegram_ch, CURLOPT_TIMEOUT, 20);
 
$telegram_ch_result = curl_exec($telegram_ch);
return $telegram_ch_result;
}

[свернуть]

P.S.

А как же сервер нас проверяет, у нас же никакого сертификата нет? Тут всё просто. Если это публичный сервис, то ему нет смысла кому-то доверять или не доверять, — он должен обслуживать все запросы от кого угодно. Аутентификация пользователей публичными сервисами производится не на основе сертификатов, а при помощи других механизмов (например, пары логин/пароль).

В принципе, клиент точно так же вполне может иметь свой сертификат, который сервер будет использовать для аутентификации клиента, однако по ряду причин для клиентов такой механизм неудобен (скажем, как таскать сертификат с собой, если клиент не привязан к машине) и обычно используется только в непубличных закрытых сетях (и даже там жёстко привязывать клиента к машине imho глупость, должны быть разграничены именно права клиентов, а не права машин в сети).

P.P.S.

Сохранить себе на диск сертификат доверенного центра, выдавшего сертификат какому-либо ресурсу, можно прямо с помощью браузера:

Например, в мозилле это делается так:

  1. Заходим браузером на нужный ресурс по https
  2. Щёлкаем по зелёному замку в адресной строке и жмём на всплывающей вкладке стрелку вправо напротив замка и далее внизу кнопку «Подробнее»
  3. В открывшемся окне жмём «Просмотреть сертификат» и на следующей открывшейся странице переходим на вкладку «Подробности»
  4. В верхней части окна, подписанной «Иерархия сертификатов» выбираем нужный сертификат и жмём внизу «Экспортировать»
  5. Выбираем тип файла «Сертификат X.509 в формате PEM», указываем куда его сохранить и как назвать. (Так же можно выгрузить сразу весь бандл)

Вот отсюда можно скачать бандл сертификатов всех основных центров сертификации, выгруженный из мозиллы.

  1. Часть 1. Что такое Telegram боты и как они работают
  2. Часть 2. Регистрация аккаунтов Telegram ботов в картинках
  3. Часть 3. Пишем простого чат-бота для Telegram на чистом php (webhook)
  4. Часть 4. Прикручиваем MySQL к чат-боту для Telegram на php (webhook)
  5. Часть 5. Пишем Telegram бота на php для работы через longpolling
  6. Дополнение. Как отправлять правильные https-запросы через curl и при чём здесь SSL-cертификаты

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