more title
tls的客戶端會話恢復與會話票證機制分析
golang fasthttp庫關於會話恢復與會話票證的源碼分析
前言
https握一次手是很艱辛的,計算量很大。所以如果連續兩次短連接通信的話,完全可以
復用上一次的會話。這樣可以壓縮通信,節省計算。
TLS提供了兩個機制來做這個事。分別是
session cache(會話緩存,會話恢復)
session ticket (會話票證)
此二者,除了達到的結果一樣以為,在機制上並沒有什么其他關系。
[classic_tong @ https://www.cnblogs.com/hugetong/p/12192587.html]
結果一致
先來看,成功的恢復了上一次的會話,或者說成功復用的情況是啥樣的。
在server發給client的包里,如下截圖中體現的特征,可以說明,兩端支持了session cache或者session ticket:
Session Cache分析
nginx可以通過如下配置,打開session cache
Syntax: ssl_session_cache off | none | [builtin[:size]] [shared:name:size];
Default:
ssl_session_cache none;
Context: http, server
見:https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
開session cache而不開session ticket的client模擬,這個事用瀏覽器,curl,wget都不是很好模擬。筆者千辛萬苦終於發現openssl
准備了這個場景,構建如下:
openssl s_client -connect test1.www.local:443 --reconnect -no_ticket -CAfile ~/Keys/https/root/root.cer
主要是reconnect參數,他會連發5次,專門用來測session cache。
協議細節
這里有個關鍵信息,叫session id,用來代表tls會話。分別位於client hello與server hello里。二者相同時,說明通過協商恢復了舊的session。
二者不同時,server hello中ID,作為本次會話的代表。(如下圖,是我從實驗中的首次TLS會話的serverhello)
當client渴望恢復會話時,它會把本地保存的上次的會話ID在 client hello中發給server。(如下圖,為緊接着上圖的第二次TLS會話)
當server收到了session id非空的clienthello時,會知道client希望進行會話恢復。如果他在本地找到了這個會話,便會用相同的ID進行
serverhello回復,否則便生成新的會話,自然也使用新的session id。(如下圖,是成功進行了會話恢復的情況)
[classic_tong @ https://www.cnblogs.com/hugetong/p/12192587.html]
Session Ticket分析
session ticket相比於前者,是一種新的會話恢復機制。它的思想在於服務器去處它的所有會話數據(狀態)並進行加密,再以票證的方式發回
客戶端。在接下來的連接中,客戶端將票證提交回服務器,由服務器檢查票證的完整性,解密其內容,再使用其中的信息恢復會哈。這種方式
有可能使擴展服務器集群更為簡單,因為如果不使用這種方式,就需要在服務集群的各個節點之間同步會話。(摘自<https權威指南>)
不過,需要額外提及的是。session ticket的引入,破壞了TLS的安全模型。(略)
配置
nginx的配置方法。默認就是開的,其實不用配
Syntax: ssl_session_tickets on | off; Default: ssl_session_tickets on; Context: http, server This directive appeared in version 1.5.9.
client,我用的是筆者fork的一個github項目,gobench:https://github.com/tony-caotong/gobench
它背后使用的是golang的fasthttp庫,后面會祥述。https://github.com/valyala/fasthttp
協議細節
RFC:https://tools.ietf.org/html/rfc5077
client
當client支持session ticket的時候,在client hello中會包含一個叫做session_ticket的擴展字段。當本地沒有需要恢復的ticket,但是渴望與server
達成一致對ticket進行支持時,如圖。
當本地,有需要恢復的ticket時,session_ticket 字段會存這需要恢復的ticket。如圖:
當client不支持session ticket時,client hello中,將不包括session ticket數據段。
server
server收到ticket的請求后,如果是一個空的ticket字段。server也會回復一個空的session ticket字段,表明它
即將發起一個新的session ticket握手,並隨后發送NewSessionTIcket報文。
about session id
ticker啟用的前提下,client會在client hello里會帶上一個session id。如果serverhello回復了同樣的session
id,說明server接受了這個ticket,並且同樣恢復。如果serverhello回復了一個新的session id。說明server
拒絕了client的ticket,並將發起一個新的ticket。
也是就是說,成功恢復的鏈接,client與server的兩個session id相同。
Server是怎么解密的呢
前文的rfc5077第一段就講了。Server用一個只有他知道的key,把server的state(以及key?)加密了作為ticket的內容發給client。
client其實也解不開,也不知道里邊是啥。它只能原樣發回來,server用它自己的key解密。這樣他就不用為每個鏈接保存state了,
有點類似於tcp的那個ticket。見多了就發現了,思路都是一樣的。
見nginx的配置文件,可以更好的理解:
https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_ticket_key
他可以顯示的配置一個key,這樣的話,多個server就可以共享ticket了。或者不配置就使用一個隨機生成的key。
more
關於gobench,關於fasthttp,關於golang tls
之所以分析了以上內容與機制,主要是因為fasthttp(golang tls)的實現中,將ticket與cache的耦合在了一起。
golang的api不能配置出一種場景:ticket關,但是cache開。(也可能不是這一點,是我不會用,其實我幾乎不會golang)所幸用上文的openssl命令可以彌補。
當關閉ticket的時候,cache功能也失效了。
主要因為在這個函數里:crypto/tls/handshake_client.go::loadSession(),有這樣的一行判斷。
[classic_tong @ https://www.cnblogs.com/hugetong/p/12192587.html]