Анонс

В предыдущей статье "Сам себе PKI: Теория на примере Let’s Encrypt" мы рассмотрели теорию и разобрали пример, как происходит установка HTTPS соединения между браузером и веб-сайтом при использовании сертификатов Let's Encrypt. В этой статье перейдем к практике и самостоятельно реализуем ту же самую схему: 

  • Сначала вручную (с помощью OpenSSL) пройдем цепочку генерации и использования самоподписанных сертификатов в HTTPS сервере. 

  • Далее развернем собственный удостоверяющий центр Smallstep и будем получать сертификаты, подписанные им.

Схема PKI остаётся той же, только изменяются компоненты:

  • В качестве CA вместо Let's Encrypt, будет использоваться OpenSSL и CA Smallstep.

  • В роли Web-сервера вместо Интернет веб-сайта, будет выступать простой HTTPS сервер, запущенный на Node.js.

Практика Self-signed certificate

OpenSSL 

Начнем с классики криптографии OpenSSL. Сейчас широко используется версия 1.1.1, но ее поддержка заканчивается в 2023. Следующая версия 3.0 будет поддерживаться до 2026 года. Проверка установленной версии:

openssl version

OpenSSL 1.1.1k FIPS 25 Mar 2021

Если OpenSSL не установлен, то его можно установить на всех востребованных платформах.

Реализация TLS

  • Для нашего CA генерируем приватный ключ 2048-бит RSA. Администратор CA должен хранить его в секрете от всех: 

openssl genrsa -out root_ca.key 2048

  • Далее для нашего CA генерируем X.509 сертификат на 365 дней (root_ca.crt) и подписываем его приватным ключом (root_ca.key). Получается самоподписанный сертификат, его можно и нужно будет скопировать на все компьютеры, между которыми необходимо организовать TLS соединение. При ответах на вопросы, которые будет задавать OpenSSL, можно указывать произвольные данные, CA я назвал "My CN": 

openssl req -x509 -new -key root_ca.key -days 365 -out root_ca.crt

  • Смотрим получившийся сертификат, убеждаемся, что данные в нем корректные:

openssl x509 -text -in root_ca.crt

  • Генерируем приватный ключ для HTTPS сервера, его нужно будет скопировать только на компьютер, на котором он будет работать и никуда больше:

openssl genrsa -out server.key 2048

  • Генерируем запрос на сертификат CSR. В этом запросе нужно передать конкретные значения в параметре "subj". Это должны быть один или несколько параметров CN (Common Name), которые будут прописаны в атрибутах сертификата "Subject" и "Subject Alternative Name".  Фактически CN-ы - это IP адреса и имена хостов, по которым будет осуществляться доступ к серверу. Имена хостов могут быть как в формате доменного имени, так и FQDN

openssl req -new -key server.key -subj "/CN=xx.xx.xx.xx/CN=server/CN=server.example.com" -out server.csr

  • Можно посмотреть получившийся запрос на сертификат и убедиться в его корректности:

openssl req -text -in server.csr

  • Теперь генерируем сертификат X.509 (server.crt) на 365 дней для HTTPS сервера и подписываем его приватным ключом CA (root_ca.key):

openssl x509 -req -in server.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out server.crt -days 365 -extensions SAN -extfile openssl.cnf

  • Файл openssl.cnf должен содержать список SAN (Subject Alternative Name) идентичный тому, что был в CSR запросе на сертификат:

[SAN]
subjectAltName = @alt_names
[alt_names]
IP.1 = xx.xx.xx.xx
DNS.1 = server
DNS.2 = server.example.com
  • Можно посмотреть на получившийся сертификат и убедиться в его корректности:

openssl x509 -text -in root_ca.crt

  • С ключами и сертификатами закончили, переходим к запуску HTTPS сервера. Копируем следующие файлы на компьютер, на котором будет работать HTTPS сервер: 

    • root_ca.crt

    • server.key

    • server.crt

  • Пишем простейший HTTPS сервер, на JS:

const fs = require('fs');
const https = require('https');
 
https
  .createServer(
    {
      requestCert: false,
      rejectUnauthorized: false,
      ca: fs.readFileSync('root_ca.crt'),
      cert: fs.readFileSync('server.crt'),
      key: fs.readFileSync('server.key')
    },
    (req, res) => {
      console.log("req.client.authorized: ", req.client.authorized)
      if (req.client.authorized) {
        const cert = req.socket.getPeerCertificate(false) // true - получить все сертификаты, false - последний
        console.log("cert: ", cert)
    }
      res.end('Hello, world!');
    }
  )
  .listen(9443);
  • Запускать данный скрипт будем с помощью Node.js. Если Node.js не установлен, то устанавливаем:

