X.509 - это формат сертификата, который связывает открытый ключ с владельцем и заверяется подписью удостоверяющего центра (CA). На нём держится почти вся практическая криптография в инфраструктуре: TLS на веб-серверах, аутентификация клиентов по сертификату, подпись пакетов и образов, S/MIME. В этом уроке вы своими руками соберёте мини-CA на OpenSSL, выпустите серверный и клиентский сертификаты, подключите цепочку в Apache, настроите выбор протоколов и шифров, а заодно разберётесь, чем PEM отличается от DER и зачем вообще нужны эти преобразования. Задача администратора тут не зазубрить ключи openssl, а понимать, какой документ он выпускает и кому потом доверяет браузер.

Как это работает
Сертификат X.509 - это структура с полями: subject (кому выдан), issuer (кто выдал), срок действия, открытый ключ, расширения (extensions) и подпись. Подпись считается удостоверяющим центром по хешу всего тела сертификата его закрытым ключом. Проверяющая сторона берёт открытый ключ CA, пересчитывает хеш и сверяет - так строится доверие.
Доверие в X.509 иерархическое. Корневой CA подписывает сам себя (self-signed) и лежит в хранилище доверенных корней. Он может подписать промежуточные CA, а те - конечные сертификаты серверов и людей. Получается цепочка: лист -> промежуточный -> корень. Клиент доверяет листу, только если может выстроить цепочку до корня, которому уже доверяет.
Ключевую роль играют extensions. basicConstraints=CA:TRUE говорит, что сертификатом можно подписывать другие. keyUsage и extendedKeyUsage задают назначение: serverAuth, clientAuth, digitalSignature. subjectAltName (SAN) перечисляет имена хостов - и именно по SAN, а не по CN, современные браузеры проверяют совпадение домена. CN для проверки имени не используется уже много лет.
PEM и DER - это два кодирования одной и той же ASN.1-структуры. DER - компактный бинарник. PEM - тот же DER в Base64 с рамкой BEGIN CERTIFICATE / END CERTIFICATE, текстовый и удобный для копирования. Apache и nginx обычно хотят PEM, Java-миры и Windows нередко - DER, PKCS#12 (.p12/.pfx) или PKCS#7. Преобразования между ними - рутина администратора.
Команды и примеры
Установка OpenSSL (актуально, OpenSSL 3.x):
Код: Выделить всё
# Debian 13 / Ubuntu 24.04
apt install openssl
# RHEL 10 / Fedora 41+
dnf install openssl
Код: Выделить всё
openssl genrsa -aes256 -out ca.key 4096
openssl req -x509 -new -key ca.key -sha256 -days 3650 \
-subj "/C=RU/O=Cyberlake Lab/CN=Cyberlake Root CA" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-out ca.crt
Серверный ключ и запрос на подпись (CSR):
Код: Выделить всё
openssl genrsa -out server.key 2048
openssl req -new -key server.key \
-subj "/C=RU/O=Cyberlake Lab/CN=www.example.lab" \
-out server.csr
Код: Выделить всё
cat > srv.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage=critical,digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=DNS:www.example.lab,DNS:example.lab
EOF
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -days 825 -sha256 -extfile srv.ext -out server.crt
Клиентский сертификат для аутентификации (extendedKeyUsage=clientAuth):
Код: Выделить всё
openssl genrsa -out client.key 2048
openssl req -new -key client.key -subj "/CN=ivan@example.lab" -out client.csr
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 365 -sha256 \
-extfile <(printf "extendedKeyUsage=clientAuth\nkeyUsage=digitalSignature") \
-out client.crt
# упакуем в PKCS#12 для импорта в браузер
openssl pkcs12 -export -inkey client.key -in client.crt -certfile ca.crt -out client.p12
Код: Выделить всё
openssl x509 -in server.crt -noout -text # читаемый дамп
openssl x509 -in server.crt -noout -subject -ext subjectAltName
openssl verify -CAfile ca.crt server.crt # проверка цепочки
openssl s_client -connect www.example.lab:443 -servername www.example.lab
Код: Выделить всё
openssl x509 -in server.crt -outform der -out server.der # PEM -> DER
openssl x509 -inform der -in server.der -out server.pem # DER -> PEM
openssl pkcs12 -in client.p12 -nodes -out client_all.pem # PKCS#12 -> PEM
Код: Выделить всё
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/server.crt
SSLCertificateKeyFile /etc/pki/tls/private/server.key
SSLCertificateChainFile /etc/pki/tls/certs/ca.crt
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
SSLHonorCipherOrder on
# Клиентская аутентификация по сертификату:
SSLCACertificateFile /etc/pki/tls/certs/ca.crt
SSLVerifyClient require
SSLVerifyDepth 2
Частые грабли
- Нет subjectAltName - сертификат с одним CN современные браузеры считают невалидным. SAN обязателен всегда.
- Забыли SSLCertificateChainFile - openssl verify локально проходит (корень у вас в доверенных), а внешний клиент видит unable to get local issuer certificate. Цепочку надо отдавать целиком, кроме самого корня.
- Порядок в bundle для nginx: сначала лист, потом промежуточные. У Apache промежуточные идут отдельным файлом. Перепутали - часть клиентов не достроит цепочку.
- Срок больше 825 дней или RSA меньше 2048 бит - часть валидаторов и сканеров такое режут.
- Ключ выпустили под одним именем, а в конфиге указали другой ключ - Apache стартует, но рукопожатие падает на несовпадении ключа и сертификата. Проверяйте модуль через openssl x509 -modulus и openssl rsa -modulus.
- PEM и DER путают по расширению. Файл .crt может быть и тем, и другим. Ориентируйтесь не на имя, а на inform/outform и на наличие рамки BEGIN.
- SSLProtocol all без минусов оставляет включёнными старые протоколы. В 2026 рабочий минимум - TLS 1.2, целевой - TLS 1.3.
- Поднимите свой корневой CA: сгенерируйте ca.key и самоподписанный ca.crt с CA:TRUE.
- Выпустите серверный сертификат для www.example.lab с правильным subjectAltName и подпишите его своим CA.
- Установите Apache, включите mod_ssl, подключите server.crt, server.key и цепочку, ужмите SSLProtocol до TLS 1.2/1.3.
- Добавьте ca.crt в доверенные на клиенте и проверьте сайт через openssl s_client и браузер - предупреждений быть не должно.
- Выпустите клиентский сертификат с clientAuth, упакуйте в PKCS#12, импортируйте в браузер.
- Включите SSLVerifyClient require и убедитесь, что без клиентского сертификата доступа нет, а с ним - есть.
- Преобразуйте серверный сертификат в DER и обратно, сверьте отпечатки через openssl x509 -fingerprint.
- По какому полю сертификата современный браузер проверяет совпадение доменного имени и почему не по CN?
- Чем отличаются PEM, DER, PKCS#12 и PKCS#7 и какой командой перейти от PKCS#12 к набору PEM?
- Какие расширения X.509 обязательны для серверного, клиентского и CA-сертификата соответственно?
- Что делает SSLCertificateChainFile и какую ошибку у клиента вызывает её отсутствие?
- Как включить взаимную аутентификацию TLS в Apache и какие директивы за это отвечают?
- Чем самоподписанный сертификат отличается от выпущенного промежуточным CA с точки зрения построения цепочки доверия?