Модуль, который я добавляю в наше большое Java-приложение, должен общаться с веб-сайтом, защищенным SSL. Проблема в том, что сайт использует самозаверяющий сертификат. У меня есть копия сертификата, чтобы убедиться, что я не сталкиваюсь с атакой «человек посередине», и мне нужно включить этот сертификат в наш код таким образом, чтобы соединение с сервером было успешным.
Вот основной код:
void sendRequest(String dataPacket) {
String urlStr = "https://host.example.com/";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setMethod("POST");
conn.setRequestProperty("Content-Length", data.length());
conn.setDoOutput(true);
OutputStreamWriter o = new OutputStreamWriter(conn.getOutputStream());
o.write(data);
o.flush();
}
Без какой-либо дополнительной обработки для самоподписанного сертификата это происходит в conn.getOutputStream () со следующим исключением:
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
....
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
....
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
В идеале мой код должен научить Java принимать этот один самозаверяющий сертификат, для этого места в приложении и нигде больше.
Я знаю, что могу импортировать сертификат в хранилище центра сертификации JRE, и это позволит Java принять его. Это не тот подход, который я хочу использовать, если могу помочь; кажется очень инвазивным делать на всех машинах наших клиентов один модуль, который они могут не использовать; это повлияет на все другие приложения Java, использующие ту же JRE, и мне это не нравится, хотя шансы любого другого приложения Java, когда-либо получающего доступ к этому сайту, равны нулю. Это также не тривиальная операция: в UNIX мне нужно получить права доступа для изменения JRE таким образом.
Я также видел, что могу создать экземпляр TrustManager, который выполняет некоторую пользовательскую проверку. Похоже, я мог бы даже создать TrustManager, который делегирует реальный TrustManager во всех случаях, кроме этого одного сертификата. Но похоже, что TrustManager устанавливается глобально, и я предполагаю, что это повлияет на все другие соединения из нашего приложения, и это тоже не совсем подходит для меня.
Каков предпочтительный, стандартный или лучший способ настроить приложение Java для принятия самозаверяющего сертификата? Могу ли я достичь всех целей, которые я имею в виду выше, или мне придется идти на компромисс? Есть ли опция, включающая файлы и каталоги, а также параметры конфигурации и практически неиспользуемый код?
Ответы:
Создайте
SSLSocket
завод самостоятельно и установите егоHttpsURLConnection
перед подключением.Вы будете хотеть создать один
SSLSocketFactory
и держать это вокруг. Вот эскиз того, как его инициализировать:Если вам нужна помощь в создании хранилища ключей, пожалуйста, оставьте комментарий.
Вот пример загрузки хранилища ключей:
Чтобы создать хранилище ключей с сертификатом формата PEM, вы можете написать свой собственный код с помощью
CertificateFactory
или просто импортировать егоkeytool
из JDK (keytool не будет работать для «ввода ключа», но вполне подходит для «доверенной записи»). ).источник
Я прочитал много мест в Интернете, чтобы решить эту проблему. Вот код, который я написал, чтобы заставить его работать:
app.certificateString - это строка, содержащая сертификат, например:
Я проверил, что вы можете помещать любые символы в строку сертификата, если она является самоподписанной, при условии соблюдения точной структуры выше. Я получил строку сертификата с помощью командной строки терминала моего ноутбука.
источник
Если создание
SSLSocketFactory
не является опцией, просто импортируйте ключ в JVMПолучите открытый ключ:,
$openssl s_client -connect dev-server:443
затем создайте файл dev-server.pem, который выглядит какИмпорт ключа:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Пароль: изменитьПерезапустите JVM
Источник: Как решить javax.net.ssl.SSLHandshakeException?
источник
Мы копируем хранилище доверенных сертификатов JRE и добавляем наши пользовательские сертификаты в это хранилище доверенных сертификатов, а затем сообщаем приложению использовать настраиваемое хранилище доверенных сертификатов с системным свойством. Таким образом, мы оставляем хранилище доверенных сертификатов JRE по умолчанию в одиночку.
Недостатком является то, что при обновлении JRE его новое хранилище доверенных сертификатов не будет автоматически объединено с вашим собственным.
Вы могли бы справиться с этим сценарием, установив установщик или подпрограмму запуска, которая проверяет хранилище доверенных сертификатов / jdk и проверяет несоответствие или автоматически обновляет хранилище доверенных сертификатов. Я не знаю, что произойдет, если вы обновите склад доверенных сертификатов во время работы приложения.
Это решение не на 100% элегантно или надежно, но просто, работает и не требует кода.
источник
Я должен был сделать что-то подобное при использовании commons-httpclient для доступа к внутреннему серверу https с самозаверяющим сертификатом. Да, наше решение состояло в том, чтобы создать собственный TrustManager, который просто проходил все (регистрируя отладочное сообщение).
Это сводится к тому, что у нас есть собственный SSLSocketFactory, который создает сокеты SSL из нашего локального SSLContext, который настроен так, что с ним связан только наш локальный TrustManager. Вам совсем не нужно подходить к хранилищу ключей / хранилищу ключей.
Так что это в нашей LocalSSLSocketFactory:
Наряду с другими методами, реализующими SecureProtocolSocketFactory. LocalSSLTrustManager является вышеупомянутой фиктивной реализацией диспетчера доверия.
источник