通过自己的 Java 客户端通过 HTTPS/SSL 连接时出现问题

Problems connecting via HTTPS/SSL through own Java client

我正在尝试与 trackobot.com 建立连接以接收一些 JSON 数据。服务器仅允许通过 HTTPS/SSL 进行连接。这是代码:

java.lang.System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");
URL url = new URL("https://trackobot.com/profile/history.json?username=USER&token=TOCKEN");     
InputStream is = url.openStream(); 
JsonParser parser = Json.createParser(is);

openSteam 抛出 javax.net.ssl.SSLHandshakeException:收到致命警报:handshake_failure

我通读了几篇与类似问题相关的帖子,但 none 中的建议有所帮助。适当的证书在我的信任库中。例如,当我尝试连接到 google.com 时,没有错误。所以,问题似乎出在我尝试连接的服务器的握手细节上。

我 运行 我的代码使用 -Djavax.net.debug=ssl 返回这个:

keyStore is : 
keyStore type is : jks
keyStore provider is : 
init keystore
init keymanager of type SunX509
trustStore is: /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/security/cacerts
trustStore type is : jks
trustStore provider is : 
init truststore

[Here I removed hundreds of „adding as trusted cert“:… lines]

trigger seeding of SecureRandom
done seeding SecureRandom
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
main, setSoTimeout(0) called
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 for TLSv1
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 for TLSv1.1
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 for TLSv1.1
%% No cached client session
*** ClientHello, TLSv1.2
RandomCookie:  GMT: 1433943269 bytes = { 109, 198, 189, 148, 62, 6, 19, 126, 179, 214, 250, 99, 207, 117, 162, 47, 62, 176, 222, 247, 98, 29, 155, 63, 255, 100, 171, 187 }
Session ID:  {}
Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS_ECDH_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_MD5, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension signature_algorithms, signature_algorithms: SHA512withECDSA, SHA512withRSA, SHA384withECDSA, SHA384withRSA, SHA256withECDSA, SHA256withRSA, SHA224withECDSA, SHA224withRSA, SHA1withECDSA, SHA1withRSA, SHA1withDSA, MD5withRSA
Extension server_name, server_name: [type=host_name (0), value=trackobot.com]
***
main, WRITE: TLSv1.2 Handshake, length = 229
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
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1991)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1104)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1343)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1371)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1355)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1511)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1439)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:254)
    at java.net.URL.openStream(URL.java:1038)
    at trackbot.readHistory(trackbot.java:37)
    at hsanalytics.main(hsanalytics.java:6)
Ende

此外,我使用 openssl 和 perl 脚本 (analyze-ssl.pl) 来检查服务器。

对于 openssl s_client -connect trackobot.com:443 我得到了:

CONNECTED(00000003)
depth=2 /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
verify error:num=19:self signed certificate in certificate chain
verify return:0
---
Certificate chain
 0 s:/C=CH/CN=www.trackobot.com/emailAddress=df1c792ce8e2fc342c0c63c2fab9c6fe-1805689@contact.gandi.net
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
 1 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
 2 s:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
   i:/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIGaTCCBVGgAwIBAgIHBaEI9iSK1jANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UE
BhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsTIlNlY3VyZSBE
aWdpdGFsIENlcnRpZm
[...]
bnCZTkntRP7wPfw6DpPdJzt8BD0Rpp0B8fVUkqkUujP
FEgspzHXqvAp3gzDuNVlElZ4pxSC/06x9xlPua4KnnKIPMVK0DjyXKdPgUaw6YH9
I3SprrGd/B5AoxdPYDM1qRGC+hto3YDnAb29CRFx13mfiEF9En6YrmlZMwJ/dMjo
RcvkqpjoxTLODmX9gWgdJ27Ublq/4f/Q9nlVfx4v00eYyqyMYY6IMlOUWEBvWoAv
zorzLLY9PmepXJtkCw==
-----END CERTIFICATE-----
subject=/C=CH/CN=www.trackobot.com/emailAddress=df1c792ce8e2fc342c0c63c2fab9c6fe-1805689@contact.gandi.net
issuer=/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
---
No client certificate CA names sent
---
SSL handshake has read 5848 bytes and written 328 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: A868799D47C550929ADF026FDC48CABD2444C96FDDAB86036196029BF7754D1B
    Session-ID-ctx: 
    Master-Key: 6C0E428129970C6B1E358E134B12125373BED6FF50D55004A68A9042AD4E51C6D70BB8480266CC1BD1F11B093E212BFC
    Key-Arg   : None
    Start Time: 1433943895
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

对于 analyze-ssl trackobot.com:443 我得到了:

