Java的HttpsUrlConnection,连接重置。

huangapple go评论69阅读模式
英文:

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:

Link 1

Link 2

Link 3

Link 4

  • 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 = &quot;foo1.blabla.com&quot;;
    String url2 = &quot;foo2_bar.blabla.com&quot;;
    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)***

&gt; SendHTTPTest.f9() connection opened Allow unsafe renegotiation: false
&gt; Allow legacy hello messages: true Is initial handshake: true Is secure
&gt; renegotiation: false main, the previous server name in SNI
&gt; (type=host_name (0), value=**foo1.blabla.com**) was replaced with (type=host_name
&gt; (0), value=**foo1.blabla.com**) Extension extended_master_secret Extension
&gt; server_name, server_name: [type=host_name (0), value=**foo1.blabla.com**]
&gt; *** main, WRITE: TLSv1.2 Handshake, length = 226 main, READ: TLSv1.2 Handshake, length = 2303
&gt; *** ServerHello, TLSv1.2

***for url2 (connection reset problem)***

&gt; SendHTTPTest.f9() connection opened
&gt; **main, &quot;foo2_bar.blabla.com&quot; is not a legal HostName for  server name indication** Allow unsafe renegotiation: false Allow legacy hello messages: true Is
&gt; initial handshake: true Is secure renegotiation: false
&gt; **main, &quot;foo2_bar.blabla.com&quot; is not a legal HostName for  server name indication**
&gt; main, WRITE: TLSv1.2 Handshake, length = 193 main, handling exception:
&gt; java.net.SocketException: Connection reset main, SEND TLSv1.2 ALERT: 
&gt; fatal, description = unexpected_message main, WRITE: TLSv1.2 Alert,
&gt; length = 2 main, Exception sending alert: java.net.SocketException:
&gt; Connection reset by peer: socket write error main, called
&gt; closeSocket() java.net.SocketException: Connection reset

Probably the message causes **main, &quot;foo2_bar.blabla.com&quot; 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&#39;t &#39;look like&#39; 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>



huangapple
  • 本文由 发表于 2020年9月12日 06:46:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/63855202.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定