Вопрос-ответ

Received fatal alert: handshake_failure through SSLHandshakeException

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

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

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

Мой метод из моего класса 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, но у меня та же ошибка, без изменений.

Переведено автоматически
Ответ 1

Сбой подтверждения связи мог произойти по разным причинам:


  • Несовместимые наборы шифров, используемые клиентом и сервером. Для этого клиенту потребуется использовать (или включить) набор шифров, который поддерживается сервером.

  • Используемые несовместимые версии SSL (сервер может принимать только TLS v1, в то время как клиент способен использовать только SSL v3). Опять же, клиенту, возможно, придется убедиться, что он использует совместимую версию протокола SSL / TLS.

  • Неполный путь доверия для сертификата сервера; клиент, вероятно, не доверяет сертификату сервера. Обычно это приводит к более подробной ошибке, но это вполне возможно. Обычно исправление заключается в импорте сертификата CA сервера в хранилище доверия клиента.

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

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

Обновить

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

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

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

Предупреждение: Хранилище доверия - это, по сути, список всех центров сертификации, которым вы доверяете. Если вы добавите сертификат, который не принадлежит центру сертификации, которому вы не доверяете, то соединения 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

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

Процесс подтверждения связи будет содержать несколько важных записей (вам нужно знать протокол SSL, чтобы понять их в деталях, но для устранения текущей проблемы будет достаточно знать, что о handshake_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
***

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

*** 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, поскольку на этом этапе рукопожатие обычно завершается (не совсем, но последующие этапы рукопожатия обычно не приводят к сбою рукопожатия). Вам нужно выяснить, какой шаг завершился неудачно, и опубликовать соответствующее сообщение в качестве обновления вопроса (если только вы уже не поняли сообщение и не знаете, что сделать для его разрешения).

Ответ 2

Сбой подтверждения связи может быть вызван ошибочной реализацией протокола TLSv1.

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

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

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

Ответ 3

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

Ответ 4

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

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

*** CertificateRequest
Cert Types: RSA
Cert Authorities:
<CN=blah, OU=blah, O=blah, L=blah, ST=blah, C=blah>
<CN=yadda, DC=yadda, DC=yadda>
<CN=moreblah, OU=moreblah, O=moreblah, C=moreblah>
<CN=moreyada, OU=moreyada, O=moreyada, C=moreyada>
... 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, ... }
java