Tech
January 22, 2024

Как работают SSL сертификаты?

Ну вот случится у тебя CURL: Peer's Certificate has expired, что делать будешь?

Теория

Обычно SSL сертификат необходим, когда клиент-браузер обращается к серверу. Трафик между ними шифруется, чтобы никто третий не смог его прочесть. Происходит это примерно так, как показано на рисунке 1 из интернетов:

Рисунок 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.

Рисунок 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

Самоподписанный сертификат сервера

Допустим,

  1. владелец сам подписал сертификат
  2. CURL-у не известен CA которым подписан сертификат
  3. база CA устарела

При запросе на такой сервер предыдущей командой вы получите такое сообщение

> 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. Для изменения этого существуют ключи:

  • --cert-type TYPE Certificate file type (DER/PEM/ENG) (SSL)
  • --key-type TYPE Private key file type (DER/PEM/ENG) (SSL)

Пример на 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

md5 хэш модуля CSR:

openssl req -noout -modulus -in CSR.csr | openssl md5

Если полученные md5 хэши одинаковы — значит файлы (Сертификат, Приватный Ключ и CSR) соответствую друг другу.

Форматы SSL сертификатов

Примечание: имейте ввиду, что расширение файла может быть некорректным, поэтому 100% доверять ему нет смысла.

PEM

ASCII файл с Base64, который обособлен тегами ----- BEGIN CERTIFICATE ----- и ----- END CERTIFICATE -----. Может иметь расшрения .pem, .crt, .cer, и .key, что порою может ввести в заблуждение, но сам по себе PEM человекоузнаваем. Может включать в себя промежуточные сертификаты CA.


DER

Бинарный файл, может иметь расширения .cet или .der. Легко открывается в Windows.


PKCS#7 и P7B

Подобно PEM сертификатам это Base64 ASCII обособленный тегами ----- BEGIN PKCS7 ------ и ----- END PKCS7 -----. Может включать в себя промежуточные сертификаты CA Обычно имеет расширения .p7b или .p7c.


PFX PKCS#12

Файл в который укомплектованы сертификат сервера, промежуточные сертификаты CA и приватный ключ. Таким файлом лучше не разбрасываться. Обычно он имеет расширениям .pfx или .p12.

Как убедиться, какой перед вами сертификат

узнать формат сертификата

Следует отметить, что это не такая явная вещь. Даже если вы на глаз определили формат сертификата, он может быть некорректным.

PEM

openssl x509 -in certificate.pem -text -noout

DER

openssl x509 -inform der -in certificate.cer -text -noout

PKCS#7 и P7B

openssl pkcs7 -in certificate.p7b

PFX PKCS#12

Вообще такие сертификаты часто зашифрованы и нужно знать пароль, чтобы с ним нормально работать. Команда ниже выдаст ошибку если вы ей укажите не PKCS#12 сертификат.

openssl pkcs12 -info -in certificate.p12

Конвертация сертификатов

Ниже представлены распространенные примеры конвертации сертификатов.

Конвертировать PEM в DER

openssl x509 -outform der -in certificate.pem -out certificate.der

Конвертировать PEM в P7B

openssl crl2pkcs7 -nocrl -certfile certificate.cer -out certificate.p7b -certfile CACert.cer

Конвертировать PEM в PFX

openssl pkcs12 -export -out certificate.pfx -inkey privateKey.key -in certificate.crt -certfile CACert.crt

Конвертировать DER в PEM

openssl x509 -inform der -in certificate.cer -out certificate.pem

Конвертировать P7B в PEM

openssl pkcs7 -print_certs -in certificate.p7b -out certificate.cer

Конвертировать P7B в PFX

openssl pkcs7 -print_certs -in certificate.p7b -out certificate.ceropenssl pkcs12 -export -in certificate.cer -inkey privateKey.key -out certificate.pfx -certfile CACert.cer

Конвертировать PFX в PEM

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"

Public-Key: (2048 bit)

Ссылки

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