Подтвердить что ты не робот

Apache HTTPClient SSLPeerUnverifiedException

Использование Apache HttpClient 4.2.1. Использование кода, скопированного из примера входа на основе формы

http://hc.apache.org/httpcomponents-client-ga/examples.html

Я получаю исключение при доступе к защищенной паролем форме:

Getting library items from https://appserver.gtportalbase.com/agileBase/AppController.servlet?return=blank
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated
Closing http connection
at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:397)
at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:572)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:180)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:294)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:640)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:479)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:906)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:805)
at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:784)

Сертификат, насколько я могу судить, в порядке (см. URL-адрес перед трассировкой стека), а не истек - браузеры не жалуются.

Я попытался импортировать сертификат в хранилище ключей a la

Как обрабатывать недопустимые SSL-сертификаты с помощью Apache HttpClient?

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

Любые идеи?

4b9b3361

Ответ 1

РЕДАКТИРОВАТЬ. Я понимаю, что этот ответ был принят давно, а также был увеличен 3 раза, но он был (по крайней мере частично) неправильным, поэтому здесь немного больше об этом исключении. Извинения за неудобства.

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated

Обычно это исключение, когда удаленный сервер вообще не отправил сертификат. Тем не менее, есть крайний случай, который встречается при использовании Apache HTTP Client из-за способа его реализации в этой версии и из-за способа sun.security. ssl.SSLSocketImpl.getSession().

При использовании HTTP-клиента Apache это исключение также будет выбрано, если удаленный сертификат не будет доверен, что чаще всего выдает "sun.security.validator.ValidatorException: PKIX path building failed".

Причины этого происходят из-за того, что HTTP-клиент Apache пытается получить SSLSession и сертификат однорангового узла, прежде чем делать что-либо еще.

Напоминаем, что 3 способа инициирования рукопожатия с помощью SSLSocket:

  • вызов startHandshake, который явно начинает рукопожатие, или
  • любая попытка чтения или записи данных приложения в этом сокете вызывает неявное рукопожатие или
  • вызов getSession пытается настроить сеанс, если в настоящий момент нет действующего сеанса, и выполняется неявное рукопожатие.

Вот 3 примера, все против хоста с сертификатом, которому не доверяют (используя javax.net.ssl.SSLSocketFactory, а не Apache).

Пример 1:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    sslSocket.startHandshake();

Это вызывает "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" (как и ожидалось).

Пример 2:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    sslSocket.getInputStream().read();

Это также выбрасывает "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" (как и ожидалось).

Пример 3:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    SSLSession sslSession = sslSocket.getSession();
    sslSession.getPeerCertificates();

Это, однако, вызывает javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated.

Это логика, реализованная в Apache HTTP Client AbstractVerifier, используемая ее org.apache.http.conn.ssl.SSLSocketFactory в версии 4.2.1. Более поздние версии сделать явный вызов startHandshake() на основе отчетов в запустите HTTPCLIENT-1346.

В конечном итоге это происходит из-за реализации sun.security. ssl.SSLSocketImpl.getSession(), который захватывает потенциальный IOException при вызове startHandshake(false) (внутренний метод), не бросая его дальше. Это может быть ошибкой, хотя это не должно иметь большого влияния безопасности, поскольку SSLSocket все равно будет закрыт.

Пример 4:

    SSLSocketFactory ssf = (SSLSocketFactory) sslContext.getSocketFactory();
    SSLSocket sslSocket = (SSLSocket) ssf.createSocket("untrusted.host.example",
            443);
    SSLSession sslSession = sslSocket.getSession();
    // sslSession.getPeerCertificates();
    sslSocket.getInputStream().read();

К счастью, это все равно будет "javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed" , когда вы на самом деле пытаетесь использовать этот SSLSocket (там нет лазейки, получив сеанс без получения сертификата однорангового узла).


Как исправить это

Как и любая другая проблема с сертификатами, которым не доверяют, необходимо убедиться, что хранилище доверия, которое вы используете, содержит необходимые привязки доверия (то есть сертификаты ЦС, которые выдали цепочку, которую вы пытаетесь проверить, или, возможно, фактический сертификат сервера для исключительных случаев).

Чтобы исправить это, вы должны импортировать сертификат CA (или, возможно, сам сертификат сервера) в свой магазин доверия. Вы можете сделать это:

  • в вашем хранилище доверия JRE, обычно это файл cacerts (что не обязательно самое лучшее, потому что это повлияет на все приложения, использующие эту JRE),
  • в локальной копии хранилища доверия (которую вы можете настроить с помощью опций -Djavax.net.ssl.trustStore=...),
  • создав для этого соединения конкретный SSLContext (как описано в этом ответе). (Некоторые предлагают использовать диспетчер доверия, который ничего не делает, но это сделает ваше соединение уязвимым для атак MITM.)

Первоначальный ответ

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated

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

Этот сервер явно не настроен для правильной поддержки TLS. Это не удается (вы не получите удаленный сертификат):

openssl s_client -tls1 -showcerts -connect appserver.gtportalbase.com:443

Однако, похоже, работает SSLv3:

openssl s_client -ssl3 -showcerts -connect appserver.gtportalbase.com:443

Если вы знаете, кто работает с этим сервером, стоит исправить эту проблему. Серверы должны действительно поддерживать TLSv1 по крайней мере в наши дни.

Между тем, одним из способов устранения этой проблемы было бы создать собственный org.apache.http.conn.ssl.SSLSocketFactory и использовать его для этого соединения с клиентом Apache Http.

Для этого factory необходимо создать SSLSocket, как обычно, использовать sslSocket.setEnabledProtocols(new String[] {"SSLv3"}); перед возвратом этого сокета, чтобы отключить TLS, который в противном случае был бы включен по умолчанию.

Ответ 2

Создайте настраиваемый контекст, чтобы вы могли регистрировать, почему сертификат недействителен. Или просто отлаживайте его.