Перейти к основному содержимому

Собственная PKI: выпуск сертификатов через OpenSSL

Для разработки, тестирования и экспериментов с mTLS в Netgap удобно развернуть собственную (private) PKI: свой корневой CA позволяет за минуты выпустить полный комплект сертификатов для локального стенда, не обращаясь к внешнему удостоверяющему центру. Эта статья — пошаговый практикум именно для такой тестовой инфраструктуры: создаем корневой CA, выпускаем серверный и клиентский сертификаты через OpenSSL и разбираем служебные детали, о которые чаще всего спотыкаются на практике.

примечание

Собственная PKI из этой статьи — инструмент для тестов и экспериментов, а не требование продукта. В производственной среде заказчик вправе использовать сертификаты любого удостоверяющего центра — публичного CA или собственного корпоративного УЦ, если таковой имеется.

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

1. Шаг 1: создание корневого CA

Корневой CA — фундамент доверия: он создается один раз и надолго, поэтому ключ — максимально стойкий (см. рекомендации по алгоритмам).

# Приватный ключ CA — самый главный секрет всей системы
openssl genrsa -out ca.key 4096
chmod 600 ca.key

# Самоподписанный корневой сертификат на 10 лет
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/CN=cluster.local" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign"

Что означает каждый флаг:

ФлагЗачем
-x509Не создавать запрос (CSR), а сразу выпустить готовый самоподписанный сертификат
-nodesНе шифровать ключ паролем (для автоматизации; для офлайн-корня пароль можно и оставить)
-days 3650Срок действия 10 лет — стандарт для корневых CA, они должны жить долго
-subj "/CN=cluster.local"CN корня — имя домена доверия, чтобы администраторам было понятно, к какому кластеру относится корень
-addext "basicConstraints=critical,CA:TRUE"Самое важное: объявляет сертификат полноценным удостоверяющим центром, имеющим право подписывать другие сертификаты
-addext "keyUsage=critical,keyCertSign,cRLSign"Ключ разрешено использовать только для подписи сертификатов (keyCertSign) и списков отзыва (cRLSign) — использовать его для обычного TLS-трафика нельзя, что сужает поверхность атаки
осторожно

Без basicConstraints=critical,CA:TRUE современные библиотеки, включая rustls, откажутся принимать такой сертификат как корень доверия: для них это «обычный паспорт», которым нельзя проверять другие сертификаты. Это самая частая причина неработающего самодельного CA.

Два важных правила для корня:

  • в корне не должно быть SPIFFE ID — корневой сертификат подтверждает лишь право выпускать другие сертификаты, а SPIFFE ID обозначает конкретную рабочую идентичность и живет в SAN конечных сертификатов (подробнее — в статье Идентичность клиента);
  • поля subject и issuer у корня полностью совпадают — это главный признак самоподписанного сертификата. Проверить: openssl x509 -in ca.crt -text -noout и openssl x509 -in ca.crt -purpose -noout.

2. Шаг 2: сертификат сервера

# Приватный ключ сервера
openssl genrsa -out server.key 2048
chmod 600 server.key

Создаем файл расширений server.ext — для современных систем (и rustls в частности) он обязателен:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = localhost
DNS.2 = gw-1.cluster.local
IP.1 = 127.0.0.1
  • authorityKeyIdentifier — связывает сертификат с ключом издателя, помогая построить цепочку;
  • basicConstraints=CA:FALSE — leaf-сертификат не имеет права подписывать другие сертификаты;
  • keyUsage и extendedKeyUsage=serverAuth — сертификат пригоден именно для роли TLS-сервера;
  • subjectAltNameобязателен: современные клиенты (rustls, браузеры, curl) сверяют имя сервера только с SAN и игнорируют CN. Сертификат без SAN не пройдет проверку имени, даже если CN совпадает с доменом.

Создаем CSR и подписываем его нашим CA:

# Запрос на подпись (CSR)
openssl req -new -key server.key -out server.csr -subj "/CN=gw-1.cluster.local"

# Подписание сертификата корневым CA (срок — 1 год)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 -sha256 -extfile server.ext

Расширения из CSR при подписании через openssl x509 -req не переносятся — именно поэтому нужен -extfile server.ext. Проверка результата: openssl verify -CAfile ca.crt server.crt должна вывести server.crt: OK (что смотреть внутри CSR и сертификата — в статье Инспекция и проверка).

