Как я могу использовать различные сертификаты для определенных соединений?
модуль, который я добавляю в наше большое приложение 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 для принятия самозаверяющего сертификата? Могу ли я достичь всех целей, которые я имею в виду выше, или у меня будет пойти на компромисс? Есть ли опция, включающая файлы и каталоги и параметры конфигурации, а также мало-никакой код?
5 ответов:
создать
SSLSocket
завод себя, и установить его наHttpsURLConnection
перед подключением.... HttpsURLConnection conn = (HttpsURLConnection)url.openConnection(); conn.setSSLSocketFactory(sslFactory); conn.setMethod("POST"); ...
вы хотите создать один
SSLSocketFactory
и держать его вокруг. Вот эскиз того, как его инициализировать:/* Load the keyStore that includes self-signed cert as a "trusted" entry. */ KeyStore keyStore = ... TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(keyStore); SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, tmf.getTrustManagers(), null); sslFactory = ctx.getSocketFactory();
Если вам нужна помощь в создании хранилища ключей, пожалуйста, прокомментируйте.
вот пример загрузки ключей:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(trustStore, trustStorePassword); trustStore.close();
чтобы создать хранилище ключей с сертификатом формата PEM, вы можете написать свой собственный код с помощью
CertificateFactory
, или просто импортировать его сkeytool
из JDK (keytool не работа для "ключевой записи", но просто отлично подходит для"доверенной записи").keytool -import -file selfsigned.pem -alias server -keystore server.jks
при создании
SSLSocketFactory
это не вариант, просто импортируйте ключ в JVM
получить открытый ключ:
$openssl s_client -connect dev-server:443
, затем создать файл dev-сервер.Пем вот такой-----BEGIN CERTIFICATE----- lklkkkllklklklklllkllklkl lklkkkllklklklklllkllklkl lklkkkllklk.... -----END CERTIFICATE-----
импортировать ключ:
#keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Пароль:changeitперезагрузка виртуальной машины JVM
источник: как решить javax. net. ssl. SSLHandshakeException?
Я прочитал много мест в интернете, чтобы решить эту вещь. Это код, который я написал, чтобы заставить его работать:
ByteArrayInputStream derInputStream = new ByteArrayInputStream(app.certificateString.getBytes()); CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(derInputStream); String alias = "alias";//cert.getSubjectX500Principal().getName(); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); trustStore.load(null); trustStore.setCertificateEntry(alias, cert); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(trustStore, null); KeyManager[] keyManagers = kmf.getKeyManagers(); TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); tmf.init(trustStore); TrustManager[] trustManagers = tmf.getTrustManagers(); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(keyManagers, trustManagers, null); URL url = new URL(someURL); conn = (HttpsURLConnection) url.openConnection(); conn.setSSLSocketFactory(sslContext.getSocketFactory());
приложение.certificateString-это строка, содержащая сертификат, например:
static public String certificateString= "-----BEGIN CERTIFICATE-----\n" + "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" + "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" + ... a bunch of characters... "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" + "-----END CERTIFICATE-----";
Я проверил, что вы можете поместить любые символы в строке сертификата, если он является самоподписанным, как долго, как вы держите точная структура выше. Я получил строку сертификата с помощью командной строки терминала моего ноутбука.
мы копируем доверенный магазин JRE и добавляем наши пользовательские сертификаты в этот доверенный магазин, а затем сообщаем приложению использовать пользовательский доверенный магазин с системным свойством. Таким образом, мы оставляем по умолчанию JRE truststore в покое.
недостатком является то, что при обновлении JRE вы не получаете его новый truststore автоматически объединяется с вашим пользовательским.
возможно, вы могли бы справиться с этим сценарием, установив программу установки или запуска, которая проверяет truststore/jdk и проверяет несоответствие или автоматически обновляет хранилище доверия. Я не знаю, что произойдет, если вы обновите truststore во время работы приложения.
Это решение не является 100% элегантным или надежным, но оно простое, работает и не требует кода.
мне пришлось сделать что-то подобное при использовании commons-httpclient для доступа к внутреннему https-серверу с самозаверяющим сертификатом. Да, наше решение состояло в том, чтобы создать пользовательский TrustManager, который просто передавал все (регистрируя сообщение отладки).
Это сводится к тому, что у нас есть собственный SSLSocketFactory, который создает SSL-сокеты из нашего локального SSLContext, который настроен так, чтобы с ним был связан только наш локальный TrustManager. Вам не нужно идти рядом с хранилищем ключей / certstore на все.
Так это в нашем LocalSSLSocketFactory:
static { try { SSL_CONTEXT = SSLContext.getInstance("SSL"); SSL_CONTEXT.init(null, new TrustManager[] { new LocalSSLTrustManager() }, null); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("Unable to initialise SSL context", e); } catch (KeyManagementException e) { throw new RuntimeException("Unable to initialise SSL context", e); } } public Socket createSocket(String host, int port) throws IOException, UnknownHostException { LOG.trace("createSocket(host => {}, port => {})", new Object[] { host, new Integer(port) }); return SSL_CONTEXT.getSocketFactory().createSocket(host, port); }
наряду с другими методами, реализующими SecureProtocolSocketFactory. LocalSSLTrustManager-это вышеупомянутая фиктивная реализация trust manager.