Как я могу использовать разные сертификаты для определенных соединений?

Модуль, который я добавляю к нашему большому 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 перед подключением.

 ... 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

  1. Получить открытый ключ: $openssl s_client -connect dev-server:443 , затем создать файл dev-server.pem, который выглядит как

     -----BEGIN CERTIFICATE----- lklkkkllklklklklllkllklkl lklkkkllklklklklllkllklkl lklkkkllklk.... -----END CERTIFICATE----- 
  2. Импортируйте ключ: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem . Пароль: changeit

  3. Перезапустить JVM

Источник: Как решить javax.net.ssl.SSLHandshakeException?

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

Недостатком является то, что когда вы обновляете JRE, вы не получаете его новое доверительное хранилище, автоматически слитое с вашим обычным.

Возможно, вы справитесь с этим сценарием, установив программу установки или запуска, которая проверяет truststore / jdk и проверяет несоответствие или автоматически обновляет доверительный магазин. Я не знаю, что произойдет, если вы обновите доверительный магазин во время работы приложения.

Это решение не на 100% элегантное или надежное, но оно простое, работает и не требует никакого кода.

Я должен был сделать что-то подобное при использовании commons-httpclient для доступа к внутреннему серверу https с самозаверяющим сертификатом. Да, наше решение заключалось в создании настраиваемого TrustManager, который просто передавал все (ведение журнала отладочного сообщения).

Это сводится к тому, что у нас есть собственный SSLSocketFactory, который создает SSL-сокеты из нашего локального SSLContext, который настроен, чтобы связать с ним только наш локальный TrustManager. Вам не нужно просто находиться рядом с хранилищем ключей / сертификатом.

Так это в нашей 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 – это вышеупомянутая реализация управляющего менеджера фиктивных решений.

Я просмотрел множество мест в SO и в Интернете, чтобы решить эту проблему. Это код, который работал для меня:

  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()); 

app.certificateString – это строка, содержащая сертификат, например:

  static public String certificateString= "-----BEGIN CERTIFICATE-----\n" + "MIIGQTCCBSmgAwIBAgIHBcg1dAivUzANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE" + "BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE" + ... a bunch of characters... "5126sfeEJMRV4Fl2E5W1gDHoOd6V==\n" + "-----END CERTIFICATE-----"; 

Я протестировал, что вы можете поместить любые символы в строку сертификата, если он подписан сам по себе, если вы сохраняете точную структуру выше. Я получил строку сертификата с помощью командной строки терминала для ноутбука.

  • Как загрузить файл PKCS # 12 в OpenSSL программно?
  • Как проверяются сертификаты ssl?
  • Как легко создать сертификат SSL и настроить его в Apache2 в Mac OS X?
  • Импорт PEM в хранилище ключей Java
  • Доверяйте всем сертификатам, используя HttpClient over HTTPS
  • HTTPS GET (SSL) с Android и самозаверяющим сертификатом сервера
  • ASP.NET MVC RequireHttps только в производстве
  • Принять самоподписанный сертификат ssl сервера в Java-клиенте
  • Использование CERT Tapioca на VM
  • Как установить: OpenSSL + WAMP
  • Использование самозаверяющего сертификата с .NET HttpWebRequest / Response
  • Давайте будем гением компьютера.