Как я могу использовать разные сертификаты для определенных соединений?
Модуль, который я добавляю к нашему большому 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 для принятия самозаверяющего сертификата? Могу ли я выполнить все цели, которые я имею в виду выше, или мне придется идти на компромисс? Есть ли опция, включающая файлы и каталоги и параметры конфигурации, а также код "маленький-на-нет"?
Ответы
Ответ 1
Создайте SSLSocket
factory самостоятельно и установите его на 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
Ответ 2
Я прочитал много мест в Интернете, чтобы решить эту проблему.
Вот код, который я написал, чтобы он работал:
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-----";
Я проверил, что вы можете помещать любые символы в строку сертификата, если она имеет собственную подпись, при условии соблюдения точной структуры выше. Я получил строку сертификата с помощью командной строки терминала моего ноутбука.
Ответ 3
Если создание SSLSocketFactory
не вариант, просто импортируйте ключ в JVM
-
Получите открытый ключ: $openssl s_client -connect dev-server:443
, затем создайте файл dev-server.pem, который выглядит как
-----BEGIN CERTIFICATE-----
lklkkkllklklklklllkllklkl
lklkkkllklklklklllkllklkl
lklkkkllklk....
-----END CERTIFICATE-----
-
Импортируйте ключ: #keytool -import -alias dev-server -keystore $JAVA_HOME/jre/lib/security/cacerts -file dev-server.pem
. Пароль: изменить
-
Перезапустите JVM
Источник: Как решить javax.net.ssl.SSLHandshakeException?
Ответ 4
Мы копируем траст доверия JRE и добавляем наши настраиваемые сертификаты в этот супермаркет, а затем сообщаем приложению использовать собственный магазин доверия с системным свойством. Таким образом, мы оставляем только один надежный магазин JRE.
Недостатком является то, что когда вы обновляете JRE, вы не получаете его новое доверительное хранилище, автоматически слитое с вашим обычным.
Возможно, вы справитесь с этим сценарием, установив программу установки или запуска, которая проверяет truststore/jdk и проверяет несоответствие или автоматически обновляет доверительный магазин. Я не знаю, что произойдет, если вы обновите доверительный магазин во время работы приложения.
Это решение не является на 100% элегантным или надежным, но оно простое, работает и не требует кода.
Ответ 5
Мне приходилось делать что-то подобное при использовании 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 - это вышеупомянутая реализация менеджера фиктивных доверительных управляющих.
Ответ 6
У меня есть сертификаты .crt и .jks. У меня есть конечные точки сервера, к которым я должен получить доступ. Мне нужно написать код Java для проверки сертификатов и доступа к конечным точкам. как проверить сертификаты и получить доступ к конечным точкам. Помоги мне с этой проблемой