подсказка

Современная альтернатива для leaf-ключа — Ed25519:

openssl genpkey -algorithm ed25519 -out mc_1_srv.key
openssl req -new -key mc_1_srv.key -out mc_1_srv.csr -subj "/CN=mc-1-srv"
openssl x509 -req -in mc_1_srv.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out mc_1_srv.crt -days 365 -sha256 -extfile mc_1_srv.ext
openssl verify -CAfile ca.crt mc_1_srv.crt
# mc_1_srv.crt: OK

Все остальные шаги идентичны; в mc_1_srv.ext для сервиса Netgap в SAN вместо DNS-имен может стоять SPIFFE ID вида URI:spiffe://cluster.local/ns/petapp/sa/mc-1-srv — см. Идентичность клиента.

3. Шаг 3: сертификат клиента

Клиентский сертификат выпускается по той же схеме; отличаются назначение ключа и смысл CN:

openssl genrsa -out client.key 2048
chmod 600 client.key

Файл расширений client.ext — вместо serverAuth указываем clientAuth, SAN с DNS-именами не нужен:

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
# В CN — имя сервиса, пользователя или устройства
openssl req -new -key client.key -out client.csr -subj "/CN=client-john-doe"

# -CAcreateserial уже не нужен: счетчик ca.srl создан на шаге 2
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-out client.crt -days 365 -sha256 -extfile client.ext

Именно CN (или SPIFFE ID в SAN) клиентского сертификата сервер Netgap использует, чтобы понять, кто пришел, после того как rustls проверил цепочку — см. Идентичность клиента и mTLS на rustls.

4. Файл ca.srl: счетчик серийных номеров

После первого подписания рядом с ca.crt появляется файл ca.srl — его создал флаг -CAcreateserial. Внутри в шестнадцатеричном виде хранится серийный номер, который получит следующий подписанный сертификат.

Как это работает:

  1. при первом подписании OpenSSL видит -CAcreateserial, генерирует случайное стартовое число, записывает его в сертификат и создает ca.srl;
  2. при подписании следующего сертификата флаг уже не указывается: OpenSSL сам находит ca.srl, читает номер, увеличивает его на единицу, присваивает новому сертификату и перезаписывает файл.

Правила обращения:

  • удалять нельзя — без файла и без флага -CAcreateserial следующее подписание завершится ошибкой: OpenSSL не найдет счетчик;
  • повторный -CAcreateserial не читает старый файл, а полностью перезаписывает его новым случайным стартовым числом. Риск совпадения серийных номеров математически ничтожен (генерируется длинное случайное число), но последовательная нумерация выпущенных сертификатов теряется;
  • хранить — в одном каталоге с ca.key и ca.crt, на офлайн-машине CA: это часть состояния удостоверяющего центра, как и сам ключ.

5. Шаг 4 (опционально): упаковка в PKCS#12

Если клиентский сертификат нужен браузеру, ОС или мобильному приложению, пара «отдельный .key + .crt» неудобна — их объединяют в защищенный паролем контейнер PKCS#12:

openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt
# Команда запросит пароль; client.p12 устанавливается в систему двойным кликом (Windows/macOS)

Для компонентов Netgap на rustls этот шаг не нужен — они читают PEM-файлы напрямую.

6. Какие файлы у кого лежат

ФайлCA (офлайн)СерверКлиентКомментарий
ca.keyдаГлавный секрет; никогда не покидает машину CA
ca.srlдаСостояние счетчика серийных номеров
ca.crtдададаПубличный корень — хранилище доверия каждой стороны
server.keyдаСекрет сервера, права 600
server.crtдаПредъявляется клиенту при handshake
client.keyдаСекрет клиента, права 600
client.crtдаПредъявляется серверу при handshake
*.csr, *.extдаАртефакты выпуска; можно хранить у CA для повторного выпуска
примечание

Ручной выпуск через OpenSSL хорош для понимания и тестовых стендов. В боевых окружениях выпуск и ротацию сертификатов автоматизируют — Vault, cert-manager, service mesh: см. Автоматизация выпуска.

Когда все три уровня выпущены и openssl verify для каждого leaf выводит OK, PKI готова к использованию. Дальше — настройка mTLS на rustls и план ротации и автоматизации выпуска.