1 . 事件描述
2024年3月18日下午,测试突然拉我进了一个群,并且@我,让我帮忙看下测试环境的一个问题。
根据故障上报同事描述:openssl握手失败,根证书已经验证过是一致的。
大概的链路:客户端-->Nginx双向认证代理。
2. 排查过程
说实话,我真的非常不擅长排查这种双向认证的问题,因为不太懂原理,但好在我之前遇到过几次双向认证的问题,虽然最终原因都不一致,但也给我增加了一点底气。
2.1 抓包分析请求过程
这里我先让客户端同事抓了一下请求的包给我,随即我打开了自己之前记录TLS握手笔记,开始回顾TLS握手的过程。
2.2 TLS握手过程
不同的TLS版本过程不同,这里以TLS1.2为例。
2.2.1 单向TLS
首先正常的TCP3次握手过程不变,具体过程这里不讲,下面主讲3次握手后,客户端和服务端的通信过程。
(1)首先客户端会发送一个“Client Hello”包给服务端,这个“Client Hello”包里面包含了客户端的TLS版本和客户端自己能用的加密套件(Cipher Suites)列表,这里的加密套件可以理解为不同的算法组合,以及一个随机数(Random)给服务端。如下图:
(2)当服务端收到这个包后,也会响应一个“Server Hello”包给客户端,在“Server Hello”包里,包含了服务端使用的TLS版本、加密套件(Cipher Suites)和随机数(Random)。
注意这里服务端发的加密套件,是和客户端一样的,后期所有的数据传输,都是用双方协商好的加密套件进行数据加密的。如下图:
(3)紧接着,服务端再发送一个“Certificate”包到客户端,这样浏览器就可以对照自己的证书信任列表来确定服务端是否可信,如下图:
(4)然后,服务端要告诉客户端自己的公钥是什么,所以还会再发一个“Server Key Exchange”包给客户端,这个包里面就包含了服务端的证书,也就是CA机构颁发给我们用的那个HTTPS证书,如下图:
(5)最后,服务端还要发送一个“Server Hello Done”包给客户端,目的是告诉客户端服务端的信息发完了,如下图:
截止目前,上面的通信过程,都还是没有加密的。
(6)到这里,客户端需要根据服务端发送的3个包来处理信息,进行响应,所以会发送一个包含三个信息的包:“Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message”,如下图:
这里首先说下“Client Key Exchange”,客户端会生成第三个随机数,也叫“预主密钥”,这第三个随机数会用刚刚收到的公钥进行加密,并把这个加密后的随机数发送给服务端,也就是下图中Pubkey对应的随机数,如下图:
接下来就是“Change Cipher Spec”,这一步就是告诉服务端,以后就用商议好的算法和密钥来加密通信,如下图:
最后就是“Encrypted Handshake Message”,表示客户端这边的TLS协商已经没有问题了,加密开始,如下图:
(7)服务端也发送一个“Encrypted Handshake Message”包给客户端,表示服务端这边已经准好了,意味着TLS握手成功,可以给数据进行加密交换了。
以上就是一个大概的TSL握手过程。
2.3 客户端TLS握手失败问题分析
回顾完TLS握手过程后,我第一时间看了客户端抓的包,如下图:
可以看到,客户端给服务端发了一个“Client Hello”包后,服务端就直接返回了“Alert”告警,意味着TLS握手第一次就失败了。
第一步一般都是客户端和服务端协商使用的加密套件,紧接着我就查看了客户端的“Client Hello”包内容,如下图:
然后对比了一下nginx代理端的加密套件配置:
最终排查发现是客户端提供的加密套件,没有在服务端的这个加密套件列表中,导致了TLS第一次握手失败。
但是我从来没遇到过,客户端携带的加密算法只有2个的,一般都有很多,最后让客户端自己想办法多加几个去了,此事也就告一段落了。