原创:打码日记(微信公众号 ID:codelogs),欢送分享,转载请保留出处。
发现问题
在上周一个将要上班的夜晚,测试忽然和我打招呼,说 IOS 推送的修复更新上线后存在问题,后盾报错。
连忙跑到测试那里看报错详情,报错如下:
重现问题
看到这个报错后,在网上搜寻了一下,这种谬误个别都是因为客户端不信赖服务端 SSL 证书导致的,回忆工作以来,如同遇到这种问题好屡次了,只有将证书导入一下就好了。
因为不能冒然在线上批改解决问题,于是获取推送相干信息 (如:设施 token) 后,到本人电脑下来测试,看是否能重现问题。
啪啦啪啦,代码批改结束,点运行坐等谬误呈现。
5 秒钟过后,发现推送音讯发送胜利了,没有呈现报错,有点懵逼!心里想,代码都是齐全一样的啊,怎么线上报错,我这却是好的呢???
纠结了一会,于是开始静下心来剖析:
- 代码必定是一样的,应该不是外表上的代码起因。
- 其次推送设施也是一样的,应该也不是手机问题。
- 那么。。。
就在没有脉络之际,我又扫了一眼工程目录,发现了 jdk8,但咱们线上零碎应用的是 jdk7 啊。
于是我将本人工程的 jdk8 换成 jdk7 试一下,同样的报错终于呈现了!
那为什么 jdk8 没问题,jdk7 却报错呢???
寻找起因
围绕着网上的说法和之前的教训,这 hand_failure
谬误应该是 SSL/TLS 握手过程中不信赖服务端证书导致的,那方法很简略,去苹果官网找到苹果服务端提供的证书,导入到 java 的证书信赖库 cacert 文件中即可。
于是,下载了证书文件 GeoTrust_Global__CA.cer
,并用 jdk 自带的 keytool 工具将证书导入到 cacert 文件中,如下:
如上,导入过程中,发现提醒曾经有这个证书了,过后没想那么多,再导一次试试吧!
导入后,再次运行代码,后果还是报错!
心里又慌乱起来,没招了,于是又百度 /google 去了,但搜寻进去的论断简直都是证书信赖问题,和本人的招式一样!
同时,也理解了一下 SSL/TLS 协定相干常识,大抵握手过程如下:
具体如下:
- 客户端发送 clientHello 音讯,通知服务端我应用的 TLS 版本与加密套件 等。
- 服务器返回 serverHello 音讯,通知本人抉择哪个 TLS 协定版本与加密套件 等。
- 服务器发送 Certification 音讯,将本人的 数字证书(包含服务器名称、CA 和公钥)作为音讯内容发给客户端。
- 客户端 Certificate verify 校验服务器的 数字证书 的有效性。
- 客户端 Change cipher spec 抉择加密套件并生成 会话密钥(客户端与服务器之间后续的数据传输将应用此会话密钥)。
- 客户端、服务器之间发送加密数据。
另外,jvm 有一个 -Djavax.net.debug=SSL
的参数,能够把 SSL/TLS 的握手过程都在控制台通过日志显示进去,如下:
Ok,既然晓得了握手过程,那就把 SSL/TLS 的日志显示进去看一下吧,看看是什么阶段呈现的问题,如下:
这阐明 GeoTrust_Global.cer
证书的确曾经增加到信赖库中了,而具体的 SSL 日志如下:
如上图,报错产生在 clientHello 发送之后,在读服务端返回的 serverHello 音讯时,却读到的是 Alert:handshake_failure
正告信息,而后 SSL 握手就中断了,就如同你在和服务器打招呼,而后服务器回复了一个滚蛋一样!
百思不得其解?
筹备放弃
一会,我看到测试走了过去。
测试:“问题解决得怎么样了,啥状况啊”
我:“还不晓得,原本认为很简略,实际上并没有,jdk8 能够,7 不行”
测试:“好吧”
我:”要不降级 jdk8 吧“
测试:”能行吗“
我:”…… 能行,jdk 兼容性都很好,而且其它零碎都曾经降级过了,这个零碎原本也打算要升,问题不大“
测试:”…… 好的“
于是,测试去降级 jdk8 了,原本我应该站在测试身后期待问题被解决,但我还是想在这期间查一查根本原因,于是又推敲了起来。
发现假相
回想起,这个零碎以前原本是 jdk6,就是因为苹果强制开发者必须应用 Https,且须要 TLSv1.2 版本,而 jdk7 才开始反对 TLSv1.2,所以这个零碎才降级到 jdk7,那当初这个报错会不会 …
于是,我连忙应用 TLSv1.2 试试,增加 jvm 参数 -Dhttps.protocols=TLSv1.2
即可强制 https 应用 TLSv1.2 版本协定,如下:
加完后再次运行,报错仍旧,那还有哪里可能会有问题呢?
我忽然灵光一闪,应用 jdk7 运行一次,再应用 jdk8 运行一次,将两次的 SSL 日志比照一下,看看哪里不一样,说不定能找到线索!
于是我马上试运行并比照两次 SSL 日志,发现两次 SSL 日志中 TLS 协定应用的加密套件不同,如下图:
jdk7 的调试信息如下:
jdk8 的调试信息如下:
jdk8 的 TLS 握手在服务端 ServerHello 之后,抉择的加密套件是TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
,而 jdk7 中可供选择的加密套件中没有TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
。
问题差不多清晰了,应该是 jdk7 中的加密套件,苹果服务器感觉太老不平安,都不反对了,那么如果我应用 jdk8,并将加密套件强制为 jdk7 中的加密套件呢,应该也是会报错的!
于是,我还是应用 jdk8,并增加了 jvm 参数-Dhttps.cipherSuites=SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_RC4_128_SHA,SSL_RSA_WITH_RC4_128_MD5,SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA,SSL_RSA_WITH_3DES_EDE_CBC_SHA
,以强制 jdk8 应用 jdk7 的加密套件,如下:
再次运行后,果然报错了!所以应该是 jdk8 反对了一些新的加密套件,而苹果服务端只认这些新的加密套件导致的。
于是,我去 jdk 官网,查了一下 jdk8 的新增个性,发现 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
中 AES 加密算法的 GCM 模式在 jdk8 中才反对,如下:
至此,问题起因全副搞清楚了,测试降级 jdk8 后也报告推送失常,此时已是 10 点多,两人写写日报连忙回家去了 ……
往期内容
密码学入门
时区的坑,不想再踩了!
常见的 Socket 网络异样场景剖析
字符编码解惑