一次SSL握手异常,我发现JDK还有发行版区别

原创:扣钉日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。

简介

最近,我们一个多机房部署的服务,调用方反馈有问题,在调用新加坡机房时正常,而调用印度机房则报SSL握手异常。

排查花了一些时间,同时也积累了一些经验,故记录一下,读完本文,你将了解到如下内容:

  1. SSL握手过程
  2. SSL握手异常时的排查思路与工具
  3. 同版本的JDK,也是有所差异的

废话不多说,往下看…

发现问题

调用方调用印度机房服务时,报错信息如下:

一次SSL握手异常,我发现JDK还有发行版区别

这个异常是同事一直在看,经过一翻搜索,怀疑是JDK版本的问题,经过询问调用方,发现调用方版本是 1.8.0_91-b14,于是同事打算下载此版本JDK本地测试一下。

但这个版本JDK不太好找,于是同事就问了下我,我也找了一会也没找到,于是打算从源码编译一个此版本JDK。

经过一段时间,我通过源码编译出来了这个版本的jdk,同时同事也在网上找到了一个此版本的JDK,如下:
JDK源码:https://github.com/openjdk/jdk8u ,tag选择jdk8u91-b14即可。
网上的JDK包:https://github.com/ojdkbuild/ojdkbuild/releases/download/1.8.0.91-3/java-1.8.0-openjdk-1.8.0.91-1.b14.el6.x86_64.zip

弄到 1.8.0_91-b14版JDK后,我和同事都进行了测试,奇怪的是,同事网上找的JDK重现了调用方的报错,即新加坡机房正常,而印度机房SSL握手失败,但我自己编译的JDK则两个机房都正常,我们可是相同版本的JDK啊!

好家伙,现在有2个疑问了,如下:

  1. 为啥新加坡机房正常,而印度机房SSL握手报错?
  2. 为啥相同版本的JDK,自己编译的没有问题?

为啥SSL握手报错?

由于我之前解决过一次SSL握手异常的bug,也写成了一篇文章 一次IOS通知推送问题排查全过程,原因是由于客户端与服务端密码套件不一致导致的。

粗略来讲,SSL握手过程如下:

  1. 客户端发送Client Hello包给服务端,其中除了包含密钥协商相关的数据外,还会告知自己支持的密码套件列表。
  2. 服务端收到Client Hello包后,会给客户端回复Server Hello,其中也包含了密钥协商数据,以及服务端选择了哪个密码套件。

但有一种情况是,客户端第一步发送的所有密码套件,服务端都不支持,因此服务端会回复一个SSL握手异常包,进而导致客户端失败报错。

注:密码套件,指的是加密系统将多种密码学算法混合使用,以实现多种安全需求,如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,使用ECDHE实现密钥协商、RSA实现证书认证、AES实现加密、SHA256实现消息防篡改。

如何确认是否是上面原因呢?我进行了如下测试:

  1. 添加JVM参数 -Djavax.net.debug=SSL,并调用正常的新加坡机房,看看SSL握手选择的是什么密码套件。
$ bin/java -Djavax.net.debug=SSL SgSendRequest

一次SSL握手异常,我发现JDK还有发行版区别
可以看到,客户端提供了很多密码套件,服务端选择了 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,那么极有可能是印度机房不支持此密码套件,导致印度机房请求失败,可用curl确认下:
  1. 使用curl以指定密码套件DHE-RSA-AES128-GCM-SHA256访问印度机房。
$ curl -v https://in.xxx.be.srv.com --ciphers DHE-RSA-AES128-GCM-SHA256

一次SSL握手异常,我发现JDK还有发行版区别
可以发现,印度机房确实不支持此密码套件。

注:jdk密码套件名称与curl的名称稍微有点不一致,curl的可以在这里查找 https://curl.se/docs/ssl-ciphers.html

这也就是说,此JDK支持的密码学套件与印度机房支持的密码学套件没有交集,服务端无法选出一个双方都支持的密码套件,可以进一步确认下,如下:
jdk支持的密码套件可以通过 SSLServerSocketFactory.getSupportedCipherSuites()获取。

$ bin/jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"

一次SSL握手异常,我发现JDK还有发行版区别

印度机房支持的密码套件可以使用nmap扫描获取,如下:

$ nmap --script ssl-enum-ciphers -p 443 in.xxx.be.srv.com

