Перейти к основному содержимому

Устранение ошибок хранилища сертификатов в JVM

· 4 мин. чтения

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» , так как сертификат был выдан центром, неизвестным браузеру:

./ad5d8b44e63816ad568b3867650940c6.png

С другой стороны, открытие https://wrong.host.badssl.com/ приводит к ошибке «Cert Common Name Invalid» , другой тип ошибки, указывающий на то, что сертификат был выпущен для другого имени хоста, чем предоставленное:

./746cc52b625a08c5959c64f2c0d212a6.png

То же самое произойдет с приложением, работающим внутри 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). Затем давайте перейдем на вкладку «Безопасность» и, наконец, на «Просмотр сертификата». Появится информация о сертификате:

./09d508558db55a3d19e781e234a29569.png

Перейдем во вкладку «Подробности», нажмем на кнопку «Экспорт» и сохраним. Это наш файл PEM:

./870391e5651c018b601626062df086d8.png

Наконец, мы импортируем его с помощью keytool:

$JAVA_HOME/bin/keytool -import -trustcacerts -file CERTIFICATEFILE -alias ALIAS -keystore $JAVA_HOME/lib/security/cacerts

7. Заключение

В этой статье мы увидели, как добавить самоподписанный сертификат в наше хранилище сертификатов JDK/JRE. Теперь наши Java-приложения могут доверять стороне сервера всякий раз, когда они открывают соединение SSL с сайтами, содержащими эти сертификаты.