Получено фатальное предупреждение: handshake_failure через SSLHandshakeException

У меня проблема с авторизованным SSL-соединением. Я создал Struts Action, который подключается к внешнему серверу с сертификатом авторизированного SSL-клиента. В своем действии я пытаюсь отправить некоторые данные на сервер банка, но без везения, потому что в результате от сервера возникает следующая ошибка:

error: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

Мой метод из моего classа Action, который отправляет данные на сервер

 //Getting external IP from host URL whatismyip = new URL("http://automation.whatismyip.com/n09230945.asp"); BufferedReader inIP = new BufferedReader(new InputStreamReader(whatismyip.openStream())); String IPStr = inIP.readLine(); //IP as a String Merchant merchant; System.out.println("amount: " + amount + ", currency: " + currency + ", clientIp: " + IPStr + ", description: " + description); try { merchant = new Merchant(context.getRealPath("/") + "merchant.properties"); } catch (ConfigurationException e) { Logger.getLogger(HomeAction.class.getName()).log(Level.INFO, "message", e); System.err.println("error: " + e.getMessage()); return ERROR; } String result = merchant.sendTransData(amount, currency, IPStr, description); System.out.println("result: " + result); return SUCCESS; 

Мой файл merchant.properties:

 bank.server.url=https://-servernameandport-/ https.cipher=-cipher- keystore.file=-key-.jks keystore.type=JKS keystore.password=-password- ecomm.server.version=2.0 encoding.source=UTF-8 encoding.native=UTF-8 

Впервые я подумал, что это проблема с сертификатом, я преобразовал ее из .pfx в .jks, но у меня такая же ошибка, без изменений.

Сбой рукопожатия мог произойти по различным причинам:

  • Несовместимые шифровальные пакеты, используемые клиентом и сервером. Это потребует от клиента использования (или включения) набора шифров, который поддерживается сервером.
  • Несовместимые версии SSL используются (сервер может принимать только TLS v1, а клиент может использовать только SSL v3). Опять же, клиенту, возможно, придется убедиться, что он использует совместимую версию протокола SSL / TLS.
  • Неполный путь доверия для сертификата сервера; сертификат сервера, вероятно, не доверяет клиент. Это обычно приводит к более подробной ошибке, но это вполне возможно. Обычно исправление заключается в том, чтобы импортировать сертификат CA сервера в хранилище доверия клиента.
  • Сертификат выпускается для другого домена. Опять же, это привело бы к более подробному сообщению, но здесь я укажу исправление, если это является причиной. В этом случае разрешение будет заключаться в том, чтобы сервер (он, похоже, не ваш) использовал правильный сертификат.

Поскольку основной отказ нельзя определить, лучше включить флаг -Djavax.net.debug=all чтобы включить отладку установленного SSL-соединения. После включения отладки вы можете определить, какая активность в рукопожатии не удалась.

Обновить

Основываясь на данных, доступных сейчас, похоже, что проблема связана с неполным доверительным доверием сертификата между сертификатом, выданным серверу, и корневым центром сертификации. В большинстве случаев это происходит потому, что сертификат корневого ЦС отсутствует в хранилище доверия, что приводит к ситуации, когда путь доверия сертификатов не может существовать; сертификат по существу не доверен клиенту. Браузеры могут представлять предупреждение, чтобы пользователи могли игнорировать это, но то же самое не касается SSL-клиентов (например, class HttpsURLConnection или любая клиентская библиотека HTTP, такая как Apache HttpComponents Client ).

Большинство этих клиентских classов / библиотек полагаются на хранилище доверия, используемое JVM для проверки сертификата. В большинстве случаев это будет файл cacerts в каталоге JRE_HOME / lib / security. Если местоположение хранилища доверия было задано с использованием JVM-системного свойства javax.net.ssl.trustStore , то хранилище в этом пути обычно используется в клиентской библиотеке. Если вы сомневаетесь, взгляните на свой class Merchant и выясните, какой class / библиотека он использует для установления соединения.

Добавление сертификата сервера, выдающего ЦС в это хранилище доверия, должно решить проблему. Вы можете обратиться к моему ответу по соответствующему вопросу о получении инструментов для этой цели, но для этого достаточно утилиты Java keytool .

