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等等。
本文爲雲棲社區原創內容,未經容許不得轉載。