一次SSL握手异常,我发现JDK还有发行版区别
经过我的检查,发现jdk的密码套件与印度机房的密码套件确实没有交集,印度机房只支持一些较新的密码套件,这就是调用印度机房服务时SSL握手失败的原因。

用相同的方法,我也确认了新加坡机房,发现新加坡机房的密码套件与jdk的密码套件有交集,而 TLS_DHE_RSA_WITH_AES_128_GCM_SHA256就在其中。

要解决这个问题也比较容易,要么让调用方升级jdk以支持新的密码套件,要么让印度机房SRE调整SSL配置以支持旧的密码套件,我们选择了前者。

那么,还有一个问题,为啥我自己编译的同版本的JDK就没有问题呢?

为啥自行编译的JDK没有问题?

有点迷惑,我用上面相同方法确认了一下我自己编译的JDK支持哪些套件,如下:

$ bin/jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"

一次SSL握手异常,我发现JDK还有发行版区别
可以发现,我自己编译的JDK,支持ECDH系列的新密码套件,这是为啥?

为了弄清区别,我使用问题JDK进行了调试,如下:

import javax.crypto.KeyAgreement;
import java.security.NoSuchAlgorithmException;

public class EcdhTest {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        KeyAgreement ka = KeyAgreement.getInstance("ECDH");
        System.out.println(ka);
    }
}

在问题JDK里面,会报如下异常:

$ bin/java EcdhTest
Exception in thread "main" java.security.NoSuchAlgorithmException: Algorithm ECDH not available
        at javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:184)
        at EcdhTest.main(EcdhTest.java:6)

有异常就好办了,只要顺着异常产生的过程调试下去即可,大概调试了如下相关方法:

sun.security.ssl.JsseJce.getKeyAgreement("ECDH")
sun.security.ec.SunEC

当调试到SunEC类时,我发现在加载 sunec动态库时会报错,如下:

一次SSL握手异常,我发现JDK还有发行版区别

于是,我去问题jdk目录下查找这个动态库文件,动态库文件在Linux下一般是 .so结尾,如下:

$ find | grep sunec
./jre/lib/ext/sunec.jar
./jre/lib/amd64/libsunec.so_DISABLED
./jre/lib/amd64/libsunec.diz

懵逼了,在这个问题JDK里, libsunec.so竟然被改名为了 libsunec.so_DISABLED,而我看了下我自己编译的JDK,这个文件是没有改名的!

终于,第二个问题也找到了原因,原来是网上找的这个JDK,通过改名 libsunec.so将EC系列算法禁用了。
我大概看了会那个JDK下载页面,这个JDK构建时间挺久了,是RedHat早期为CentOS6构建的一个JDK8版本,至于为啥要禁用EC系列算法,也没找到相关解释,只好就此打住。

总结

这个问题在报错能被稳定重现出来时,其实就不难了,但排查思路与使用到的工具还是挺值得分享的,如下:

  1. 客户端与服务端支持的密码套件没有交集,会导致SSL握手失败。
  2. 使用 -Djavax.net.debug=SSL可以调试java的SSL握手过程。
  3. 通过 curl --ciphers指定客户端密码套件访问服务端,可以确认服务端是否支持此密码套件。
  4. 通过 SSLServerSocketFactory.getSupportedCipherSuites()可获取JDK支持的密码套件。
  5. 使用 nmap --script ssl-enum-ciphers可扫描出服务端支持的密码套件。
  6. 同样版本的JDK,不同发行商发行的,也可能存在着差异。

往期内容

一次IOS通知推送问题排查全过程
密码学入门
接口偶尔超时,竟又是JVM停顿的锅!
耗时几个月,终于找到了JVM停顿十几秒的原因
mysql的timestamp会存在时区问题?
真正理解可重复读事务隔离级别
字符编码解惑

