Java HTTPS запрос: имя хоста в сертификате не совпадает - PullRequest
0 голосов
/ 26 июня 2019

Я пытаюсь сделать HTTPS-запрос от клиента Java 8 (используя Apache HttpClient 4.3) к общедоступному серверу (которым я не владею). Обычно у меня никогда не было проблем с запросами, но целевой сервер, похоже, внес некоторые изменения, и запросы начали давать сбой.

Первоначально ошибка была ValidatorException: PKIX Path Building Failed, но я исправил это, добавив сертификат сервера в мое хранилище доверенных сертификатов Java. Теперь я получаю ошибку hostname in certificate didn't match: <target.server.com> != <*.something.else.com> OR <something.else.com>.

Хост something.else.com - это своего рода хостинг и т. Д. В любом случае, я уверен, что в этом нет ничего подозрительного. Я вытащил сертификат для target.server.com, выполнив следующую команду:

openssl s_client -connect target.server.com:443 -showcerts

Я не вижу никакой ссылки на target.server.com в информации о сертификате, только для something.else.com. У меня нет проблем с посещением target.server.com в веб-браузере (Chrome), и нет никаких претензий к сертификату SSL. Я добавил сертификат сервера в хранилище доверенных сертификатов с помощью этой команды:

sudo keytool -import -file /tmp/target.cer -keystore /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/security/cacerts -alias targetserv

Есть ли способ исправить это без изменения кода Java, например добавить что-то в команду импорта сертификата и т. д., а если нет, как еще можно заставить эти запросы работать снова.


Вот мой код HTTP-запроса (с использованием HttpClient 4.3):

CloseableHttpClient httpClient = HttpClients.custom().setUserAgent(HTTP_USER_AGENT).build();
HttpGet request = new HttpGet(url);
HttpResponse response = httpClient.execute(request);

... бросает java.io.IOException: hostname in certificate didn't match

Кроме того, это фактический целевой сервер, к которому я пытаюсь связаться: https://www.isi.gov.ie


Я пытался использовать HttpClient v4.5, и все равно получаю ту же ошибку, что и в 4.3. Я попытался сделать запрос, используя встроенные в Java классы HTTP-запросов, и запрос успешно выполнен (возвращается 302):

URL obj = new URL("https://www.isi.gov.ie");
HttpURLConnection con = (HttpURLConnection) obj.openConnection();

con.setRequestMethod("GET");
con.setRequestProperty("User-Agent", USER_AGENT);

int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);

BufferedReader in = new BufferedReader(
        new InputStreamReader(con.getInputStream()));

String inputLine;
StringBuffer response = new StringBuffer();

while ((inputLine = in.readLine()) != null) {
    response.append(inputLine);
}
in.close();

//print result
System.out.println(response.toString());

Можно ли как-нибудь исправить эти проблемы с помощью HttpClient?


Оказывается, в другом месте моего приложения я отключал SNI с помощью системного свойства Java jsse.enableSNIExtension и забыл об этом. Причина, по которой я это делал, заключается в том, что это было единственное исправление, которое я смог найти, чтобы предотвратить ВСЕ HTTP-запросы, использующие HttpClient, с ошибкой:

sun.security.validator.ValidatorException: сбой построения пути PKIX: sun.security.provider.certpath.SunCertPathBuilderException: невозможно найти действительный путь сертификации для запрошенной цели

Как я могу снова включить SNI без этих ошибок?
Я использую следующую версию Java:

openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03)
OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)

1 Ответ

0 голосов
/ 26 июня 2019

TLDR: это может быть SNI (и цепочка)

Этот сервер, очевидно, как и многие другие сегодня, поддерживает несколько «виртуальных» хостов с разными сертификатами с использованием расширения TLS 'Индикация имени сервера ' aka SNI.При обращении с использованием SNI для www.isi.gov.ie он предоставляет сертификат, действительный для этого домена и isi.gov.ie, но при обращении с использованием без SNI он предоставляет сертификат expired для доменов *.rdccremote.ie и rdccremote.ie;Я так понимаю, это то, что вы называете «хостинг-сайтом», хотя я не вижу никаких признаков этого;мой лучший гугл хит для этого - этот парламентский вопрос , который, кажется, авторитетно говорит о том, что он находится на «общей службе» правительственного департамента - хотя он не решает тот же адрес (146.0.55.149) с использованием легкодоступного мне DNS.

Далее, оба эти сертификата выдаются DigiCert, но в обоих случаях сервер не отправляет цепной / промежуточный сертификат, необходимый для проверки, в качестве спецификаций TLS.требуют.Браузеры обычно могут «заполнить» отсутствующие сертификаты цепочки, но Java JSSE (и большинство других программных инструментов) не может;это, вероятно, вызвало вашу ошибку построения пути, но не влияет на несоответствие имени хоста.

openssl s_client в версиях ниже 1.1.0 не отправляет SNI по умолчанию, и ваша отредактированная команда не указывает, что вы указали опцию для него.(И -showcerts на сервере бесполезен, поскольку не отправляет сертификат (ы) цепочки).Также имейте в виду, что s_client отображает в декодированном виде только поле Subject сертификата (и Issuer, как s: и i: соответственно), но не SubjectAlt [ernative] Name или расширение SAN, которое используется для имени хоста (s) сертификата сервера SSL / TLS примерно с начала этого столетия.Чтобы увидеть SAN, передайте блок PEM с s_client по openssl x509 -text -noout или другой инструмент декодирования, например keytool -printcert -file $pemfile, или средство просмотра сертификатов в Windows.

Java 8 up обычно отправляет SNI, хотя это может бытьпод влиянием вызывающего кода (приложения или промежуточного программного обеспечения).AHC 4.3 довольно старый, и я не уверен в этом, но 4.5 работает для меня на Java 7 и 8 (Oracle, но AFAIK JSSE идентичен для Oracle и OpenJDK).Не могли бы вы (или кто-либо другой) настроить JVM для предотвращения SNI с sysprop jsse.enableSNIExtension=false?Можете ли вы работать с sysprop javax.net.debug=ssl и захватывать или регистрировать (большие) выходные данные, или, альтернативно, захватывать и проверять данные проводов с помощью wireshark, tcpdump или аналогичного, чтобы проверить, что ваша Java на самом деле отправляет в ClientHello?

...