HTTPS那些協(xié)議:TLS, SSL, SNI, ALPN, NPN
如今 HTTPS 已經(jīng)普遍應(yīng)用了,在帶來(lái)安全性的同時(shí)也確實(shí)給 Web 引入了更多復(fù)雜的概念。這其中就包括一系列從沒(méi)見(jiàn)過(guò)的網(wǎng)絡(luò)協(xié)議?,F(xiàn)在 Harttle 從 HTTPS 的原理出發(fā),嘗試以最通俗的方式來(lái)解讀 HTTPS 涉及的這些協(xié)議。
HTTPS 概要
HTTPS 是建立在安全通信之上的 HTTP,使用傳輸層加密(TLS 或 SSL)的手段。其目的是保護(hù)用戶隱私(比如防止經(jīng)過(guò)的網(wǎng)絡(luò)節(jié)點(diǎn)截獲 HTTP 內(nèi)容)和數(shù)據(jù)完整性(比如運(yùn)營(yíng)商強(qiáng)插廣告),就是端到端加密來(lái)防止中間人攻擊。
TLS/SSL 是在傳輸層之上應(yīng)用層之下的協(xié)議,因此 HTTP 協(xié)議的內(nèi)容不受影響。這些加密采用非對(duì)稱加密算法因此需要一個(gè)官方來(lái)發(fā)布公鑰,這就是 密鑰基礎(chǔ)設(shè)施(CA)。因此各瀏覽器會(huì)內(nèi)置一些 CA 的根證書,這些 CA 可以進(jìn)一步授權(quán)其他的域名,這樣你的瀏覽器就可以對(duì)正在訪問(wèn)的域名進(jìn)行身份認(rèn)證。
如果你要自己的服務(wù)也支持 HTTPS 去 CA 注冊(cè)自己的域名就可以了。有一些免費(fèi)的 CA 比如 GoDaddy, Let’s Encrypt, CloudFlare 等可以選擇。
HTTPS 交互示例
以下 Wireshark 日志記錄了一個(gè)發(fā)往 https://github.com/harttle 的 GET 請(qǐng)求,可以看到主要的幾個(gè)協(xié)議的交互過(guò)程:
- TCP。前三行完成一對(duì) SYN/ACK(即俗稱的三次握手),至此 TCP 連接已經(jīng)成功建立。
- TLS。4-5 行開(kāi)始了 TLS 握手,建立這個(gè)加密層。
- TLS 有眾多擴(kuò)展協(xié)議比如 SNI,NPN,ALPN 等(見(jiàn)下文),就發(fā)生在 TLS 的 ClientHello 和 ServerHello 階段。

TLS/SSL
TLS 的前身是 SSL,TCP/IP 協(xié)議棧中運(yùn)行在 TCP 之上,用來(lái)交換密鑰并形成一個(gè)加密層(Record Layer)。 TLS 是 HTTPS 的核心協(xié)議,HTTPS 交互與 HTTP 交互的主要區(qū)別就在這一層:

開(kāi)始傳輸密文前需要進(jìn)行互換密鑰、驗(yàn)證服務(wù)器證書等準(zhǔn)備工作,因此 TLS 也存在握手階段,主要步驟為:客戶端發(fā)送 ClientHello,服務(wù)器發(fā)送 ServerHello,服務(wù)器繼續(xù)發(fā)送 Certificate,然后互相發(fā)送 KeyExchange 消息,最后發(fā)送 ChangeCipherSpec 來(lái)通知對(duì)方后續(xù)都是密文。具體交互和協(xié)議字段請(qǐng)參考 RFC 5246(TLSv1.2)和 RFC 6176(TLSv2.0)。
TLS 作為 TCP/IP 協(xié)議棧中的加密協(xié)議有廣泛的用途,為支持通用機(jī)制的協(xié)議擴(kuò)展,定義了 RFC 4366 - TLS Extensions。 TLS 先后被郵件服務(wù)、Web 服務(wù)、FTP 等采用,這里有一個(gè) 擴(kuò)展協(xié)議列表。
本文關(guān)注其中 Web 服務(wù)(HTTPS)相關(guān)的擴(kuò)展,如 SNI, NPN, ALPN。這些協(xié)議通過(guò)擴(kuò)展 TLS 的 ClientHello/ServerHello 消息為 TLS 增加新的功能。為此我們先看一下 ClientHello 消息的結(jié)構(gòu)(ServerHello 類似):
- struct {
- ProtocolVersion client_version;
- Random random;
- SessionID session_id;
- CipherSuite cipher_suites<2..2^16-2>;
- CompressionMethod compression_methods<1..2^8-1>;
- select (extensions_present) {
- case false:
- struct {};
- case true:
- Extension extensions<0..2^16-1>;
- };
- } ClientHello;
注意最后一個(gè)字段,最多可以有 65536 個(gè) Extension,其中 Extension 定義為一個(gè)兩字節(jié)的 ExtensionType 以及對(duì)應(yīng)的不透明數(shù)據(jù)。下文的 SNI,NPN,ALPN 都是其中之一。
SNI
SNI(Server Name Indication)指定了 TLS 握手時(shí)要連接的 主機(jī)名。 SNI 協(xié)議是為了支持同一個(gè) IP(和端口)支持多個(gè)域名。
因?yàn)樵?TLS 握手期間服務(wù)器需要發(fā)送證書(Certificate)給客戶端,為此需要知道客戶請(qǐng)求的域名(因?yàn)椴煌蛎淖C書可能是不一樣的)。這時(shí)有同學(xué)要問(wèn)了,要連接的主機(jī)名不就是發(fā)起 HTTP 時(shí)的 Host 么!這是對(duì) HTTPS 機(jī)制的誤解,TLS Handshake 發(fā)生時(shí) HTTP 交互還沒(méi)開(kāi)始,自然 HTTP 頭部還沒(méi)到達(dá)服務(wù)器。SNI 協(xié)議就定義在 RFC 6066 中:
- struct {
- NameType name_type;
- select (name_type) {
- case host_name: HostName;
- } name;
- } ServerName;
- enum {
- host_name(0), (255)
- } NameType;
- opaque HostName<1..2^16-1>;
- struct {
- ServerName server_name_list<1..2^16-1>
- } ServerNameList;
我們看本文剛開(kāi)始的例子,第4行發(fā)往 github.com 的 ClientHello 中的 SNI Extension 字段:
- Extension Header || Extension Payload (SNI)
- ---------------------------------------------------------------------------------------------------
- ExtensionType Length || PayloadLength Type ServerLength ServerName
- ---------------------------------------------------------------------------------------------------
- 00 00 00 0f 00 0d 00 00 0a 67 69 74 68 75 62 2e 63 6f 6d
- sni(0) 15 || 13 host_name 10 github.com
ALPN/NPN
ALPN(Application-Layer Protocol Negotiation)也是 TLS 層的擴(kuò)展,用于協(xié)商應(yīng)用層使用的協(xié)議。它的前身是 NPN,最初用于支持 Google SPDY 協(xié)議(現(xiàn)已標(biāo)準(zhǔn)化為 HTTP/2)。 TLS 客戶端和服務(wù)器版本的問(wèn)題,導(dǎo)致 SPDY->HTTP/2 和 NPN -> ALPN 的切換過(guò)程引發(fā)了不少陣痛:
- The day Google Chrome disables HTTP/2
- 從啟用 HTTP/2 導(dǎo)致網(wǎng)站無(wú)法訪問(wèn)說(shuō)起
因此 以標(biāo)準(zhǔn)先行的方式來(lái)推進(jìn) Web 基礎(chǔ)設(shè)施 已成為今日 Web 平臺(tái)的共識(shí)。這里我們不提那些仍然在進(jìn)行作坊式生產(chǎn)的(類)瀏覽器廠商,任何阻擋 Web 平臺(tái)發(fā)展的實(shí)現(xiàn)(甚至標(biāo)準(zhǔn),試看 XHTML, OSI…)遲早會(huì)被淘汰。
言歸正傳,ALPN 定義在 RFC 7301 - Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension,
- enum {
- application_layer_protocol_negotiation(16), (65535)
- } ExtensionType;
- opaque ProtocolName<1..2^8-1>;
- struct {
- ProtocolName protocol_name_list<2..2^16-1>
- } ProtocolNameList;
我們看本文剛開(kāi)始的例子,第4行發(fā)往 github.com 的 ClientHello 中的 ALPN Extension 字段:
- Extension Header || Extention Payload (ALPN)
- ---------------------------------------------------------------------------------------------------
- ExtensionType Length || PayloadLength StringLength Protocol StringLength Protocol
- ---------------------------------------------------------------------------------------------------
- 00 10 00 0e 00 0c 02 68 32 08 68 74 74 70 2f 31 2e 31
- alpn(16) 14 || 12 2 h2 8 http/1.1
Extention 的消息體包含多個(gè)字符串(protocol_name_list),表示客戶端支持的所有應(yīng)用層協(xié)議。上面的例子中有 h2 和 http/1.1 兩個(gè),支持 SPDY 的客戶端這里會(huì)多一個(gè) spdy/2。服務(wù)器給出的 ServerHello 中需要選擇其中之一,本文的例子中 ServerHello 的 ALPN 字段為:
- 00 10 00 0b 00 09 08 68 74 74 70 2f 31 2e 31
- h t t p / 1 . 1
這樣 Server 和 Client 就利用 ALPN 協(xié)議達(dá)成了共識(shí),將會(huì)在握手結(jié)束后使用 HTTP/1.1 協(xié)議進(jìn)行通信。
參考和致謝
從 HTTPS 的關(guān)鍵一層 TLS 開(kāi)始,介紹了一個(gè)典型的 HTTPS 交互過(guò)程。結(jié)合抓包給出的字節(jié)序列,依次介紹了 TLS、SNI、ALPN 等協(xié)議原理和主要內(nèi)容。