1. Обзор
В этом руководстве мы рассмотрим распространенные проблемы, с которыми мы можем столкнуться при отправке SSL-запросов.
2. Ошибка хранилища сертификатов
Всякий раз, когда приложение Java открывает SSL-соединение с удаленной стороной, оно должно проверить, заслуживает ли сервер доверия или нет, путем проверки своих сертификатов . Если корневой сертификат не содержится в файле хранилища сертификатов, возникает исключение безопасности:
Untrusted: Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Мы должны помнить, что расположение этого файла по умолчанию — $JAVA_HOME/lib/security/cacerts.
3. Самоподписанные сертификаты
В непроизводственных средах часто встречаются сертификаты, подписанные ненадежными эмитентами, которые называются самозаверяющими сертификатами .
Мы можем найти несколько примеров ненадежных сертификатов по адресу https://wrong.host.badssl.com/ или https://self-signed.badssl.com/. Открытие обоих URL-адресов в любом браузере приведет к исключению безопасности. Мы можем проверить их и увидеть различия в сертификатах.
После открытия https://self-signed.badssl.com/ мы видим, что браузер возвращает ошибку «Cert Authority Invalid»
, так как сертификат был выдан центром, неизвестным браузеру:
С другой стороны, открытие https://wrong.host.badssl.com/ приводит к ошибке «Cert Common Name Invalid»
, другой тип ошибки, указывающий на то, что сертификат был выпущен для другого имени хоста, чем предоставленное:
То же самое произойдет с приложением, работающим внутри JDK/JRE. Эти исключения безопасности не позволят нам открыть SSL-соединение с этими ненадежными сторонами.
4. Управление хранилищем сертификатов, чтобы доверять нашим сертификатам
К счастью, JDK/JRE предоставляет инструмент для взаимодействия с хранилищем сертификатов для управления его содержимым . Этот инструмент называется Keytool
и находится в $JAVA_HOME/bin/keytool
.
Важное примечание : для взаимодействия с keytool
требуется пароль. Пароль по умолчанию — «changeit».
4.1. Список сертификатов
Чтобы получить список всех сертификатов, зарегистрированных в хранилище сертификатов JVM, нам нужно выполнить следующую команду:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts
Это вернет список со всеми записями, например:
Your keystore contains 227 entries
Alias name: accvraiz1
Creation date: Apr 14, 2021
Entry type: trustedCertEntry
Owner: C=ES, O=ACCV, OU=PKIACCV, CN=ACCVRAIZ1
Issuer: C=ES, O=ACCV, OU=PKIACCV, CN=ACCVRAIZ1
....
4.2. Добавить сертификаты
Чтобы вручную добавить сертификат в этот список, чтобы он проверялся всякий раз, когда мы выдаем запрос SSL, нам нужно выполнить следующую команду:
keytool -import -trustcacerts -file [certificate-file] -alias [alias] -keystore $JAVA_HOME/lib/security/cacerts
Например:
keytool -import -alias ss-badssl.com -keystore $JAVA_HOME/lib/security/cacerts -file ss-badssl.pem
4.3. Пользовательский путь к хранилищу сертификатов
Если ничего из вышеперечисленного не работает, возможно, наше Java-приложение использует другое хранилище сертификатов. Чтобы убедиться в этом, мы можем указать хранилище сертификатов, которое будет использоваться всякий раз, когда мы запускаем ваше Java-приложение:
java -Djavax.net.ssl.trustStore=CustomTrustStorePath ...
Таким образом, мы удостоверимся, что он использует хранилище сертификатов, которое мы ранее редактировали. Если это не поможет, мы также можем отладить соединения SSL, применив параметр VM:
-Djavax.net.debug=all
5. Скрипт автоматизации
В заключение мы можем создать простой, но удобный скрипт для автоматизации всего процесса:
#!/bin/sh
# cacerts.sh
/usr/bin/openssl s_client -showcerts -connect $1:443 </dev/null 2>/dev/null | /usr/bin/openssl x509 -outform PEM > /tmp/$1.pem
$JAVA_HOME/bin/keytool -import -trustcacerts -file /tmp/$1.pem -alias $1 -keystore $JAVA_HOME/lib/security/cacerts
rm /tmp/$1.pem
В сценарии мы видим, что первая часть открывает SSL-соединение с DNS, переданным в качестве первого аргумента, и запрашивает его для отображения сертификатов. После этого информация о сертификате передается через openssl
для обработки и сохранения в виде файла PEM.
Наконец, этот файл PEM мы будем использовать, указав keytool
импортировать сертификат в файл cacerts с DNS в качестве псевдонима.
Например, мы можем попробовать добавить сертификат для https://self-signed.badssl.com:
cacerts.sh self-signed.badssl.com
И после его запуска мы можем проверить, что наш файл cacerts теперь содержит сертификат:
keytool -list -keystore $JAVA_HOME/lib/security/cacerts
Наконец, мы увидим новый сертификат:
#5: ObjectId: 2.5.29.32 Criticality=false
CertificatePolicies [
[CertificatePolicyId: [2.5.29.32.0]
Alias name: self-signed.badssl.com
Creation date: Oct 22, 2021
Entry type: trustedCertEntry
Owner: CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
Issuer: CN=*.badssl.com, O=BadSSL, L=San Francisco, ST=California, C=US
Serial number: c9c0f0107cc53eb0
Valid from: Mon Oct 11 22:03:54 CEST 2021 until: Wed Oct 11 22:03:54 CEST 2023
Certificate fingerprints:
....
6. Добавление сертификата вручную
Мы также можем использовать наш браузер для извлечения сертификата и добавления его через keytool
, если по какой-либо причине мы не хотим использовать openssl
.
В браузерах на основе Chromium мы открываем веб-сайт, например https://self-signed.badssl.com/, и открываем инструменты разработчика (F12 для Windows и Linux). Затем давайте перейдем на вкладку «Безопасность» и, наконец, на «Просмотр сертификата». Появится информация о сертификате:
Перейдем во вкладку «Подробности», нажмем на кнопку «Экспорт» и сохраним. Это наш файл PEM:
Наконец, мы импортируем его с помощью keytool:
$JAVA_HOME/bin/keytool -import -trustcacerts -file CERTIFICATEFILE -alias ALIAS -keystore $JAVA_HOME/lib/security/cacerts
7. Заключение
В этой статье мы увидели, как добавить самоподписанный сертификат в наше хранилище сертификатов JDK/JRE. Теперь наши Java-приложения могут доверять стороне сервера всякий раз, когда они открывают соединение SSL с сайтами, содержащими эти сертификаты.