[tls][https][nginx] https的client session cache与session ticket机制分析
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]