赞
踩
TLS/SSL握手是一个相对复杂的过程,在阿里云环境中结合产品,安全等特性,可能会让TLS/SSL握手过程的不定性更多。本文来总结下各种握手失败的场景。
本文不详细介绍TLS/SSL基础知识,相关介绍可以参考文章。下面3张图描述了3种TLS/SSL握手的全过程。
服务器验证的完全握手 (Full Handshake with Mutual Authentication)
这种是互联网大部分HTTPS流量使用的验证模式。证书在服务器上,客户端通过证书来验证服务器是否可靠。
双向验证的完全握手 (Full Handshake with Server Authentication)
这种是对客户端安全性有要求的验证模式。除了客户端要验证服务器外,服务器对客户端也需要进行验证,所以需要双向验证。和上面的步骤相比,多了客户端向服务器传输证书的过程。
简单握手 (Abbreviated Handshake)
完全握手需要2个RTT并交互很多消息,在会话复用的场景下,可以让握手简化到1个RTT完成。过程如下:
自从TLS 1.2版本在2008年发布以来,绝大部分HTTPS流量都跑在TLS 1.2上。服务器处于安全性考虑通常也只支持较高版本TLS,比如TLS1.0及以上。但是仍然有一些版本比较旧的操作系统和浏览器存在,如果这些客户端用低版本TLS/SSL向服务器发起握手,会因为服务器不支持而直接失败。
比如淘宝网只支持TLS 1.0及以上版本,用openssl发起SSL 3版本的握手,就会出现handshake failure。
- # openssl s_client -connect www.taobao.com:443 -ssl3 -msg
- CONNECTED(00000003)
- >>> ??? [length 0005]
- 16 03 00 00 8f
- >>> SSL 3.0 Handshake [length 008f], ClientHello
- 01 00 00 8b 03 00 2a a0 d3 c5 10 b0 0a c0 0b ea
- fc e7 49 8f d1 66 cd 2a 51 c1 ab f4 ab b7 63 e1
- a7 3e e0 d7 14 9b 00 00 64 c0 14 c0 0a 00 39 00
- 38 00 37 00 36 00 88 00 87 00 86 00 85 c0 0f c0
- 05 00 35 00 84 c0 13 c0 09 00 33 00 32 00 31 00
- 30 00 9a 00 99 00 98 00 97 00 45 00 44 00 43 00
- 42 c0 0e c0 04 00 2f 00 96 00 41 c0 12 c0 08 00
- 16 00 13 00 10 00 0d c0 0d c0 03 00 0a 00 07 c0
- 11 c0 07 c0 0c c0 02 00 05 00 04 00 ff 01 00
- <<< ??? [length 0005]
- 15 03 00 00 02
- <<< SSL 3.0 Alert [length 0002], fatal handshake_failure
- 02 28
- 140191222585232:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:s3_pkt.c:1493:SSL alert number 40
- 140191222585232:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
- ---
- no peer certificate available
- ---
- No client certificate CA names sent
在握手的前两个ClientHello和ServerHello包中有一个重要的任务就是协商cipher。客户端在ClientHello中会带上所有支持的cipher suite, 服务器在收到ClientHello中的cipher suite后,会和自己支持的cipher suite一一匹配,如果没有可以匹配的就会握手失败。
服务器出于安全性考虑通常只会支持安全性较高的cipher,所以当客户端发过去的cipher suite安全性都比较低时会造成握手失败。
例如用openssl向淘宝网发起握手,客户端的ClientHello中只有一个安全性较低的DHE-RSA-AES128-SHA256 cipher,会出现handshake failure。
- # openssl s_client -connect www.taobao.com:443 -cipher DHE-RSA-AES128-SHA256 -msg
- CONNECTED(00000003)
- >>> TLS 1.2 [length 0005]
- 16 03 01 00 5e
- >>> TLS 1.2 Handshake [length 005e], ClientHello
- 01 00 00 5a 03 03 4a d3 f5 53 f0 f3 e2 8f a8 a3
- 4a 26 81 91 84 fb fd cf 80 13 21 c6 42 d3 c4 2b
- a7 70 de 4c e0 48 00 00 04 00 67 00 ff 01 00 00
- 2d 00 23 00 00 00 0d 00 20 00 1e 06 01 06 02 06
- 03 05 01 05 02 05 03 04 01 04 02 04 03 03 01 03
- 02 03 03 02 01 02 02 02 03 00 0f 00 01 01
- <<< TLS 1.2 [length 0005]
- 15 03 03 00 02
- <<< TLS 1.2 Alert [length 0002], fatal handshake_failure
- 02 28
- 139737777813392:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769:
- ---
- no peer certificate available
- ---
- No client certificate CA names sent
在握手过程中,客户端对服务器证书会做验证,验证不过时会出现Warning。浏览器可以选择忽略,用curl也可以使用-k参数来忽略。严格来说并不算Failure,这里归类成Warning,不做详细讨论。例如如下几种比较常见的情况:
进入阿里云的流量会经过云盾,类似于其他安全设备,云盾会根据流量特征采取一定动作。
如下是一个例子。客户端访问阿里云的一个公网IP地址TLS/SSL握手失败。先来看下现象,在客户端的抓包如下:
可以看到前面的TCP三次握手和一些数据交互(特定协议相关,正常情况在TCP三次握手后直接开始TLS/SSL握手)都没有问题。但是开始TLS/SSL握手交互过程客户端发出第一个报文,马上收到一个TCP RESET。这个和上面提到的常规握手失败很不一样, TCP RESET报文通常是设备或者主机协议栈主动发出,符合一定场景或者有一定网络管理含义。
云盾根据访问的目的域名有没有备案做执行相关动作。云盾并没有在TCP建连时就针对源目IP做阻断,而是提取ClientHello中的SNI(Servername Indication)域名信息判断是否备案而做阻断,返回TCP RESET。
SNI是ClientHello中的一个扩展字段,带有要访问的目标域名,让同一个IP上托管多个HTTPS站点的服务器知道客户端访问的是哪个目标域名,以便使用对应的证书进行交互。在ClientHello报文的如下位置:
在双向验证的场景中,不仅仅客户端要验证服务器证书,服务器也需要验证客户端证书。在服务器验证客户端证书的过程中,由于客户端证书的安全性较低,可能会直接产生Fatal Alert,导致握手直接中断。
如下是一个手机App访问服务器的例子。72号报文报出了Bad Certificate的Fatal Alert,从上下文看,这里是客户端向服务器端发送完Certificate, Client Key Exchange等消息后,服务器返回给客户端的报错。
在手机App中的报错如下:
SSL handshake aborted: ssl=0x7c1bbf6e88: Failure in SSL library, usually a protocol error
error:10000412:SSL routines:OPENSSL_internal:SSLV3_ALERT_BAD_CERTIFICATE (external/boringssl/src/ssl/tls_record.cc:592 0x7c6c627e48:0x00000001)
在双向认证时,openssl认为客户端证书的安全性过低,中断TLS/SSL握手。
在某些场景中,需要获取ClientHello中的SNI字段来作为一个必要条件, 比如用NGINX stream对HTTPS流量做4层代理时。客户端ClientHello中没有携带SNI,则会造成一个通过代理握手失败的局面。
和上面个握手失败的现象如出一辙,在客户端发出ClientHello后,马上被代理服务器FIN掉,唯一不同的是这里的ClientHello并没有带上SNI字段。
在利用NGINX stream做正向代理时,NGXIN服务器需要获取客户端想要访问的目的域名。利用ngx_stream_ssl_preread_module模块在不解密的情况下拿到ClientHello报文中SNI才能实现代理的正常功能。详情参考文章。
上面总结了很多握手失败的场景,一个有趣的现象是:失败的原因可能各不相同,但是抓包的结果大部分都比较一致,即客户端发的一个TLS/SSL握手包被服务器FIN/RST掉。对于这类问题的排查和分析,抓包分析仅仅只是一个线索,更加关键的是需要理解TLS/SSL握手整个过程中的细节以及当前场景中的网络链路,比如链路中有没有安全设备,代理,有没有使用双向验证,Keyless等等。
原文链接
本文为云栖社区原创内容,未经允许不得转载。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。