Server Name Indication (SNI) 原理簡介
如果你有接觸過 web server ,例如 apache 或是 nginx 或是 IIS,有個名詞叫做 virtual host 。意思是你可以在一台 web server 上面裝多個網站,可以讓 a.com
跟 b.com
對應到不同的處理邏輯。
HTTP headers 裡面有個欄位叫做 Host ,會帶有 client 想要訪問的網站域名。 Server 可以根據這個訊息來判斷 client 倒底想訪問 a.com
還是 b.com
。
HTTPS 造成的問題
HTTPS 把這件事變得有點麻煩。在 TCP 連線建立完成後,接著進行的是 TLS handshake ,這時候 Server 會需要回應一張證書給 client 。如果今天一個網站有一張以上的證書,事情就變得很麻煩,我到底要給哪一張。
這時候沒辦法看 Host header ,原因是 TLS 會發生在 HTTP headers 訊息傳送之前。除非你可以預知未來,不然無法偷看到 Host header 。
因此 SNI 要解決的問題,就是 server 不知道要給哪一張 certificate 的問題。
SNI extension
SNI 是在 TLS handshake 的 client hello 規格部分加一個額外的欄位,裡面放的是 client 想訪問的域名。如此一來 server 就知道要回應哪一張證書了。
為什麼不把域名全部放在同一張證書就好
這個其實也是可以的。SAN (Subject Alternate Name) 可以放多個域名或是 ip,但會有維護上的問題。假設未來多了一個網站,就要更新一次證書,這也是很繁瑣的。
這樣還安全嗎
答案是,看情況。TLS handshake 做完之後才會加密,這等於說你要訪問哪個站其實是可以被中間的網路節點看到的。因為 SNI 露在外面,有些國家網路政策可以利用這個資訊,在網路中間網路節點作怪,故意不給你訪問某些網站。
SNI 跟 Host header 可不可以不同?
理論上可以,但有機率會被 CDN 當作非法的使用者而被擋下來,例如 Amazon CloudFront 就會擋下來。
這種情形稱作 domain fronting 。
用 openssl 指令做個實驗
openssl s_client -connect cube.lichi-chen.com:443 -servername cube.lichi-chen.com
這個指令會建立 TLS handshake 。建立好了我們就接著使用 openssl 傳送 HTTP 的訊息。
GET / HTTP/1.1
Host: cube.lichi-chen.com
記得最後一行要多加個換行才會送出喔。這時候會拿到成功的回應。
故意把 SNI 跟 Host header 弄成不同
舉例來說,如果 CloudFront (AWS 旗下的 CDN 服務) 認出這個 host 跟你的 SNI 所屬的網站是不同的屬於不同 AWS 帳戶下的資源。 CloudFront 會給你 421 錯誤訊息。
GET / HTTP/1.1
Host: www.alexa.comHTTP/1.1 421 Misdirected Request
Server: CloudFront
Date: Sat, 08 Aug 2020 10:12:53 GMT
Content-Type: text/html
Content-Length: 187
Connection: keep-alive
X-Cache: Error from cloudfront
Via: 1.1 839de761badea2aa0a28c5970b81514d.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: NRT12-C4
X-Amz-Cf-Id: G0WiLPYz2LD0zZ57GcN6Vl8Ym86z4HuOrd8QeOThBIVba-Gifywtxg==<html>
<head><title>421 Misdirected Request</title></head>
<body bgcolor="white">
<center><h1>421 Misdirected Request</h1></center>
<hr><center>CloudFront</center>
</body>
</html>