sudo yum groupinstall 'Development Tools'

sudo yum install nodejs

  • Проверяем, что Node и Npm установились успешно:

npm -v

node -v

  • Запускаем сервер:

node server.js

  • Проверяем сервер curl-ом:

curl https://server.example.com:9443

При первом запросе возникнет ошибка: 

curl: (60) SSL certificate problem: self signed certificate in certificate chain

Это значит, что самоподписанный корневой сертификат нашего CA (root_ca.crt) не находится в хранилище доверительных сертификатов. Процесс загрузки сертификатов в это хранилище специфичен для операционной системы. Приведу алгоритм для RH Linux:

  • Если сертификат не в PEM (текстовом) формате, то его надо сконвертировать в текстовой формат:

openssl x509 -in root_ca.crt -out root_ca.pem -outform PEM

  • Копируем сертификат в определенный каталог на сервере. ДляRH 7 и выше это каталог:

/etc/pki/ca-trust/source/anchors

  • Выполняем команду обновления хранилища сертификатов:

update-ca-trust

  • Теперь curl проходит 

curl https://server.example.com:9443

Hello, world!

  • на Windows есть специфика - в curl необходимо добавлять параметр "ssl-no-revoke", чтобы не проверялся список отозванных сертификатов:

curl --ssl-no-revoke https://server.example.com:9443

  • При этом сервер выведет в консоль строку:

req.client.authorized: false

Это значит, что мы установили TLS соединение только с проверкой сертификата сервера, а не клиента. Переходим к mTLS.

Реализация mTLS 

В коде сервера включаем mTLS, а именно выставляем в true параметр requestCert, чтобы HTTPS сервер запрашивал клиентский сертификат и параметр rejectUnauthorized, чтобы сервер отклонял запросы клиентов, которые не прошли авторизацию:

{
requestCert: true,
rejectUnauthorized: true,
}

Алгоритм генерации клиентских сертификатов аналогичен серверным.

  • Генерируем приватный ключ для клиента:

openssl genrsa -out client.key 2048

  • Генерируем запрос на сертификат CSR со списком CN:

openssl req -new -key client.key -subj "/CN=xx.xx.xx.xx/CN=client/CN=client.example.com" -out client.csr

  • Генерируем сертификат для клиента и подписываем его приватным ключом CA:

openssl x509 -req -in client.csr -CA root_ca.crt -CAkey root_ca.key -CAcreateserial -out client.crt -days 365 -extensions SAN -extfile openssl.cnf

Файл openssl.cnf - такой же как и для сервера, только вместо "server" пишем "client". 

  • Копируем следующие файлы на компьютер, на котором будет работать клиент: 

    • root_ca.crt

    • client.key

    • client.crt

  • Теперь если просто вызвать curl:

curl https://server.example.com:9443

  • То произойдет ошибка авторизации

