TLS/SSL握手是一个相对复杂的过程,在阿里云环境中结合产品,安全等特性,可能会让TLS/SSL握手过程的不定性更多。本文来总结下各类握手失败的场景。浏览器
本文不详细介绍TLS/SSL基础知识,相关介绍能够参考文章。下面3张图描述了3种TLS/SSL握手的全过程。安全
服务器验证的彻底握手 (Full Handshake with Mutual Authentication)
这种是互联网大部分HTTPS流量使用的验证模式。证书在服务器上,客户端经过证书来验证服务器是否可靠。服务器
双向验证的彻底握手 (Full Handshake with Server Authentication)网络
这种是对客户端安全性有要求的验证模式。除了客户端要验证服务器外,服务器对客户端也须要进行验证,因此须要双向验证。和上面的步骤相比,多了客户端向服务器传输证书的过程。less
简单握手 (Abbreviated Handshake)curl
彻底握手须要2个RTT并交互不少消息,在会话复用的场景下,可让握手简化到1个RTT完成。过程以下:ui
自从TLS 1.2版本在2008年发布以来,绝大部分HTTPS流量都跑在TLS 1.2上。服务器处于安全性考虑一般也只支持较高版本TLS,好比TLS1.0及以上。可是仍然有一些版本比较旧的操做系统和浏览器存在,若是这些客户端用低版本TLS/SSL向服务器发起握手,会由于服务器不支持而直接失败。阿里云
好比淘宝网只支持TLS 1.0及以上版本,用openssl发起SSL 3版本的握手,就会出现handshake failure。url
# 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一一匹配,若是没有能够匹配的就会握手失败。spa
服务器出于安全性考虑一般只会支持安全性较高的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等等。
本文为云栖社区原创内容,未经容许不得转载。