问题描述
最近为我司系统接入某第三方服务,假设该第三方服务为W系统,使用https协议对外提供接口,访问W系统接口的时候,收到如下错误:
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://open.wwww.com/api/device/status": Received fatal alert: protocol_version; nested exception is javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:746)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:672)
根据日志提示,可猜测为SSL协议版本问题造成的异常。
问题分析
首先查看一下W系统支持的SSL协议版本
方法一:通过myssl.com网站
输出如下:
可知,W系统仅支持TLSv1.2。
以上工具地址为:SSL/TLS安全评估报告 (myssl.com)
方法二:通过nmap命令
[root@as01-251-81 ~]# nmap --script ssl-enum-ciphers -p 443 open.wwww.com
Starting Nmap 7.70 ( https://nmap.org ) at 2022-06-28 11:34 CST
Nmap scan report for open.figps.com (120.55.18.63)
Host is up (0.010s latency).
PORT STATE SERVICE
443/tcp open https
| ssl-enum-ciphers:
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (dh 2048) - A
| TLS_DHE_RSA_WITH_AES_128_CCM_8 (dh 2048) - A
| TLS_DHE_RSA_WITH_AES_128_CCM (dh 2048) - A
| TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (dh 2048) - A
| TLS_DHE_RSA_WITH_AES_128_CBC_SHA (dh 2048) - A
| compressors:
| NULL
| cipher preference: server
|_ least strength: A
输出是一致的,也是仅支持TSLv1.2。
通过该命令查看阿里云接口支持的SSL协议:
[root@as01 ~]# nmap --script ssl-enum-ciphers -p 443 dypnsapi.aliyuncs.com
Starting Nmap 7.70 ( https://nmap.org ) at 2022-06-28 11:15 CST
Nmap scan report for dypnsapi.aliyuncs.com (106.11.45.35)
Host is up (0.029s latency).
PORT STATE SERVICE
443/tcp open https
| ssl-enum-ciphers:
| TLSv1.0:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| TLSv1.1:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
| TLSv1.2:
| ciphers:
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
| TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
| TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
| TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
| compressors:
| NULL
| cipher preference: server
|_ least strength: A
可以看到,阿里云接口支持多个版本的SSL协议。
接下来看一下客户端支持的协议版本,首先看一下JDK的版本:
[root@as01 ~]# java -version
java version "1.8.0_281"
Java(TM) SE Runtime Environment (build 1.8.0_281-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.281-b09, mixed mode)
jdk1.8支持的SSL协议版本:
TLSv1.2 (default)
TLSv1.1
TLSv1
SSLv3
来源:Diagnosing TLS, SSL, and HTTPS (oracle.com)
综上,客户端和服务的协议版本是能够匹配的,为什么会报protocol_version的异常还需要进一步探查。
接下来,通过在应用程序的启动命令中添加-Djavax.net.debug=all 打开网络调试日志,如下所示:
java -Xmx4096m -Xms4096m -jar -Djavax.net.debug=all /alidata0/wwwjava/myapp-dir/jar/myapp-name.jar --spring.profiles.active=prod
观察输出日志,发现在调用goeasy接口之前,对第三方系统调用的SSL协议有多个版本,在goeasy接口调用之后,所有其他第三方的接口调用的SSL协议版本都变成了TLSv1,观察握手部分的ClientHello即可:
*** ClientHello, TLSv1
RandomCookie: GMT: 1530541852 bytes = { 25, 106, 142, 179, 195, 87, 163, 223, 105, 170, 57, 91, 102, 15, 218, 48, 52, 167, 231, 83, 190, 177, 54, 27, 232, 111, 11, 140 }
Session ID: {}
继续看goeasy代码,发现其通过环境变量的方式强制设置了SSL协议版本为TSLv1:
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
if (PUBLISH_URL.startsWith("https://")) {
System.setProperty("https.protocols", "TLSv1");
TrustManager[] tm = {new PubSubX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
httpsConn.setSSLSocketFactory(ssf);
}
这个方式太暴力了,这也解释了为什么在解决该问题期间,不管是尝试在http客户端上设置SSL协议版本,还是在Springboot启动的时候,通过环境变量设置SSL协议版本,都是无效的原因。
曾尝试在http客户端工具上设置协议版本:
SSLContext ctx = SSLContexts.custom().useProtocol("TLSv1.2").build();
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(new SSLConnectionSocketFactory(ctx)).build();
曾尝试在Springboot启动的时候,通过环境变量设置协议版本:
System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,TLSv1.0,SSLv3");
解决方案
找到问题原因,解决方案就好说了,把goeasy的版本从0.3.16升级到0.4.0,问题就解决了。查看升级之后的goeasy代码:
URL url = new URL(PUBLISH_URL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
if (PUBLISH_URL.startsWith("https://")) {
TrustManager[] tm = {new PubSubX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
httpsConn.setSSLSocketFactory(ssf);
}
已经去掉了设置TSLv1的代码。
https://mvnrepository.com/artifact/io.goeasy/goeasy-sdk
背景知识
SSL是Secure Sockets Layer的缩写,TLS是Transport Layer Security的缩写。
SSL v1.0由于安全问题没有公开发布,SSL v2.0于1995年2月发布,但依然有安全问题,直到1996年的SSL v3.0发布,才修正这些问题。
TLS1.0是SSL v3.0的升级版,是更新、更安全的 SSL 版本,目前市面上所有的https都基于TLS。
以下是SSL/TLS的发布历史:
SSL 1.0 – never publicly released due to security issues.
SSL 2.0 – released in 1995. Deprecated in 2011. Has known security issues.
SSL 3.0 – released in 1996. Deprecated in 2015. Has known security issues.
TLS 1.0 – released in 1999 as an upgrade to SSL 3.0. Planned deprecation in 2020.
TLS 1.1 – released in 2006. Planned deprecation in 2020.
TLS 1.2 – released in 2008.
TLS 1.3 – released in 2018.
SSL和TLS这两个术语可以混用。一般在技术交流中会使用TLS,市场宣传中更多的用SSL,如下:
Sorry, the comment form is closed at this time.
No comments yet.