作者:懷知
TLS/SSL握手是一個相對複雜的過程,在阿裡雲環境中結合産品,安全等特性,可能會讓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/SSL握手失敗
TLS/SSL版本不比對
自從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```
TLS/SSL cipher suite不比對
----
在握手的前兩個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]TLS 1.2 Handshake [length 005e], ClientHello16 03 01 00 5e
<<< TLS 1.2 [length 0005]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 Alert [length 0002], fatal handshake_failure15 03 03 00 02
139737777813392:error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure:s23_clnt.c:769:02 28
no peer certificate available
No client certificate CA names sent
TLS/SSL握手Warning
----
在握手過程中,用戶端對伺服器證書會做驗證,驗證不過時會出現Warning。浏覽器可以選擇忽略,用curl也可以使用-k參數來忽略。嚴格來說并不算Failure,這裡歸類成Warning,不做詳細讨論。例如如下幾種比較常見的情況:
- 通路的域名不在伺服器證書的CN(Common Name)和SAN(Subject Alternative Name)中。
- 伺服器證書被吊銷,導緻驗證不通過。
- 由于本地系統時間不準,導緻驗證證書有效期時出現誤判。
雲盾導緻TLS/SSL握手失敗
----
進入阿裡雲的流量會經過雲盾,類似于其他安全裝置,雲盾會根據流量特征采取一定動作。
現象
----
如下是一個例子。用戶端通路阿裡雲的一個公網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封包的如下位置:

用戶端證書問題導緻TLS/SSL握手失敗
----
在雙向驗證的場景中,不僅僅用戶端要驗證伺服器證書,伺服器也需要驗證用戶端證書。在伺服器驗證用戶端證書的過程中,由于用戶端證書的安全性較低,可能會直接産生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握手。
無法提取SNI導緻TLS/SSL握手失敗
----
在某些場景中,需要擷取ClientHello中的SNI字段來作為一個必要條件, 比如用NGINX stream對HTTPS流量做4層代理時。用戶端ClientHello中沒有攜帶SNI,則會造成一個通過代理握手失敗的局面。
現象
----
和上面個握手失敗的現象如出一轍,在用戶端發出ClientHello後,馬上被代理伺服器FIN掉,唯一不同的是這裡的ClientHello并沒有帶上SNI字段。

根本原因
----
在利用NGINX stream做正向代理時,NGXIN伺服器需要擷取用戶端想要通路的目的域名。利用ngx_stream_ssl_preread_module子產品在不解密的情況下拿到ClientHello封包中SNI才能實作代理的正常功能。詳情參考文章。
總結
----