CS两端TLS版本不适配导致Connection reset问题

2018-10-01  本文已影响0人  码代码的陈同学

欢迎访问陈同学博客原文

问题背景

近期平台在公司的一个出口IP流量偶尔抖动,在与运营商扯皮无结果后,IT帮忙开了一条新的专线。我们需要把域名在公网的DNS指向新的出口IP。

下面是简图: image

切换DNS后,部署在云服务器上的应用利用Http Client访问部署在公司内网的服务时,出现 java.net.SocketException: Connection reset 异常。

部分stacktrace如下,Http Client在建立连接时出现了问题。

Caused by: java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:196)
    at java.net.SocketInputStream.read(SocketInputStream.java:122)
    at sun.security.ssl.InputRecord.readFully(InputRecord.java:442)
    at sun.security.ssl.InputRecord.read(InputRecord.java:480)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:934)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1332)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1359)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1343)
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)

问题排查

至此,问题解决。是因为公司代理只支持TLSv1.1、TLSv1.2,而客户端发起请求时用的是TLSv1,服务端直接拒绝握手。虽然无法升级jdk版本,但可以指定http client使用的TLS协议版本。

解决问题还是比较简单,下面借助于问题学习下涉及到的知识。

晓波补充:C不止于Java Client,像浏览器等其他client也会出现类似问题。问题可以归纳为CS两端TLS协议版本不适配。

拓展学习

什么是 connection reset ?

指服务端因某种原因关闭了连接,而此时客户端依然在读取数据,此时服务端会返回复位标识 RST,客户端就会提示:java.net.SocketException: Connection reset

造成 connection reset 原因很多,本文的问题是因TLS协议问题导致。

JDK1.7与1.8在TLS协议方面的区别?

jdk1.7 默认是TLSv1,但支持TLSv1.1、TLSv1.2

jdk1.8 默认是TLSv1.2.

通过以下代码可以查看受支持的协议(supported protocols)和启用的协议(enabled protocols),可以从受支持的协议中进行选择并启用。

SSLContext context = SSLContext.getInstance("TLS");
context.init(null, null, null);

SSLSocketFactory factory = context.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket();

String[] protocols = socket.getSupportedProtocols();
System.out.println(Arrays.asList(protocols));

protocols = socket.getEnabledProtocols();
System.out.println(Arrays.asList(protocols));

贴一下 Oracle 的一篇blog: Diagnosing TLS, SSL, and HTTPS,摘取几个JDK版本中TLS协议信息:

JDK 8 (March 2014 to present) JDK 7 (July 2011 to present) JDK 6 (2006 to end of public updates 2013)
TLS Protocols TLSv1.2 (default) <br />TLSv1.1<br />TLSv1<br />SSLv3 TLSv1.2<br />TLSv1.1<br />TLSv1 (default) <br />SSLv3 TLS v1.1 (JDK 6 update 111 and above) <br />TLSv1 (default) <br />SSLv3

为什么-Dhttps.protocols=TLSv1.2不生效?

最开始就怀疑是TLS协议问题,但因设置该系统参数无效,导致忽略了这个因子,最后却证实依然是这个问题。那为什么这个参数不生效?

参考 Setting TLSv1.2 in https.protocols not workingDiagnosing TLS, SSL, and HTTPS

发现 https.protocols 环境变量只对 HttpsURLConnection 有效,下面是两个相关参数:

为什么OkHttp在jdk1.7没问题?

后续我又用了OkHttp替换Http Client,发现一切OK。OkHttp默认会使用TLSv1.2。OkHttp只知道但没用过,看了下发现使用还挺方便,如果是新项目,可以使用OkHttp替换Http Client。

关于 TLS1.0、1.1、1.2、1.3

数据来自 PCI DSS合规标准:禁用不安全的TLS 1.0

TLS各版本的信息稍微了解一下。

防人之心不可无

对于小团队来说,一方面由于安全意识薄弱,且没有专门的安全团队,精力全部扑在业务上;另一方面,这些团队的产品基本也不在攻击范围之内,没有什么攻击价值。

但没有发生危险不代表没有危险,该来的总会来。因此,对于开展的新项目,还是禁用TLS1.0、1.1,只使用TLS1.2,迎接TLS1.3的普及!


欢迎关注陈同学的公众号,一起学习,一起成长

image
上一篇 下一篇

猜你喜欢

热点阅读