Самоподписанные сертификаты: создание и использование
Я, как и наверняка большинство разработчиков, в процессе разработки и локального тестирования различных сервисов использую незащищённые соединения. Но иногда возникает необходимость в использовании защищённых соединений. Использовать сертификаты, выданные сертифицирующими центрами, в этом случае не совсем логично, т.к. они не бесплатны. Вместо этого логичнее использовать самоподписанные сертификаты. В данной статье я рассмотрю создание самоподписанных сертификатов при помощи консольной утилиты 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 # целевой алиас
Смело используйте самоподписанные сертификаты, где не требуются сертификаты, подписанные сертифицирующими центрами. Ну, а завтра я постараюсь всё таки снять ролик на эту тему.
