陈同学
微服务
Accelerator
About
# CS两端TLS版本不适配导致Connection reset问题 ## 问题背景 近期平台在公司的一个出口IP流量偶尔抖动,在与运营商扯皮无结果后,IT帮忙开了一条新的专线。我们需要把域名在公网的DNS指向新的出口IP。 下面是简图:<img src="https://blog-1256695615.cos.ap-shanghai.myqcloud.com/2018/09/26/4963ac7aeb1541a08f92b82f1a6f7eaa.png" width="60%"> * 旧:流量经公网IP 126,采用端口映射直接到平台的代理机,然后再转发给后端具体的业务代理。 * 新:流量经公网IP 189,然后经公司统一代理(Proxy A),由代理把流量转发到我们自己的代理上。 切换DNS后,部署在云服务器上的应用利用Http Client访问部署在公司内网的服务时,出现 `java.net.SocketException: Connection reset` 异常。 部分stacktrace如下,Http Client在建立连接时出现了问题。 ```java 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) ``` ## 问题排查 * 因应用未更新,仅切换了DNS,这是唯一变量。做了个试验,将流量切换回旧的 **x.x.x.126** 后,一切正常。 **=>** **初步确定问题是由新线路与旧线路有些不同导致!** * 领导特意提醒:公司统一代理只支持TLSv1.1、TLSv1.2。我在本地测试,加了系统参数 **-Dhttps.protocols=TLSv1.2**,发现还是出同样问题。 **=>** 于是,**认为这个问题可能和TLS版本无关**。 * 用 **curl** 和 **postman** 发起相同请求,一切正常。 **=>** **那应该就是Http Client 这里的问题了**。 * 由于我们对Http Client做了封装,简化了使用(简称A项目)。因此,新建了个项目(简称B项目),直接用 Http Client 发起请求,发现竟然请求成功了。 **=>** 真是不可思议,那两个项目的差别是什么?一下也没想起来。 * 因为是网络问题,因此,A、B项目都加上系统参数 **-Djavax.net.debug=all**,希望能抓住点蛛丝马迹。一运行,还真不一样。 A项目是jdk1.7,建立连接时报文有:**ClientHello, TLSv1** B项目是jdk1.8,建立连接时报文有:**ClientHello, TLSv1.2** 此时,才想起,这是一个旧项目,用的是jdk1.7。 至此,问题解决。是因为公司代理只支持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),可以从受支持的协议中进行选择并启用。 ```java 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](https://blogs.oracle.com/java-platform-group/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](http://www.oracle.com/technetwork/java/eol-135779.html)) | | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------------------------------- | ------------------------------------------------------------ | | [TLS Protocols](https://en.wikipedia.org/wiki/Transport_Layer_Security#History_and_development) | [TLSv1.2 (default)](https://blogs.oracle.com/java-platform-group/entry/java_8_will_use_tls) <br />TLSv1.1<br />TLSv1<br />SSLv3 | TLSv1.2<br />TLSv1.1<br />TLSv1 (default) <br />SSLv3 | TLS v1.1 ([JDK 6 update 111](http://www.oracle.com/technetwork/java/javase/overview-156328.html) and above) <br />TLSv1 (default) <br />SSLv3 | ### 为什么-Dhttps.protocols=TLSv1.2不生效? 最开始就怀疑是TLS协议问题,但因设置该系统参数无效,导致忽略了这个因子,最后却证实依然是这个问题。那为什么这个参数不生效? 参考 [Setting TLSv1.2 in https.protocols not working](https://stackoverflow.com/questions/36031412/java-setting-tlsv1-2-in-https-protocols-not-working) 和 [Diagnosing TLS, SSL, and HTTPS](https://blogs.oracle.com/java-platform-group/diagnosing-tls,-ssl,-and-https)。 发现 **https.protocols** 环境变量只对 **HttpsURLConnection** 有效,下面是两个相关参数: * **javax.net.debug** 打印connection相关信息,例如: *-Djavax.net.debug=all* 或 *-Djavax.net.debug=ssl:handshake* * **https.protocols** 控制使用 **HttpsURLConnection** 或 **URL.openStream()** 获取https连接的Java Clients 的协议版本。 例如:*-Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2* ### 为什么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](https://www.trustauth.cn/news/security-news/26215.html) TLS各版本的信息稍微了解一下。 * TLS 1.0于1999年发行,至今将近有20年。对于目前的互联网技术,TLS 1.0的存在可以说就是一种安全隐患。因为TLS 1.0易受各种攻击(如BEAST和POODLE)已有多年,除此之外,支持较弱加密,对当今网络连接的安全已失去应有的保护效力。因此,从去年开始,众多平台、安全企业纷纷放弃。 * 2017年的年中,微软强烈建议企业、及其客户或者合作伙伴禁用已经出现问题的老旧版本TLS 1.0及TLS 1.1。 * 2018年2月,GitHub停止支持弱加密标准,其中就包括弃用TLS 1.0及1.1协议。 * 2018年4月1日,DigiCert禁用TLS 1.0/1.1,只支持TLS 1.2和更高版本。 * 2018年6月21日,GlobalSign 将禁用 TL1.0 和 TLS1.1 * 2018年6月30日,PCI 安全标准要求各大网站停止支持TLS1.0 * 2018年8月10日,互联网工程任务组(IETF)发布了最新版本的传输层安全TLS——TLS 1.3,相比之前所有的版本,TLS 1.3顺应了目前互联网的需求,在安全性和响应速度性能方面作了更进一步的提升。TLS 1.3的出现,将会加快淘汰TLS 1.0的速度。 ### 防人之心不可无 对于小团队来说,一方面由于安全意识薄弱,且没有专门的安全团队,精力全部扑在业务上;另一方面,这些团队的产品基本也不在攻击范围之内,没有什么攻击价值。 但没有发生危险不代表没有危险,该来的总会来。因此,对于开展的新项目,还是禁用TLS1.0、1.1,只使用TLS1.2,迎接TLS1.3的普及!
本文由
cyj
创作,可自由转载、引用,但需署名作者且注明文章出处。
文章标题:
CS两端TLS版本不适配导致Connection reset问题
文章链接:
https://chenyongjun.vip/articles/77
扫码或搜索 cyjrun 关注微信公众号, 结伴学习, 一起努力