Как решить проблему PHP Curl с центром сертификации HTTPS в Windows
Успешный HTTPS-запрос включает в проверку HTTP-клиентом предоставленного сервером TLS-сертификата по списку известных и доверенных корневых сертификатов. Расширение PHP Curl не отличается от других; расширение Curl использует libcurl
для выполнения HTTPS-запроса, а libcurl
, в свою очередь, использует библиотеку TLS, такую как OpenSSL, для проверки запроса.
Расширение Curl требует наличия файла, содержащего все доверенные корневые сертификаты для завершения проверки HTTPS, и PHP предоставляет его в виде директивы в файле php.ini
.
В Linux, BSD и macOS libcurl может по умолчанию использовать корневые сертификаты системы, но в Windows это невозможно, поскольку Windows не поставляется с одним файлом, содержащим все корневые сертификаты системы.
В этой статье рассматриваются два возможных подхода к успешному выполнению HTTPS-запросов с помощью расширения Curl, а также то, что не следует делать, чтобы HTTPS-запросы оставались небезопасными.
Почему это не работает
$ch = curl_init('https://php.watch');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch); // false
curl_error($ch);
// SSL certificate problem: unable to get local issuer certificate
Если вызовы curl_exec
завершаются с ответом false
, а curl_error
указывает на проблему SSL certificate problem: unable to get local issuer certificate error
, это означает, что Curl не был предоставлен файл, содержащий корневые сертификаты, или он не смог его обнаружить.
Эта ошибка редко встречается в системах Linux, BSD и macOS, но довольно распространена в Windows, так как нет специального файла для получения корневых сертификатов, а PHP не поставляет список корневых сертификатов сам по себе.
Решение состоит в том, чтобы предоставить файл с актуальными корневыми сертификатами или, в идеале, позволить Curl разобрать собственное хранилище корневых сертификатов, предоставляемое базовой операционной системой.
Используйте системные центры сертификации
В Curl 7.71 и более поздних версиях можно установить опцию для запроса Curl на использование собственных (системных) корневых сертификатов. Это работает даже в Windows, где Curl анализирует системные корневые сертификаты и использует их.
Когда опция CURLOPT_SSL_OPTIONS
имеет значение CURLSSLOPT_NATIVE_CA
или битовую маску, содержащую эти биты, Curl пытается использовать нативное хранилище корневых сертификатов, в зависимости от возможностей и версий базовой библиотеки TLS.
Это рекомендуемое исправление, если расширение Curl собрано с Curl 7.71 или более поздней версией и PHP 8.2 или более поздней версией.
$ch = curl_init('https://php.watch');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
curl_exec($ch);
Обратите внимание, что в приведённом выше фрагменте не проверяется версия Curl и версия PHP, а предполагается, что требования к версии PHP и Curl выполнены. Далее приведён пример условного добавления опции Curl:
$ch = curl_init('https://php.watch');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if (defined('CURLSSLOPT_NATIVE_CA')
&& version_compare(curl_version()['version'], '7.71', '>=')) {
curl_setopt($ch, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
}
curl_exec($ch);
Загрузите и сохраните файл cacert.pem.
Для приложений, работающих на PHP версии старше 8.2 (где константа CURLSSLOPT_NATIVE_CA
недоступна), или когда версия Curl старше 7.71, рекомендуемым альтернативным решением является загрузка файла корневого сертификата, совместимого с Curl, и настройка PHP или запроса Curl на его использование.
Проект Curl поддерживает актуальный список сертификатов. См. CA Certificates extracted from Mozilla.
Загрузите файл cacert.pem
Переместите файл в каталог, доступный PHP и веб-серверу. Например, в
C:/php/cacert.pem
.Отредактируйте файл
php.ini
и измените записьcurl.cainfo
так, чтобы она указывала на абсолютный путь к файлуcacert.pem
.[curl]
; A default value for the CURLOPT_CAINFO option. This is required to be an
; absolute path.
-;curl.cainfo =
+curl.cainfo = "C:/php/cacert.pem"При необходимости перезапустите веб-сервер (например, Apache), чтобы перезагрузить INI-файл.
Недостатком этого подхода является необходимость регулярного обновления файла cacert.pem
. Файл cacert.pem
, предоставляемый, например, проектом Curl, извлекается из корневого хранилища, поддерживаемого Mozilla. В среднем этот список и сам файл обновляются 4-5 раз в год. Чтобы обеспечить совместимость с последним списком корневых сертификатов, регулярно обновляйте локальную копию этого файла.
Если модификация INI-файла невозможна, укажите абсолютный путь к файлу cacert.pem
в запросе Curl:
$ch = curl_init('https://php.watch');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_CAINFO, 'C:/php/cacert.pem');
curl_exec($ch);
На PHP 8.2+ с Curl 7.77 с помощью опции CURLOPT_CAINFO_BLOB
можно получить строку, содержащую содержимое cacert.pem
.
НЕ отключайте проверку сертификатов
На форумах и в статьях в Интернете часто встречается неправильный совет — отключить проверку сертификатов. Это проблема безопасности, поскольку без проверки сертификата Curl с радостью примет любой сертификат TLS, включая потенциально перехваченное или изменённое содержимое.
В следующем примере показано такое часто предлагаемое решение, которое небезопасно и не рекомендуется.
$ch = curl_init('https://php.watch');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Отключение валидации сертификатов.
// НЕ ДЕЛАЙТЕ ЭТОГО!!!
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_exec($ch);