-- trackobot.com port 443
 ! server sent unused chain certificate '/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority'
 ! server sent unused chain certificate '/C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority'
 * maximum SSL version  : TLSv1_2 (SSLv23)
 * supported SSL versions with handshake used and preferred cipher(s):
   * handshake protocols ciphers
   * SSLv23    TLSv1_2   ECDHE-RSA-AES256-GCM-SHA384
   * TLSv1_2   TLSv1_2   ECDHE-RSA-AES256-GCM-SHA384
   * TLSv1_1   TLSv1_1   ECDHE-RSA-AES256-SHA
   * TLSv1     TLSv1     ECDHE-RSA-AES256-SHA
   * SSLv3     FAILED: SSL connect attempt failed because of handshake problems error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure 
 * cipher order by      : server
 * SNI supported        : ok
 * certificate verified : ok
 * chain on 5.102.146.151
   * [0/0] bits=2048, ocsp_uri=http://ocsp.startssl.com/sub/class1/server/ca, /C=CH/CN=www.trackobot.com/emailAddress=df1c792ce8e2fc342c0c63c2fab9c6fe-1805689@contact.gandi.net SAN=DNS:www.trackobot.com,DNS:trackobot.com
   * [1/1] bits=2048, ocsp_uri=http://ocsp.startssl.com/ca, /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Class 1 Primary Intermediate Server CA
   * [2/-] bits=4096, ocsp_uri=, /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
   * [-/2] bits=4096, ocsp_uri=, /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority
 * OCSP stapling        : no stapled response
 * OCSP status          : good (soft error: no ocsp_uri for /C=IL/O=StartCom Ltd./OU=Secure Digital Certificate Signing/CN=StartCom Certification Authority)

版本:

Mac OSX 10.10.3
OpenSSL 0.9.8zd 8 Jan 2015
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

谁能发现我的 Java 程序哪里出错了?如何满足服务器的握手要求?我真的是这个问题吗?

Cipher Suites: [TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ...

您 Java 客户端没有提供 AES256 密码套件。

Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA

因为它们在您的应用程序中不可用。我不是 Java 专家,但要么这些在您的 Java 中不可用,要么需要明确启用。需要它们是因为服务器仅支持 AES256 密码:

 $ perl analyze-ssl.pl -v3 --all-ciphers trackobot.com
 ...
* supported ciphers with SSLv23 handshake
 * TLSv1_2 ECDHE-RSA-AES256-GCM-SHA384
 * TLSv1_2 ECDHE-RSA-AES256-SHA384
 * TLSv1_2 ECDHE-RSA-AES256-SHA
 * TLSv1_2 DHE-RSA-AES256-GCM-SHA384
 * TLSv1_2 DHE-RSA-AES256-SHA256
 * TLSv1_2 DHE-RSA-AES256-SHA

您使用的 Java 版本可能因为出口法规不支持 AES256,请参阅 https://knowledge.safe.com/articles/Error_Unexpected_Behavior/Enabling-AES256-in-the-Java-Runtime-Environment-for-Single-Sign-On

根据https://www.ssllabs.com,服务器支持密码套件

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA 
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
TLS_DHE_RSA_WITH_AES_256_CBC_SHA 

如您在调试消息中所见,它们被列为 "unavailable cipher suite"。

在JRE/lib/security/local_policy.jar中,我们看到

// Some countries have import limits on crypto strength. This policy file
// is worldwide importable.

grant {
    permission javax.crypto.CryptoPermission "DES", 64;
    permission javax.crypto.CryptoPermission "DESede", *;
    permission javax.crypto.CryptoPermission "RC2", 128, 
                                     "javax.crypto.spec.RC2ParameterSpec", 128;
    permission javax.crypto.CryptoPermission "RC4", 128;
    permission javax.crypto.CryptoPermission "RC5", 128, 
          "javax.crypto.spec.RC5ParameterSpec", *, 12, *;
    permission javax.crypto.CryptoPermission "RSA", *;
    permission javax.crypto.CryptoPermission *, 128;
};

下载并安装“(JCE) Unlimited Strength Jurisdiction Policy Files” - http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html - 我可以确认问题已解决。自述文件说

Due to import control restrictions of some countries, the version of the JCE policy files that are bundled in the Java Runtime Environment, or JRE(TM), 8 environment allow "strong" but limited cryptography to be used. This download bundle (the one including this README file) provides "unlimited strength" policy files which contain no restrictions on cryptographic strengths.

感谢 Steffen Ullrich 的提示,我检查了可用的密码 java。显然,在 Java 8 中,您的密码没有无限强度。例如,在我的例子中,我的程序无法使用服务器所需的 AES 256 位密码。

为了解决这个问题,oracle 提供了一组允许无限强度加密的策略文件。你可以找到它 here.

自述文件指出:

Due to import control restrictions of some countries, the version of the JCE policy files that are bundled in the Java Runtime Environment, or JRE(TM), 8 environment allow "strong" but limited cryptography to be used.

只需按照安装说明下载捆绑包并替换适当的文件即可。在我这样做之后,握手就像一个魅力。

您可能需要升级您的 JDK,我们的 linux 服务器上也有类似的问题。我们尝试了不同的方法。似乎没有任何工作,包括安装新的 JCE。

JDK 中存在一个关于 SSL 连接主机名验证器的错误,该错误禁用了导致握手失败的 SNI 扩展:http://www.oracle.com/technetwork/java/javase/2col/8u141-bugfixes-3720387.html

我们升级到最新的jdk8u162,现在一切正常。