Как работают SSL сертификаты?
Ну вот случится у тебя CURL: Peer's Certificate has expired
, что делать будешь?
Теория
Обычно SSL сертификат необходим, когда клиент-браузер обращается к серверу. Трафик между ними шифруется, чтобы никто третий не смог его прочесть. Происходит это примерно так, как показано на рисунке 1 из интернетов:
На рисунке хорошо показано, что алгоритм шифрует трафик с помощью public key который был передан в открытом виде внутри сертификата клиенту. Но вот назад этот трафик расшифровывается только с помощью privat key. Это позволяет любому передать серверу session key, который будет известен только клиенту и серверу. В дальнейшем клиент и сервер уже будут общаться шифруя трафик с помощью session key.
Отлично, но вот незадача. Сам факт этого обмена может быть скомпрометирован. Ведь если кто-то перехватит этот обмен, он сможет подменить сертификат, установив два безопасных соединения между клиентом и сервером.
Для решения этой проблемы нам необходимо точно знать, что сертификат, который мы получили от сервера, принадлежит этому серверу.
Как вариант, мы можем заранее получить public key из доверенного источника. Например напрямую, приехав к владельцу сервера и скопировав public key на флешку. Если у владельца сервера никто не украдет privat key, мы почти точно будем знать, что на той стороне именно владелец privat key (потому что он сможет это подтвердить, см. картинку).
Однако это сложно. Ведь и пользоваться хочется не одним сервером, а множеством. Для этого были созданы удостоверающие центры, так называемые Certificate Authority (CA). Удостоверяющие центры подписывают сертификаты (именно сертификаты) своей подписью. Они могут подписать очень много сертификатов. И вам не придется ставить сертификат каждого сервера, достаточно будет скачать лишь сертификат CA.
В свою очередь, CA проверяет подлинность владельца сертификата за вас, добавляя с подписью CN (Common Name). Вообще это поле может содержать просто имя. Но если это классический SSL сертификат, там будет содержаться доменное имя (доменные имена), в том числе wildcard, или IP адрес.
Теперь у нас есть посредник. Эдакий нотариус. Пример взаимодействия в таком случае немного усложнится, теперь проверка сертификата сервера - это один из этапов. Смотрите рисунок 2.
Обратите внимание, что на схеме сервер также проверяет сертификат клиента с помощью CA). На самом деле в типовых случаях достаточно проверки сервера, т.к. установленное в таком случае соединение в рамках сессии может быть защищено session key, который на рисунке 2 представлен как Symmetric Key, а в остальном поможет авторизация. Но верифицировать клиента тоже нормально. И тогда процедура верификации в обе стороны ничем не отличается. Можно прочесть эту статью https://dev64.wordpress.com/2013/06/12/ssl-basic/ чтобы прояснить детали.
Практика
Я буду использовать curl, чтобы немного прояснить, как с ним работать.
Выполним простой запрос. По умолчанию CURL будет использовать известные ему CA и запрос к серверу может выглядеть очень просто. Например:
curl -X GET -i https://ya.ru
Самоподписанный сертификат сервера
При запросе на такой сервер предыдущей командой вы получите такое сообщение
> curl: (60) Peer's certificate issuer has been marked as not trusted by the user.
В данной ситуации можно установить сертификат, например, предварительно скачав его
openssl s_client -showcerts -connect 127.0.0.1:443 </dev/null 2>/dev/null|openssl x509 -outform PEM > ./CA/servercert.pem
А после, его можно использовать в CURL так, указав путь до этого сертификата в параметре --cacert:
curl --cacert ./CA/mycertfile.pem -X GET -i https://127.0.0.1/
или указав путь к папке с такими сертификатами
curl --capath CA/ -X GET -i https://127.0.0.1/
Иногда можно и вовсе проигнорировать сертификат сервера. Например если он истёк, и вы получаете ошибку
curl: (60) Peer's Certificate has expired.
Можно использовать ключ -k (будьте ответственны)
curl -k -X GET -i https://127.0.0.1/
Установка CA сертификата в CentOS
Вы также можете установить CA сертификаты скачав их и разместив в директории /etc/pki/ca-trust/source/anchors.
Например предположим, что на сайте example.server.ru установлен сертификат GlobalSign Extended Validation CA - SHA256-G3 при этом когда мы делаем запрос с помощью $ curl https://exampple.server.ru
получаем ответ curl: (60) Peer's Certificate issuer is not recognized
.
Решить проблему можно и на уровне сервера. Если сертификат на example.server.ru установлен корректно, то скорее всего ваш сервер не доверяет ему, т.к. на сервере нет сертификатов удостоверяющего центра. На сайтах CA почти всегда можно их скачать (здесь иногда случается квест). Например для GlobalSing G3 можно найти Intermediate сертификат здесь https://support.globalsign.com/ca-certificates/intermediate-certificates/extendedssl-intermediate-certificates
Скачайте его в специальную директорию (кнопка Download Certificate (Binary/DER Encoded)). Ниже приведен весь пример действий
curl -I https://example.server.ru
ответ curl: (60) Peer's Certificate issuer is not recognized.
cd /etc/pki/ca-trust/source/anchors wget https://secure.globalsign.com/cacert/gsextendvalsha2g3r3.crt update-ca-trust extract curl -I https://example.server.ru
ответ HTTP/1.1 200 OK
Двухсторонняя верификация
Как упомянуто выше, сервер тоже может проверить сертификат клиента. Например, вы написали два сервиса, которые должны общаться друг с другом, но между ними нет аутентификации на уровне приложения. Это может быть реализовано на уровне SSL (https).
Поэтому CURL как клиенту теперь необходимо передать собственный подписанный сертификат (который содержит также и публичный ключ), владеть приватным ключом, чтобы доказать свою подлинность, ну и CA сертификат, чтобы доверять второй стороне.
Запросы CURL теперь могут выглядеть так:
curl --capath ./CA --cert ./cert.pem --key ./key.pem -X GET -i 127.0.0.1:443
добавились параметры --key - приватный ключ клиента и --cert - сертификат клиента.
Стоит отметить, что CURL по умолчанию будет работать с сертификатами так, будто они в формате PEM. Для изменения этого существуют ключи:
Пример на PHP
Давайте закрепим, сделав типовой запрос используя библиотеку CURL в PHP (в коде всё будет прокомментировано):
<?php $dir = $_SERVER["DOCUMENT_ROOT"]?$_SERVER["DOCUMENT_ROOT"]:__DIR__; // Данные для соединения $url = 'https://secure-server.server.ru/virtshowwindow/create'; $port = '12001'; // Клиентские сертификат и приватный ключ (PEM): $crt = '/client_certificate.pem'; $key = '/client_private_key.pem'; // Папка с сертификатами в формате PEM которым будем доверять $capath = '/CA'; // Инициализируем CURL $curl = curl_init(); // Подготовим данные в JSON (в примере используется этот формат данных для обмена) $output_data = json_encode(['name'=>'Alexander','city'=>'Novosibirsk'], JSON_UNESCAPED_UNICODE); // Устанавливаем опции, описание опций // доступно на странице документации PHP // https://www.php.net/manual/ru/function.curl-setopt.php curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_PORT, $port); curl_setopt($curl, CURLOPT_CAPATH, $dir.$capath); // При необходимости можно отключить провеку сертификата, тогда параметр CURLOPT_CAPATH потеряет смысл // curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // Обратите внимание, что в PHP есть возможность отключить проверку хоста отдельно, в то время как у curl в коммандной строке есть лишь параметр -k // curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_SSLCERT, $dir.$crt); curl_setopt($curl, CURLOPT_SSLKEY, $dir.$key); // Если кто-то похитит ваш приватный ключ, ему потребуется знать еще и серкретную фразу чтобы работать с ним, если она была добавлена в приватный ключ. // Поэтому если она была установлена, используйте этот параметр: //curl_setopt($curl, CURLOPT_SSLCERTPASSWD, ''); curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_POST, true); // может быть критичным для установке соединения. в документации PHP это хорошо описано curl_setopt($curl, CURLOPT_SSLVERSION,6); curl_setopt($curl, CURLOPT_HTTPHEADER, array( 'Content-Type: application/json;', 'Content-Length: ' . strlen($output_data)) ); curl_setopt($curl, CURLOPT_POSTFIELDS, $output_data); curl_setopt($curl, CURLOPT_VERBOSE, true); $out = curl_exec($curl); if($out !== false){ echo "Success: <br>".PHP_EOL; var_dump($out); }else{ echo "Error: <br>" . PHP_EOL; echo curl_error($curl); } curl_close($curl);
Проверка ключей на соответствие
Выведем значения модулей Приватного Ключа, SSL Сертификата и CSR, а затем преобразуем их в md5 хэши для удобного сравнения.
md5 хэш модуля SSL сертификата:
openssl x509 -noout -modulus -in CERTIFICATE.crt | openssl md5
md5 хэш модуля Приватного Ключа:
openssl rsa -noout -modulus -in PRIVATEKEY.key | openssl md5
openssl req -noout -modulus -in CSR.csr | openssl md5
Если полученные md5 хэши одинаковы — значит файлы (Сертификат, Приватный Ключ и CSR) соответствую друг другу.
Форматы SSL сертификатов
Примечание: имейте ввиду, что расширение файла может быть некорректным, поэтому 100% доверять ему нет смысла.
ASCII файл с Base64, который обособлен тегами ----- BEGIN CERTIFICATE -----
и ----- END CERTIFICATE -----
. Может иметь расшрения .pem, .crt, .cer, и .key, что порою может ввести в заблуждение, но сам по себе PEM человекоузнаваем. Может включать в себя промежуточные сертификаты CA.
Бинарный файл, может иметь расширения .cet или .der. Легко открывается в Windows.
Подобно PEM сертификатам это Base64 ASCII обособленный тегами ----- BEGIN PKCS7 ------
и ----- END PKCS7 -----
. Может включать в себя промежуточные сертификаты CA Обычно имеет расширения .p7b или .p7c.
Файл в который укомплектованы сертификат сервера, промежуточные сертификаты CA и приватный ключ. Таким файлом лучше не разбрасываться. Обычно он имеет расширениям .pfx или .p12.
Как убедиться, какой перед вами сертификат
Следует отметить, что это не такая явная вещь. Даже если вы на глаз определили формат сертификата, он может быть некорректным.
openssl x509 -in certificate.pem -text -noout
openssl x509 -inform der -in certificate.cer -text -noout
openssl pkcs7 -in certificate.p7b
Вообще такие сертификаты часто зашифрованы и нужно знать пароль, чтобы с ним нормально работать. Команда ниже выдаст ошибку если вы ей укажите не PKCS#12 сертификат.
openssl pkcs12 -info -in certificate.p12
Конвертация сертификатов
Ниже представлены распространенные примеры конвертации сертификатов.
openssl x509 -outform der -in certificate.pem -out certificate.der
openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CACert.cer
openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt
openssl x509 -inform der -in certificate.cer -out certificate.pem
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer
openssl pkcs7 -print_certs -in certificate.p7b -out certificate.ceropenssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx -certfile CACert.cer
openssl pkcs12 -in certificate.pfx -out certificate.cer -nodes
Узнать длину
openssl rsa -in secret.key -text -noout | grep "Private-Key"
Private-Key: (2048 bit)
Извлечь Длину Ключа из SSL Сертификата
openssl x509 -in certificate.crt -text -noout | grep "Public-Key"
RSA Public-Key: (2048 bit)
Узнать Длину Ключа Шифрования HTTPS Сайта
echo | openssl s_client -connect google.com:443 2>/dev/null | openssl x509 -text -noout | grep "Public-Key"
Ссылки
https://dev64.wordpress.com/2013/06/12/ssl-basic/
https://www.php.net/manual/ru/function.curl-setopt.php
https://curl.haxx.se/libcurl/c/CURLOPT_CAPATH.html
https://curl.haxx.se/libcurl/c/CURLOPT_CAINFO.html
https://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
https://curl.haxx.se/libcurl/c/CURLOPT_SSLKEY.html
https://stuff-things.net/2015/09/28/configuring-apache-for-ssl-client-certificate-authentication/
https://www.shellhacks.com/ru/openssl-check-private-key-matches-ssl-certificate-csr/
https://www.shellhacks.com/ru/openssl-find-ssl-key-length-linux-command-line/
https://www.emaro-ssl.ru/blog/convert-ssl-certificate-formats/
https://www.sslshopper.com/article-most-common-openssl-commands.html