Для тестовых стендов, внутренних интеграций и mTLS между своими сервисами постоянно
нужны сертификаты, которые никто внешний не выпустит: со своими SAN, сроками и
расширениями. Правильный ответ — маленький локальный CA на openssl ca. Этот текст —
переработка моей рабочей шпаргалки, которой пользуюсь много лет: полный цикл от конфига
до отзыва, с расшифровкой каждого флага.
Команды актуальны для OpenSSL 3.x; там, где поведение старых версий (1.0.2/1.1.1) отличается, стоит пометка ⚠ legacy. Всё — Linux; никакой Windows-специфики.
Структура каталогов
openssl ca — это простейшая «база данных» удостоверяющего центра на плоских файлах.
Рабочий каталог:
/opt/ca/
├── openssl.cnf # конфиг (ниже)
├── CA/ # сертификат и ключ самого CA
│ ├── CAcert.pem
│ └── CAkey.pem
├── DB/db # реестр выпущенных сертификатов (изначально пустой)
├── DB/db.attr # unique_subject = yes
├── serial/serial # счётчик серийников (изначально: 00)
├── newcerts/ # копии всех выпущенных сертификатов (кладёт сам openssl)
├── certs/ # рабочая папка для текущих операций
└── CRL/ # списки отзыва
| |
Пара слов о неочевидном:
- DB/db — реестр: по строке на каждый выпущенный сертификат (статус, серийник,
subject). Именно по нему строится CRL.
unique_subject = yesзапрещает выпустить два действующих сертификата с одинаковым subject — для перевыпуска сначала отзывайте старый (или ставьтеno, если такой контроль не нужен); - serial — следующий серийный номер. Со значением
00номера идут по порядку. Альтернатива — случайные 128-битные серийники: опция-create_serialпри подписании, файл serial тогда не нужен. ⚠ legacy: в 1.0.2 пустой serial без-create_serialроняет команду с ошибкой.
Конфиг openssl.cnf
| |
Ключевые отличия от типовых конфигов десятилетней давности:
- SAN обязателен. Современные клиенты (браузеры, Go, Java 11+) игнорируют CN и
проверяют имя только по subjectAltName — серверный сертификат без SAN бесполезен.
SAN задаётся в профиле (
server_ext) или переносится из CSR (copy_extensions = copy; включайте осознанно — CA начинает доверять расширениям из запроса); - Профили-секции (
server_ext/client_ext) выбираются при подписании ключом-extensions— один CA выпускает и серверные, и клиентские сертификаты; - Пароль CA в конфиге не храним — никакая секция с passphrase в файле не нужна, openssl спросит его интерактивно (или берите из окружения при автоматизации).
Создание CA
| |
| Флаг | Что делает |
|---|---|
req -x509 | вместо запроса на подпись сразу выпустить самоподписанный сертификат |
-new | создать новый запрос (и новый ключ, раз -key не указан) |
-days 3652 | срок действия — 10 лет (для CA нормально) |
-out / -keyout | файлы сертификата и приватного ключа |
Openssl спросит passphrase для ключа CA (запишите в менеджер паролей — восстановить
нельзя) и поля DN. Ненужное поле пропускается вводом . (точки).
⚠ legacy: в 1.0.2 вывод начинается со строки Loading 'screen' into random state —
это норма тех версий, в 3.x её нет.
Проверить, что получилось:
| |
Ключ и запрос на сертификат (CSR)
Ключ:
| |
| Вариант | Когда |
|---|---|
genrsa ... 2048 | стандартный RSA-2048, максимум совместимости |
genrsa -aes256 ... | зашифрованный ключ: спросит passphrase, и её же будет спрашивать при каждом использовании ключа |
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out svc.key | ECDSA: ключи и подписи короче, поддержка давно повсеместная |
⚠ legacy: genrsa в 3.x пишет ключ в формате PKCS#8
(-----BEGIN PRIVATE KEY-----), а 1.0.2 — в PKCS#1 (-----BEGIN RSA PRIVATE KEY-----).
Функционально для TLS это одно и то же, но парсеры некоторых старых приложений
понимают только PKCS#1: конвертация — openssl rsa -traditional (3.x).
Запрос:
| |
Если SAN приходит из запроса (а не из профиля CA), добавьте его прямо из командной строки — без правки конфига:
| |
⚠ legacy: -addext появился в 1.1.1; в 1.0.2 SAN в CSR добавляется только через
секцию конфига (-reqexts).
Проверка запроса перед отправкой/подписанием:
| |
Подписание
| |
| Флаг | Что делает |
|---|---|
-days 730 | срок; без флага возьмётся default_days из конфига |
-extensions server_ext | профиль расширений (серверный/клиентский) |
-startdate 20260801000000Z | (опционально) сертификат действует с указанного момента UTC — удобно для заранее подготовленной ротации |
-create_serial | (опционально) случайный 128-битный серийник |
CA покажет содержимое будущего сертификата и попросит подтвердить дважды: подписать
и записать в реестр. Копия ляжет в newcerts/<serial>.pem, строка — в DB/db.
Файл на выходе содержит и текстовую расшифровку, и PEM-блок. Если принимающая система требует «чистый» PEM — вырезаем только блок:
| |
Проверка соответствия ключ ↔ CSR ↔ сертификат
Классика жанра: файлов накопилось много, и непонятно, от какого ключа какой сертификат. Сравниваем публичные ключи — у совпадающей тройки хэши идентичны:
| |
Проверка цепочки до CA:
| |
И сроки действия одной строкой:
| |
Отзыв и CRL
Отзыв — это отметка в реестре плюс перевыпуск CRL:
| |
Посмотреть, кто отозван:
| |
CRL имеет собственный срок (default_crl_days) — по его истечении клиенты, проверяющие
отзыв, перестанут принимать список, так что перевыпуск CRL нужно поставить в cron,
даже если никто не отзывался.
PKCS#12: контейнер «ключ + сертификат + цепочка»
Один файл с ключом, сертификатом и CA — стандартный способ передать identity в Java-приложения, nginx-плагины, браузеры:
| |
| Флаг | Что делает |
|---|---|
-export | режим создания контейнера (без него — чтение) |
-in / -inkey | сертификат и его приватный ключ |
-certfile | добавить цепочку (CA / промежуточные) |
-name | alias записи внутри контейнера (Java его увидит) |
⚠ legacy: OpenSSL 3.x шифрует PKCS#12 современными алгоритмами (AES/PBES2), и старые
потребители (Java 8 без обновлений, openssl 1.0.2) могут его не открыть. Для
совместимости со старьём добавьте -legacy. И наоборот: старый p12 в 3.x открывается
командой openssl pkcs12 -in old.p12 -legacy ....
Достать всё обратно:
| |
Конвертации форматов
Сертификат один и тот же — упаковки разные. Быстрый определитель: PEM — текст
с -----BEGIN ...-----; DER — бинарный; PKCS#7 (.p7b) — контейнер только для
сертификатов (без ключей); PKCS#12 (.p12/.pfx) — контейнер с ключами.
| |
Шпаргалка «посмотреть глазами»
| |
Продолжение — про хранилища Java (keytool, PKCS#12 vs JKS, импорт цепочек и типовые операции) — в отдельном посте.