作者:懷知
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]
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
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才能實現代理的正常功能。詳情參考文章。
總結
----