英文:
Java HttpsUrlConnection, connection reset
问题
String url1 = "foo1.blabla.com";
String url2 = "foo2_bar.blabla.com";
URLConnection urlConnection = new URL(url1).openConnection();
urlConnection.setDoInput(true);
//Fails
InputStream in = urlConnection.getInputStream();
We were able to connect url1 without problems, but they recently changed their url to url2 and claim that they only changed their url and nothing else. But after the modification I got the following exceptions:
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
at sun.security.ssl.InputRecord.read(InputRecord.java:503)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
-
I tried Java 1.8.181, 1.8.191.
-
I tried using customhostnameverifier such as
class CustomHostNameVerifier implements HostnameVerifier {
public CustomHostNameVerifier() {
}
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}
-
Both url1 and url2 have the same certificate information; I also added their .cer into the keystore using keytool.
-
There is no firewall or antivirus issue because on the same computer I can connect to url2 with postman. I added the same headers as postman to my Java code.
-
I checked the following Stack Overflow pages:
- I ran the program with
-Djavax.net.debug=ssl:handshake:verbose:keymanager:trustmanager -Djava.security.debug=access:stack
and observed the following differences:
for url1 (working fine)
> SendHTTPTest.f9() connection opened Allow unsafe renegotiation: false
> Allow legacy hello messages: true Is the initial handshake: true Is secure
> renegotiation: false main, the previous server name in SNI
> (type=host_name (0), value=foo1.blabla.com) was replaced with (type=host_name
> (0), value=foo1.blabla.com) Extension extended_master_secret Extension
> server_name, server_name: [type=host_name (0), value=foo1.blabla.com]
> *** main, WRITE: TLSv1.2 Handshake, length = 226 main, READ: TLSv1.2 Handshake, length = 2303
> *** ServerHello, TLSv1.2
for url2 (connection reset problem)
> SendHTTPTest.f9() connection opened
> main, "foo2_bar.blabla.com" is not a legal HostName for the server name indication Allow unsafe renegotiation: false Allow legacy hello messages: true Is
> the initial handshake: true Is secure renegotiation: false
> main, "foo2_bar.blabla.com" is not a legal HostName for the server name indication
> main, WRITE: TLSv1.2 Handshake, length = 193 main, handling an exception:
> java.net.SocketException: Connection reset main, SEND TLSv1.2 ALERT:
> fatal, description = unexpected_message main, WRITE: TLSv1.2 Alert,
> length = 2 main, Exception sending alert: java.net.SocketException:
> Connection reset by peer: socket write error main, called
> closeSocket() java.net.SocketException: Connection reset
Probably the message causes main, "foo2_bar.blabla.com" is not a legal HostName for the server name indication the problem; is it SNI related? Does the underscore in url2 cause a problem?
Link to Stack Overflow post about SNI
<details>
<summary>英文:</summary>
String url1 = "foo1.blabla.com";
String url2 = "foo2_bar.blabla.com";
URLConnection urlConnection = new URL(url1).openConnection();
urlConnection.setDoInput(true);
//Fails
InputStream in = urlConnection.getInputStream();
We were able to connect url1 without problems, but they recently changed their url to url2 and claim that they only changed their url and nothing else. But after the modification I got the following exceptions:
java.net.SocketException: Connection reset
at java.net.SocketInputStream.read(SocketInputStream.java:210)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.security.ssl.InputRecord.readFully(InputRecord.java:465)
at sun.security.ssl.InputRecord.read(InputRecord.java:503)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:975)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
* I tried Java 1.8.181, 1.8.191.
* I tried using customhostnameverifier such as
class CustomHostNameVerifier implements HostnameVerifier {
public CustomHostNameVerifier() {
}
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
}
* Both url1 and url2 have same certificate information, I also added their .cer into keystore using keytool.
* There is not firewall or antivirus issue becasue on same computer I can connect to url2 with postman. I added same headers like postman to my java code.
* I checked following so pages:
https://stackoverflow.com/questions/27242426/java-net-socketexception-connection-reset-with-httpconnection
https://stackoverflow.com/questions/585599/whats-causing-my-java-net-socketexception-connection-reset
https://stackoverflow.com/questions/62929/java-net-socketexception-connection-reset/31741436#31741436
https://stackoverflow.com/questions/27275203/connection-reset-on-httpsurlconnection-with-valid-url
* I run the program with
-Djavax.net.debug=ssl:handshake:verbose:keymanager:trustmanager -Djava.security.debug=access:stack
and see the following differences:
***for url1 (working fine)***
> SendHTTPTest.f9() connection opened Allow unsafe renegotiation: false
> Allow legacy hello messages: true Is initial handshake: true Is secure
> renegotiation: false main, the previous server name in SNI
> (type=host_name (0), value=**foo1.blabla.com**) was replaced with (type=host_name
> (0), value=**foo1.blabla.com**) Extension extended_master_secret Extension
> server_name, server_name: [type=host_name (0), value=**foo1.blabla.com**]
> *** main, WRITE: TLSv1.2 Handshake, length = 226 main, READ: TLSv1.2 Handshake, length = 2303
> *** ServerHello, TLSv1.2
***for url2 (connection reset problem)***
> SendHTTPTest.f9() connection opened
> **main, "foo2_bar.blabla.com" is not a legal HostName for server name indication** Allow unsafe renegotiation: false Allow legacy hello messages: true Is
> initial handshake: true Is secure renegotiation: false
> **main, "foo2_bar.blabla.com" is not a legal HostName for server name indication**
> main, WRITE: TLSv1.2 Handshake, length = 193 main, handling exception:
> java.net.SocketException: Connection reset main, SEND TLSv1.2 ALERT:
> fatal, description = unexpected_message main, WRITE: TLSv1.2 Alert,
> length = 2 main, Exception sending alert: java.net.SocketException:
> Connection reset by peer: socket write error main, called
> closeSocket() java.net.SocketException: Connection reset
Probably the message causes **main, "foo2_bar.blabla.com" is not a legal HostName for server name indication** the problem, it is SNI related? Underscore in url2 may cause problem?
https://stackoverflow.com/questions/36323704/sni-client-side-mystery-using-java8
</details>
# 答案1
**得分**: 2
(从解决方案和搜索的注释中)
在 SSL/TLS 握手过程中,许多因素可能会导致重置,这取决于服务器,但如今一个常见的原因是缺少**服务器名称指示(Server Name Indication,SNI)**。
除了某些旧版本中的错误之外,**Java(JSSE)在几种情况下无法发送 SNI**:
* 主机名是 IP 地址(v4 或 v6)
* 主机名不包含点,或者在末尾包含点(即不“看起来像”DNS 名称)
* 主机名包含除字母、数字和连字符以外的 ASCII 字符(在 DNS 和 IDN 允许的位置),以及点(在 DNS 允许的位置);这个限制显然是基于 RFC952,正如 STD3=RFC1123 中所引用的那样。(非 ASCII 字符 —— Unicode U+0080 及以上 —— 将根据 IDN 规则转换为 punycode,按设计符合这些限制。)
在这种情况下,问题出在第三点上;主机名包含了 ASCII 下划线。
<details>
<summary>英文:</summary>
(From comments for resolution, and search)
**Many things can cause reset** on SSL/TLS handshake, depending on the server, but nowadays a common one is **missing [Server Name Indication (SNI)](https://en.wikipedia.org/wiki/Server_Name_Indication)**.
Aside from bugs in some older versions, **Java (JSSE) fails to send SNI in several cases**:
* hostname is an IP address (v4 or v6)
* hostname contains no dot, or has dot at end (i.e. doesn't 'look like' a DNS name)
* hostname contains ASCII characters other than letters, digits, and hyphen (in the positions allowed by DNS and IDN) and dot (in the positions allowed by DNS); this restriction is apparently based on RFC952 as referenced in STD3=RFC1123. (_NonASCII_ characters -- Unicode U+0080 and up -- are converted following IDN rules to punycode, which by design satisfies the restrictions.)
In this case the problem was the third point; the hostname contained an ASCII underscore.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论