Предупреждение. Хранилище доверия – это, по сути, список всех доверенных центров, которым вы доверяете. Если вы введете сертификат, который не принадлежит CA, которому вы не доверяете, тогда соединения SSL / TLS с сайтами, имеющими сертификаты, выпущенные этим объектом, могут быть дешифрованы, если доступен закрытый ключ.

Обновление № 2: Понимание вывода трассы JSSE

Хранилище ключей и доверительные хранилища, используемые JVM, обычно перечисляются в самом начале, что несколько напоминает следующее:

 keyStore is : keyStore type is : jks keyStore provider is : init keystore init keymanager of type SunX509 trustStore is: C:\Java\jdk1.6.0_21\jre\lib\security\cacerts trustStore type is : jks trustStore provider is : 

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

Если вы хотите проверить, содержит ли список сертификатов доверия требуемые сертификаты, то есть раздел для того же самого, который начинается с:

 adding as trusted cert: Subject: CN=blah, O=blah, C=blah Issuer: CN=biggerblah, O=biggerblah, C=biggerblah Algorithm: RSA; Serial number: yadda Valid from SomeDate until SomeDate 

Вам нужно будет искать, если CA сервера является предметом.

Процесс рукопожатия будет иметь несколько существенных записей (вам нужно знать SSL, чтобы понять их подробно, но для целей отладки текущей проблемы достаточно знать, что сообщение handhake_failure обычно сообщается в ServerHello.

1. ClientHello

При инициализации соединения будет сообщена серия записей. Первым сообщением, отправленным клиентом в настройке соединения SSL / TLS, является сообщение ClientHello, обычно сообщаемое в журналах как:

 *** ClientHello, TLSv1 RandomCookie: GMT: 1291302508 bytes = { some byte array } Session ID: {} Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA] Compression Methods: { 0 } *** 

Обратите внимание на используемые шифра. Возможно, вам придется согласиться с записью в вашем файле merchant.properties, поскольку та же конвенция может быть использована библиотекой банка. Если используемое соглашение отличается от других, нет никаких причин для беспокойства, поскольку ServerHello будет указывать так, если набор шифров несовместим.

2. ServerHello

Сервер отвечает сервером ServerHello, который укажет, может ли быть установлена ​​настройка соединения. Записи в журналах обычно имеют следующий тип:

 *** ServerHello, TLSv1 RandomCookie: GMT: 1291302499 bytes = { some byte array} Cipher Suite: SSL_RSA_WITH_RC4_128_SHA Compression Method: 0 *** 