Original: https://www.cnblogs.com/codelogs/p/16633704.html
Author: 扣钉日记
Title: 一次SSL握手异常,我发现JDK还有发行版区别

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/581355/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • crudapi增删改查接口零代码产品成功案例之商会联盟卡项目

    crudapi增删改查接口零代码产品成功案例之商会联盟卡项目 简介 商会联盟卡项目主要通过免费领取会员卡的方式吸引会员去合作商家线下消费,通过会员卡买单可以享受打折福利,最终顾客得…

    Java 2023年6月6日
    0118
  • MyBatis-Plus–@TableLogic注解

    开发过程中一般会遇到删除场景,但是为了保存数据实际运用时不会真的删除,MyBatis-Plus里可以将某个字段(例:delete_flag)标记为逻辑删除字段,方法是:在字段上加@…

    Java 2023年6月13日
    079
  • nginx重新整理——————nginx 的设计模型[八]

    前言 简单介绍一下nginx的设计模型,对我们设计程序还是有一定帮助的。 正文 这里先列一下模型哈,后面有深入篇,介绍的比较清楚。 nginx 的处理模型: nginx 进程模型:…

    Java 2023年5月30日
    074
  • 【校招VIP】[前端][一本][6分]项目需要考虑到PC端和移动端

    关注【校招VIP】 公众号,回复【简历】 ,添加校招顾问微信,即可获取简历指导! 本份简历是一位21届一本前端同学的简历,简历评分6分。 一、学员简历 二、指导意见 简历模板没有问…

    Java 2023年6月5日
    078
  • 各种集合的集合

    数组千千万,集合是真理 我们在代码中常见的数据保存形式一般就是两种—>数组与集合;数组和集合的最大的区别在于查询和扩展,一般情况下,在数据确定的情况下,我们采用数组的形式会更…

    Java 2023年5月29日
    086
  • 为何在JDK安装路径下存在两个JRE?

    “两个jre”和”三个lib”的功能简单扼要的解释 安装JDK后,Java目录下有jdk和jre两个文件夹,但jdk下还有一个jre…

    Java 2023年5月30日
    086
  • qemu aarch64虚拟机创建好后,使用NAT连接网络

    宿主机上创建文件 /etc/qemu-ifup,内容如下 #!/bin/sh # Copyright IBM, Corp. 2010 # Authors: Anthony Ligu…

    Java 2023年5月30日
    062
  • SpringBoot 2.x 开发案例之前后端分离鉴权

    前言 阅读本文需要一定的前后端开发基础,前后端分离已成为互联网项目开发的业界标准使用方式,通过 Nginx代理+Tomcat的方式有效的…

    Java 2023年5月30日
    081
  • IBM MQ Explorer 示例操作

    此示例为双向传输 建立队列管理器 建立【test01】【test02】两个队列管理器,一直下一步即可,端口号不能一致(需要记住设置的端口号,后面会用到) 【test01】端口号 1…

    Java 2023年5月29日
    076
  • 设计模式学习笔记(二十)状态模式及其实现

    状态模式(State Pattern)指允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。 一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统的开发中:…

    Java 2023年6月6日
    090
  • 索引有什么分类?

    索引有什么分类? – 1、主键索引:名为primary的唯一非空索引,不允许有空值。 – 2、唯一索引:索引列中的值必须是唯一的,但是允许为空值。唯一索引和…

    Java 2023年6月5日
    080
  • MySQL中的事务和MVCC

    本篇博客参考掘金小册——MySQL 是怎样运行的:从根儿上理解 MySQL 以及极客时间——MySQL实战45讲。 虽然我们不是DBA,可能对数据库没那么了解,但是对于数据库中的索…

    Java 2023年6月5日
    094
  • 引路蜂地图API:Gis.Location包定义

    本包定义了GPS接收器一个通用接口,并提供对NMEA 2.0数据的解码方法。在Java ME平台上对JSR179 进行了封装. Coordinates 定义地址经纬度坐标。 Loc…

    Java 2023年5月30日
    0109
  • idea快捷键指南:让你开发效率蹭蹭蹭的上涨

    一文让你开发效率蹭蹭蹭的上涨(idea快捷键指南) 没有写使用说明的就表示没有特别的注意事项直接用就可以了。 Ctrl + Shift + A 说明:IDEA 把所有的可以执行的操…

    Java 2023年6月5日
    074
  • 图解BM(Boyer-Moore)字符串匹配算法+代码实现

    简介 本篇文章主要分为两个大的部分,第一部分通过图解的方式讲解BM算法,第二部分则代码实现一个简易的BM算法。 基本概念 bm是一个字符串匹配算法,有实验统计,该算法是著名kmp算…

    Java 2023年6月9日
    091
  • Spring1

    一、简介: Spring : 为简化开发而生,但是后期由于各类组件的增多,使得其配置过于繁琐,本身就是一个大杂烩 , 整合现有的框架技术,渐渐的被称为了”配置地狱&#8…

    Java 2023年6月9日
    098
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球