curl: (35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

  • Поэтому вызываем curl с дополнительными параметрами, в которых указываем ключ клиента и сертификаты клиента и СА: 

curl --cert ./client.crt --key client.key --cacert root_ca.crt https://server.example.com:9443

  • Теперь запрос проходит, при этом сервер выведет в консоль следующую строку и далее содержимое полученного от клиента сертификата:

req.client.authorized: true 

Это значит, что мы установили mTLS соединение с проверкой сертификата как сервера, так и клиента. Теперь установим mTLS из браузера.

Реализация mTLS в браузере 

Если просто запустить наш запрос к серверу (https://server.example.com:9443) из браузера, то выдается ошибка клиентского сертификата ERR_BAD_SSL_CLIENT_AUTH_CERT. Рабочие адреса закрасил желтым:

Для загрузки в браузер клиентский сертификат и приватный ключ надо сконвертировать в формат p12. Этот формат определяется стандартом PKCS #12 и является архивом для хранения нескольких объектов. В нашем случае файл будет хранить клиентский сертификат и его приватный ключ. Понятно, что приватный ключ - это чувствительная к разглашению информация и ее владелец не должен выкладывать p12 сертификат в открытый доступ. Для конвертации тоже используем OpenSSL:

openssl pkcs12 -inkey client.key -in client.crt -export -out client.p12

Импортируем client.p12 в браузер. Для этого, например в Chrome, идем в меню: Settings / Privacy and security / Manage certificates, в открывшемся окне нажимаем Import, выбираем наш файл client.p12 и место хранения "Personal". Видим, что клиентский сертификат, выданный нашим удостоверяющим центром "My CN", появился  в хранилище персональных сертификатов:

Вызываем запрос заново, уже лучше, но браузер все равно пишет, что соединение не безопасно. Это из-за того, что клиентский сертификат выдан удостоверяющим центром "My CN", которого нет в списке доверительных CA (Trusted Root Certification Authorities). 

Добавляем корневой сертификат нашего CA (root_ca.crt) в Trusted Root Certification Authorities. Делается с помощью того же вызова Import, что и для клиентского сертификата, но хранилище указываем Trusted Root Certification Authorities. Проверяем, что наш CA "My CN" появился в хранилище:

Повторяем наш запрос (https://server.example.com:9443). При первом запросе браузер находит подходящий клиентский сертификат и просит явно подтвердить его использование:

Дальнейшие вызовы проходят без запросов на подтверждение клиентского сертификата:

Если щелкнуть на замочке а адресной строке, то можно посмотреть сертификат сервера и убедиться, что мы взаимодействуем с правильным сервером (Issued to: server.example.com), сертификат которому выдан нашим удостоверяющим центром (Issued by: My CN) и время действия сертификата еще не прошло (Valid to: 31.05.2023):


Таким образом мы организовали mTLS соединение из браузера. Далее приведу для справки несколько дополнительных возможностей OpenSSL, которые могут пригодиться в процессе работы. 

Дополнительные возможности OpenSSL

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

  • Конвертируем сертификат в der (бинарный формат):

openssl x509 -outform der -in client.crt -out client.der

  • Конвертируем сертификат в PEM (текстовой формат)

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

Отладка с помощью OpenSSL

  • Проверка, что сертификат подписан определенным (root_ca.pem) удостоверяющим центром:

openssl verify -verbose -x509_strict -CAfile root_ca.pem server.crt

  • Запускаем OpenSSL server

openssl s_server -accept 9443 -cert server.crt -key server.key

  • Запускаем OpenSSL client

openssl s_client -connect xx.xx.xx.xx:9443 -prexit -showcerts -state -status -tlsextdebug -verify 10 

  • При вызове curl параметр:

    • -k - позволяет подсоединяться к серверу без проверки цепочки сертификатов (certificate path validation). 

    • --ssl-no-revoke  - предотвращает проверку отозванных сертификатов 

Практика CA Smallstep

Установка Smallstep и CLI step 

CA Smallstep - это open source CA, который можно развернуть локально для автоматического получения X.509 сертификатов. На официальном сайте есть ряд инструкций по его установке и настройке.  Для экспериментов я использовал вариант с docker:

  • Краткая выжимка из инструкции по установке на RH Linux. Сначала устанавливаем docker:

sudo yum -y update

sudo yum install -y yum-utils

sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

sudo yum -y install docker-ce

sudo systemctl start docker

  • Проверяем корректность установки docker:

sudo docker run hello-world

  • Запускаем Smallstep:

sudo docker pull smallstep/step-ca

  • Инициализируем наш CA:

docker run -it -v step:/home/step smallstep/step-ca step ca init

  • При инициализации по умолчанию будет ряд ограничений, например, будут одинаковые пароли для корневого и промежуточного приватного ключа. Для разворота в промышленную среду необходимо внимательно изучить рекомендации Production considerations when running a certificate authority server.

При создании CA в консоль будет выведен fingerprint корневого сертификата в виде шестнадцатеричной строки (например: 55de8dc05cd...8a70f725a56). Ее надо сохранить для последующей инициализации утилиты CLI step, которая используется для взаимодействия с CA Smallstep. Проверяем, что CA Smallstep запущен корректно. xx.xx.xx.xx:9000 - это IP и порт, на котором запущен Smallstep CA:

curl https://xx.xx.xx.xx:9000/health 

должна вернуться строка:

"status": "ok"

На этом же или другом компьютере по инструкции  устанавливаем утилиту CLI step. Краткая выжимка:

tar -xf step_linux_0.19.0_386.tar.gz

  • Копируем утилиту step в /usr/bin

  • Регистрируем CLI step для использования с нашим CA. Этот процесс называется "bootstrap".  Значение параметра "ca-url" - это IP и порт, на котором запущен Smallstep CA, "fingerprint " - это сохраненное значение при инициализации CA:

step ca bootstrap --ca-url https://xx.xx.xx.xx:9000 --fingerprint 55de8dc05cd...8a70f725a56 --install

  • Проверка связи с CA:

step ca health

должна вернуться строка:

"ok"

Все готово для генерации ключей и сертификатов.

Генерация ключей и сертификатов 

Также как и в случае с OpenSSL, сначала генерируем ключ и сертификат для сервера и установки TLS соединения, потом для клиента и установки mTLS. 

  • В отличии от OpenSSL, CA Smallstep может одновременно сформировать приватный ключ сервера 2048-бит RSA (server.key) и запрос на сертификат (server.csr). В запросе явно указываем, что пароль должен быть пустой (no-password), xx.xx.xx.xx - это IP адрес сервера, для которого генерируется запрос: 

step certificate create --csr --no-password --insecure --kty=RSA --size=2048 "xx.xx.xx.xx" server.csr server.key

  • Подписываем сертификат на нашем CA Smallstep:

step ca sign server.csr server.crt

  • Смотрим полученный сертификат: 

step certificate inspect server.crt

  • Получаем с CA Smallstep его корневой сертификат: 

step ca root root_ca.crt

  • Смотрим корневой сертификат:

step certificate inspect root_ca.crt

  • Аналогично OpenSSL копируем ключи и сертификаты на наш HTTPS сервер:

    • root_ca.crt

    • server.key

    • server.crt

  • Запускаем сервер:

node server.js

  • Проверяем сервер curl-ом:

curl https://xx.xx.xx.xx:9443

Hello, world!

Все нормально, теперь генерируем ключ и сертификат для клиента.

  • Аналогично серверу одновременно формируем приватный ключ клиента 2048-бит RSA (client.key) и запрос на сертификат (client.csr). Явно указываем, что пароль должен быть пустой (no-password), xx.xx.xx.xx - это IP адрес клиента, для которого генерируется запрос: 

step certificate create --csr --no-password --insecure --kty=RSA --size=2048 "xx.xx.xx.xx" client.csr client.key

  • Подписываем сертификат на CA:

step ca sign client.csr client.crt

  • Смотрим сертификат:

step certificate inspect client.crt

  • Получаем корневой сертификат CA:

step ca root root_ca.crt

  • Смотрим корневой сертификат:

step certificate inspect root_ca.crt

  • Копируем на клиента:

    • root_ca.crt

    • client.key

    • client.crt

  • Для проверки вызываем curl с параметрами:

curl --cert ./client.crt --key client.key --cacert root_ca.crt https://xx.xx.xx.xx:9443

Hello, world!

Отлично - mTLS тоже работает с выданными CA Smallstep сертификатами!

Дополнительные возможности CA Smallstep

С помощью CLI step можно проверить необходимость обновления сертификата: 

step certificate needs-renewal server.crt

  • Если обновление не требуется возвращается строка:

certificate does not need renewal

  • Если требуется, то строка пустая и тогда необходимо обновить сертификат:

step ca renew server.crt server.key

  • При необходимости сертификат можно отозвать, в этом случае его невозможно будет продлить, только получить заново:

step ca revoke --cert server.crt --key server.key

  • Посмотреть все сертификаты в цепочке: 

step certificate inspect server.crt --bundle

  • Запуск CLI step в режиме отладки:

STEPDEBUG=1 step /команда/

Заключение 

В данной статье мы рассмотрели две возможности генерации ключей и сертификатов для клиента и сервера:

  • С помощью OpenSSL.

  • Используя CA Smallstep.

И двумя способами установили TLS и mTLS соединения:

  • С помощью curl.

  • Из браузера, загрузив в него клиентский и корневой сертификаты.

Теперь у нас есть понимание, как организовать свою систему PKI с помощью CA любого производителя, т.к. логика взаимодействия у всех аналогичная. Более того, большинство CA используют под капотом OpenSSL, оборачивая его в свой API.

Комментарии (1)


  1. saipr
    20.06.2022 10:44
    +1

    Далее развернем собственный удостоверяющий центр Smallstep и будем получать сертификаты, подписанные им.

    А можно еще использовать CAFL63 с графическим интерфейсом:
    image