Server Name Indication(SNI)
SNI (Server Name Indication)是用來改善服務器與客戶端 SSL (Secure Socket Layer)和 TLS (Transport Layer Security) 的一個擴展。主要解決一台服務器只能使用一個證書(一個域名)的缺點,隨着服務器對虛擬主機的支持,一個服務器上可以為多個域名提供服務,因此SNI必須得到支持才能滿足需求。
SNI產生背景
SSL以及TLS(SSL的升級版)為客戶端與服務器端進行安全連接提供了條件。但是,由於當時技術限制,SSL初期的設計順應經典的公鑰基礎設施 PKI(Public Key Infrastructure)設計,PKI 認為一個服務器只為一個域名提供服務,從而一個服務器上也就只能使用一個證書。這樣客戶端在發送請求的時候,利用DNS域名解析,只要向解析到的IP地址(服務器地址)發送請求,然后服務器將自身唯一的證書返回回來,交給客戶端驗證,驗證通過,則繼續進行后續通信。然后通過協商好的加密通道,獲得所需要的內容。這意味着服務器可以在 SSL 的啟動動階段發送或提交證書,因為它知道它在為哪個特定的域名服務。
隨着HTTP 服務器開啟虛擬主機支持后,每個服務器通過相同的IP地址可以為很多域名提供服務。這種為虛擬主機提供通信安全的簡單途徑,卻經常導致使用了錯誤的數字證書,因為服務器端無法知道客戶端到底請求的是哪個域名下的服務,從而導致瀏覽器對用戶發出警告。
不幸的是,當設置了 SSL加密,服務器在讀取HTTP請求里面的域名之前已經向客戶端提交了證書,也就是已經為默認域提供了服務。但是,一個服務器可能為上千個域名提供服務,不可能將所有證書都發送給客戶端,讓客戶端一一驗證,找到與請求域名對應的證書。SNI的設計目的是為了讓服務器根據請求來決定為哪個域服務,這個信息通常從HTTP請求頭獲得。
SSL/TLS握手
熟悉SSL/TLS握手過程的都知道,主要經過以下幾個過程:
基於RSA握手和密鑰交換的客戶端驗證服務器為示例詳解TLS/SSL握手過程。
1 C->S:client_hello
客戶端發起請求,以明文傳輸請求信息,包含版本信息,加密套件候選列表,壓縮算法候選列表,隨機數,擴展字段等信息。
SSL/STL版本支持的最高TSL協議版本version,從低到高依次 SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2,當前基本不再使用低於 TLSv1 的版本;
客戶端支持的加密套件 cipher suites 列表, 每個加密套件對應前面 TLS 原理中的四個功能的組合:認證算法 Au (身份驗證)、密鑰交換算法 KeyExchange(密鑰協商)、對稱加密算法 Enc (信息加密)和信息摘要 Mac(完整性校驗);
支持的壓縮算法 compression methods 列表,用於后續的信息壓縮傳輸;
隨機數 random_C,用於后續的密鑰的生成;
擴展字段 extensions,支持協議與算法的相關參數以及其它輔助信息等,常見的 SNI 就屬於擴展字段,后續單獨討論該字段作用。
2 server_hello+server_certificate+sever_hello_done
server_hello, 服務端返回協商的信息結果,包括選擇使用的協議版本 version,選擇的加密套件 cipher suite,選擇的壓縮算法 compression method、隨機數 random_S 等,其中隨機數用於后續的密鑰協商;
server_certificates, 服務器端配置對應的證書鏈,用於身份驗證與密鑰交換;
server_hello_done,通知客戶端 server_hello 信息發送結束;
3 證書校驗
客戶端驗證證書的合法性,如果驗證通過才會進行后續通信,否則根據錯誤情況不同做出提示和操作,合法性驗證包括如下:
證書鏈的可信性 trusted certificate path,方法如前文所述;
證書是否吊銷 revocation,有兩類方式離線 CRL 與在線 OCSP,不同的客戶端行為會不同;
有效期 expiry date,證書是否在有效時間范圍;
域名 domain,核查證書域名是否與當前的訪問域名匹配,匹配規則后續分析;
4 client_key_exchange+change_cipher_spec+encrypted_handshake_message
client_key_exchange,合法性驗證通過之后,客戶端計算產生隨機數字 Pre-master,並用證書公鑰加密,發送給服務器;
此時客戶端已經獲取全部的計算協商密鑰需要的信息:兩個明文隨機數 random_C 和 random_S 與自己計算產生的 Pre-master,計算得到協商密鑰;
enc_key=Fuc(random_C, random_S, Pre-Master)
change_cipher_spec,客戶端通知服務器后續的通信都采用協商的通信密鑰和加密算法進行加密通信;
encrypted_handshake_message,結合之前所有通信參數的 hash 值與其它相關信息生成一段數據,采用協商密鑰 session secret 與算法進行加密,然后發送給服務器用於數據與握手驗證;
5 change_cipher_spec+encrypted_handshake_message
服務器用私鑰解密加密的 Pre-master 數據,基於之前交換的兩個明文隨機數 random_C 和 random_S,計算得到協商密鑰:enc_key=Fuc(random_C, random_S, Pre-Master);
計算之前所有接收信息的 hash 值,然后解密客戶端發送的 encrypted_handshake_message,驗證數據和密鑰正確性;
change_cipher_spec, 驗證通過之后,服務器同樣發送 change_cipher_spec 以告知客戶端后續的通信都采用協商的密鑰與算法進行加密通信;
encrypted_handshake_message, 服務器也結合所有當前的通信參數信息生成一段數據並采用協商密鑰 session secret 與算法加密並發送到客戶端;
6 握手結束
客戶端計算所有接收信息的 hash 值,並采用協商密鑰解密 encrypted_handshake_message,驗證服務器發送的數據和密鑰,驗證通過則握手完成;
7 加密通信
開始使用協商密鑰與算法進行加密通信。
由以上過程可以知道,沒有SNI的情況下,服務器無法預知客戶端到底請求的是哪一個域名的服務。
SNI 應用
SNI的TLS擴展通過發送虛擬域的名字做為TSL協商的一部分修正了這個問題,在Client Hello階段,通過SNI擴展,將域名信息提前告訴服務器,服務器根據域名取得對應的證書返回給客戶端已完成校驗過程。
curl
Linux中主要的網絡交互工具,curl 7.18.1+ & openssl 0.9.8j+ 可以支持SNI,CentOS6.5及以下都是curl 7.15 不支持SNI,curl 7.21.3 又支持了–resolve 參數,可以直接定位到IP地址進行訪問,對於一個域名有多個部署節點的服務來說,這個參數可以定向的訪問某個設備。基本語法為:
Example:
curl -k -I --resolve www.example.com:80:192.0.2.1 https://www.example.com/index.html
WireShark抓包驗證SNI
使用curl7.15 (不支持SNI)抓包結果:
使用curl7.43(支持SNI)抓包結果:
可以看到,使用curl7.15抓包得到的數據無SNI擴展,而是用curl7.43抓包得到的數據,包含SNI擴展,其中包含host信息。
原文鏈接:https://blog.csdn.net/makenothing/article/details/53292335