Обратите внимание на набор шифров, который он выбрал; это лучший набор, ansible как для сервера, так и для клиента. Обычно набор шифров не указан, если есть ошибка. Сертификат сервера (и, необязательно, целая цепочка) отправляется сервером и будет найден в записях как:

 *** Certificate chain chain [0] = [ [ Version: V3 Subject: CN=server, O=server's org, L=server's location, ST =Server's state, C=Server's country Signature Algorithm: SHA1withRSA, OID = some identifer .... the rest of the certificate *** 

Если проверка сертификата прошла успешно, вы найдете запись, аналогичную:

 Found trusted certificate: [ [ Version: V1 Subject: OU=Server's CA, O="Server's CA's company name", C=CA's country Signature Algorithm: SHA1withRSA, OID = some identifier 

Один из вышеперечисленных шагов не преуспел бы, в результате получилось бы handshake_failure, поскольку рукопожатие, как правило, завершено на этом этапе (на самом деле, но последующие этапы рукопожатия обычно не вызывают сбоя рукопожатия). Вам нужно выяснить, какой шаг завершился неудачно, и разместить соответствующее сообщение в качестве обновления к вопросу (если вы уже не поняли это сообщение и знаете, что ему делать, чтобы его решить).

Я не думаю, что это решает проблему для первого вопросника, но для гуглеров, приходящих сюда для ответов:


В обновлении 51 java 1.8 запретил [1] шифры RC4 по умолчанию, как мы видим на странице «Примечания к выпуску»:

Исправлена ​​ошибка: запретить кодировку RC4

RC4 теперь рассматривается как скомпрометированный шифр.

Сборы RC4-шифров были удалены из списка привилегированных наборов шифрования клиента и сервера по умолчанию в реализации Oracle JSSE. Эти SSLEngine.setEnabledCipherSuites() шифрования могут быть включены с помощью SSLEngine.setEnabledCipherSuites() и SSLSocket.setEnabledCipherSuites() . См. JDK-8077109 (не публичный).

Если ваш сервер имеет сильное предпочтение в отношении этого шифрования (или использует только этот шифр), это может вызвать handshake_failure на java.

Вы можете протестировать подключение к серверу с помощью шифров RC4 (во-первых, попробуйте без enabled аргумента, чтобы увидеть, вызывает ли триггер handshake_failure , а затем enabled :

 import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.*; import java.util.Arrays; /** Establish a SSL connection to a host and port, writes a byte and * prints the response. See * http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services */ public class SSLRC4Poke { public static void main(String[] args) { String[] cyphers; if (args.length < 2) { System.out.println("Usage: "+SSLRC4Poke.class.getName()+"   enable"); System.exit(1); } try { SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1])); cyphers = sslsocketfactory.getSupportedCipherSuites(); if (args.length ==3){ sslsocket.setEnabledCipherSuites(new String[]{ "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_WITH_RC4_128_MD5", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_WITH_RC4_128_MD5", "SSL_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_RSA_WITH_RC4_128_SHA", "TLS_ECDH_anon_WITH_RC4_128_SHA", "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "TLS_KRB5_WITH_RC4_128_MD5", "TLS_KRB5_WITH_RC4_128_SHA" }); } InputStream in = sslsocket.getInputStream(); OutputStream out = sslsocket.getOutputStream(); // Write a test byte to get a reaction :) out.write(1); while (in.available() > 0) { System.out.print(in.read()); } System.out.println("Successfully connected"); } catch (Exception exception) { exception.printStackTrace(); } } } 

1 – https://www.java.com/ru/download/faq/release_changes.xml

Установка расширения Java Cryptography Extension (JCE) Unlimited Strength ( для JDK7 | для JDK8 ) исправит эту ошибку. Разархивируйте файл и следуйте readme, чтобы установить его.

Это также может случиться, когда клиент должен предоставить сертификат. После того, как сервер перечислит цепочку сертификатов, может случиться следующее:

3. Запрос сертификата . Сервер выдаст запрос клиента клиенту. В запросе будут перечислены все сертификаты, которые сервер принимает.

 *** CertificateRequest Cert Types: RSA Cert Authorities:     ... the rest of the request *** ServerHelloDone 

4. Цепочка сертификатов клиента Это сертификат, который клиент отправляет на сервер.

 *** Certificate chain chain [0] = [ [ Version: V3 Subject: EMAILADDRESS=client's email, CN=client, OU=client's ou, O=client's Org, L=client's location, ST=client's state, C=client's Country Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5 ... the rest of the certificate *** ClientKeyExchange, RSA PreMasterSecret, TLSv1 ... key exchange info 

Если в цепочке нет сертификата, а серверу требуется один, вы получите ошибку рукопожатия. Вероятная причина – путь к вашему сертификату не найден.

5. Проверка сертификата . Клиент просит сервер проверить сертификат.

 *** CertificateVerify ... payload of verify check 

Этот шаг произойдет, только если вы отправляете сертификат.

6. Закончено . Сервер ответит на запрос проверки

 *** Finished verify_data: { 345, ... } 

Ошибка рукопожатия может быть ошибкой реализации протокола TLSv1.

В нашем случае это помогло с java 7:

 java -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1 

Jvm будет вести переговоры в этом порядке. Серверы с последним обновлением будут делать 1,2, багги будут идти до v1 и это работает с аналогичным v1 в java 7.

У меня есть эта ошибка, когда я пытался использовать JDK 1.7. Когда я обновил JDK до jdk1.8.0_66, все стало нормально работать.

Таким образом, самым простым решением для этой проблемы может быть обновление вашего JDK, и оно может начать работать хорошо.

Предполагая, что вы используете надлежащие протоколы SSL / TLS, правильно настроили свой keyStore и trustStore и подтвердили, что проблем с самими сертификатами не существует, вам может потребоваться усиление ваших алгоритмов безопасности .

Как упоминалось в ответе Винеета , одна из возможных причин получения этой ошибки связана с использованием несовместимых наборов шифров. US_export_policy банки local_policy и US_export_policy в моей папке security JDK с теми, которые были предоставлены в расширении Java Cryptography Extension (JCE) , я смог успешно завершить рукопожатие.

Сегодня я сталкиваюсь с такой же проблемой с клиентом OkHttp, чтобы ПОЛУЧИТЬ URL-адрес, основанный на https. Это было вызвано ошибкой протокола Https и несоответствием метода Cipher между стороны сервера и клиента .

1) проверьте версию протокола HTTPS и метод шифрования.

openssl>s_client -connect your_website.com:443 -showcerts

Вы получите много подробной информации, информация о ключе перечисляется следующим образом:

 SSL-Session: Protocol : TLSv1 Cipher : RC4-SHA 

2) настройте свой http-клиент, например, в случае клиента OkHttp :

 @Test() public void testHttpsByOkHttp() { ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_0) //protocol version .cipherSuites( CipherSuite.TLS_RSA_WITH_RC4_128_SHA, //cipher method CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) .build(); OkHttpClient client = new OkHttpClient(); client.setConnectionSpecs(Collections.singletonList(spec)); Request request = new Request.Builder().url("https://your_website.com/").build(); try { Response response = client.newCall(request).execute(); if(response.isSuccessful()){ logger.debug("result= {}", response.body().string()); } } catch (IOException e) { e.printStackTrace(); } } 

Это получит то, что мы хотим.

Я обнаружил, что HTTPS-сервер потерпел неудачу, если мой клиентский процесс Java был настроен с

 -Djsse.enableSNIExtension=false 

Не удалось выполнить соединение с помощью handshake_failure после ServerHello завершения ServerHello , но до начала streamа данных.

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

 main, READ: TLSv1.2 Alert, length = 2 main, RECV TLSv1.2 ALERT: fatal, handshake_failure %% Invalidated: [Session-3, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384] main, called closeSocket() main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 

Я изолировал проблему, используя параметр « -Djsse.enableSNIExtension=false » и без -Djsse.enableSNIExtension=false

Mine была несовместимой ошибкой версии TLS .

Раньше это был TLSv1 я изменил его TLSV1.2 это решило мою проблему.

У меня была аналогичная проблема; обновление до Apache HTTPClient 4.5.3 исправлено.

Ugg! Это оказалось просто проблемой Java-версии для меня. Я получил ошибку рукопожатия, используя JRE 1.6, и все отлично работало с использованием JRE 1.8.0_144.

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

Я получал эту ошибку при использовании Parasoft SOATest для отправки запроса XML (SOAP).

Проблема заключалась в том, что я выбрал неправильный псевдоним из выпадающего списка после добавления сертификата и проверки подлинности.

Я использую com.google.api http client. Когда я общаюсь с внутренним сайтом компании, у меня возникает эта проблема, когда я ошибочно использовал https вместо http.

 main, READ: TLSv1.2 Alert, length = 2 main, RECV TLSv1.2 ALERT: fatal, handshake_failure main, called closeSocket() main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure main, IOException in getSession(): javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure main, called close() main, called closeInternal(true) 262 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnection - Connection shut down main, called close() main, called closeInternal(true) 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager - Released connection is not reusable. 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ConnPoolByRoute - Releasing connection [HttpRoute[{s}->https://]][null] 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ConnPoolByRoute - Notifying no-one, there are no waiting threads Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:431) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:339) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:123) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:147) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:108) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554) at com.google.api.client.http.apache.ApacheHttpRequest.execute(ApacheHttpRequest.java:67) at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:960) 

В моем случае сертификат импортируется, ошибка остается, решается это путем добавления System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3"); перед подключением

В моем случае, сайт просто может использовать TLSv1.2. и я использую apache httpclient 4.5.6, я использую этот код и устанавливаю jce для решения этой проблемы (JDK1.7):

ОКО

jdk7 http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html

jdk 8 http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

код:

 SSLContext sslContext = SSLContext.getDefault(); SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory( sslContext, new String[]{"TLSv1.2"}, // important null, NoopHostnameVerifier.INSTANCE); Registry registry = RegistryBuilder.create() .register("https", sslConnectionFactory) .register("http", PlainConnectionSocketFactory.INSTANCE) .build(); HttpClientConnectionManager ccm = new BasicHttpClientConnectionManager(registry); httpclient = HttpClientBuilder.create(). .setSSLSocketFactory(sslConnectionFactory) .setConnectionManager(ccm) .build(); 
Давайте будем гением компьютера.