Самоподписанные сертификаты: создание и использование

Я, как и наверняка большинство разработчиков, в процессе разработки и локального тестирования различных сервисов использую незащищённые соединения. Но иногда возникает необходимость в использовании защищённых соединений. Использовать сертификаты, выданные сертифицирующими центрами, в этом случае не совсем логично, т.к. они не бесплатны. Вместо этого логичнее использовать самоподписанные сертификаты. В данной статье я рассмотрю создание самоподписанных сертификатов при помощи консольной утилиты OpenSSL.

В качестве подопытного сервиса будет выступать небольшой REST-сервис на Spring Boot, который я хочу запускать в режиме HTTP2, он должен быть доступен по адресу https://localhost:8080 и https://127.0.0.1:8080. Для него я и буду создавать самоподписанный сертификат.

Но я не хочу создавать действительно самоподписанный сертификат, подписываемый ключом, для которого он выпускается. Сначала я хочу создать приватный ключ и сертификат, при помощи которых буду подписывать сертификаты для всех моих сервисов, проще говоря, имитировать CA.

Мимикрия под CA

Первым делом я создам приватный ключ и сертификат, которые будут имитировать CA:

openssl req `# создать новый сертификат/ключ` \
  -subj 'CN=ROOT CA' `# FQDN субъекта` \
  -x509 -sha256 `# создать сертификат, а не запрос на подпись сертификата` \
  -days 3653 `# на 10 лет` \
  -newkey rsa:2048 `# сгенерировать новый ключ размером 2048 бит с алгоритмом RSA` \
  -keyout root_ca.key `# файл для сохранения ключа` \
  -out root_ca.crt # файл для сохранения сертификата

В процессе создания OpenSSL спросит пароль для создаваемого ключа. Кстати, именно так создаётся самоподписанный сертификат.

Сертификат для сервиса

Теперь можно создать ключ для сервиса:

openssl genrsa `# сгенерировать ключ с алгоритмом RSA` \
  -out localhost.key `# файл для сохранения ключа` \
  2048 # размер ключа в битах

Для созданного ключа нужно создать запрос на подпись сертификата:

openssl req `# запрос подписи сертификата` \
  -new `# новый запрос` \
  -subj 'CN=localhost' `# FQDN субъекта` \
  -key localhost.key `# файл ключа` \
  -out localhost.csr `# файл для запроса`

Теперь нужно подписать запрос при помощи CA-сертификата и ключа. Но перед этим нужно создать файл с расширениями сертификата, которые будут добавлены при подписании - localhost.ext. В расширениях нужно указать, что полученный сертификат не является CA-сертификатом и не может использоваться для подписания других сертификатов, а так же альтернативные имена субъекта.

authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName=@alt_names
[alt_names]
DNS.1=localhost
IP.1=127.0.0.1

Теперь можно подписывать:

openssl x509 -req `# Подпись сертификата` /
  -CA root_ca.crt `# Сертификат CA` /
  -CAkey root_ca.key `# Ключ CA` /
  -in localhost.csr `# Запрос на подпись` /
  -out localhost.crt `# Файл для полученного сертификата` /
  -days 365 `# на 1 год` /
  -CAcreateserial`# сгенерировать идентификатор` /
  -extfile localhost.ext # файл с расширениями

Для удобства импорта сертификатов root_ca.crt и localhost.crt их можно объединить в цепочку сертификатов следующим образом:

cat localhost.crt root.crt > localhost_full.crt

На этом этапе у нас есть всё необходимое для использования SSL в сервисах вроде NGINX или Apache httpd. Однако в большинстве Java-приложений SSL настраивается при помощи хранилищ ключей (keystore), который можно создать следующим образом:

openssl pkcs12 \
  -export `# экспорт в формате PKCS#12` \
  -in localhost_full.crt `# экспортируемая цепочка сертификатов` \
  -inkey localhost_1.key `# экспортируемый ключ` \
  -name localhost `# алиас ключа` \
  -out keystore.p12 # целевое хранилище ключей

ВАЖНО! Если на этапе создания ключа для приложения было выбрано шифрование ключа, то полученное хранилище нужно импортировать в другое при помощи команды keytool:

keytool -importkeystore `# импорт хранилища ключей` \
  -srckeystore keystore.p12 `# исходное хранилище` \
  -srcstoretype pkcs12 `# формат исходного хранилища` \
  -srcstorepass storepassword `# пароль исходного хранилища` \
  -srcalias localhost `# исходный алиас` \
  -destkeystore localhost.p12 `# целевое хранилище` \
  -deststoretype pkcs12 `# формат целевого хранилища` \
  -deststorepass storepassword `# пароль целевого хранилища` \
  -destalias localhost # целевой алиас

Полученное хранилище ключей можно использовать в Java-приложении.

Использование сертификата

Теперь в файле application.yml можно настроить HTTP2 и SSL следующим образом:

server:
  http2:
    enabled: true
  ssl:
    key-store: keystore.p12
    key-store-password: storepassword
    key-alias: localhost

Однако, если я сейчас попробую открыть в браузере адрес https://localhost:8080/api/greeting, то увижу предупреждение об использовании сайтом сертификата, подписанного недоверенным авторизационным центром.

Предупреждение об использовании недостоверного сертификата
Предупреждение об использовании недостоверного сертификата

Решается это добавлением CA-сертификата (root_ca.crt) в список центров сертификации в настройках браузера, после чего можно наблюдать корректную работу, в т.ч. и HTTP2:

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

CA-сертификат можно добавить в список доверенных сертификатов для всей системы, чтобы ему доверяли различные утилиты вроде cURL. На Ubuntu Linux это можно сделать следующим образом:

$ sudo cp root_ca.crt /usr/local/share/ca-certificates
$ sudo update-ca-certificates

Если некоторому Java-приложению требуется работать с вашим сервисом через защищённое соединение, то сертификат необходимо добавить в хранилище доверенных сертификатов (truststore), используемое этим приложением:

keytool -importcert `# импорт сертификата` \
  -file root_ca.crt `# сертификат` \
  -keystore truststore.p12 `# целевое хранилище` \
  -storetype pkcs12 `# формат целевого хранилища` \
  -storepass storepassword `# пароль целевого хранилища` \
  -alias root_ca # целевой алиас

Смело используйте самоподписанные сертификаты, где не требуются сертификаты, подписанные сертифицирующими центрами. Ну, а завтра я постараюсь всё таки снять ролик на эту тему.

106 views·5 shares