做者:鞠朕
野狗科技后端工程师
野狗官博:https://blog.wilddog.com/
野狗官网:https://www.wilddog.com/
公众订阅号:wilddogbaasnode
根据CloudFlare公司的测试报告,OCSP Stapling能提高TLS性能达30%。目前主流的web server都已支持OCSP Stapling,如Apache( 2.3.3 及以上),Nginx(1.3.7及以上), IIS(Win2008及以上)。当使用这些web server做为反向代理的时候,只须要简单的进行配置就能够实现OCSP Stapling。然而野狗的websocket服务使用node.js实现,前边并无反向代理。目前国内几乎尚未人在node.js上实现过OCSP Stapling,咱们在研究的过程当中踩过一些坑,也积累了一些经验,在此分享给你们。git
咱们知道HTTPS站点基于符合PKI(public key infrastructure )的X.509证书证实本身的身份合法性,但由于信息变动,证书私钥泄露等缘由,证书可能在过时以前就被撤销。那么访问HTTPS站点的客户端如何判断服务端下发的证书是否已被撤销呢?这个问题的解决之路上前后出现了三个技术方案:CRLs、OCSP、 OCSP Stapling。github
CRLs(certificate revocation lists)也就是证书撤销列表。签发证书的CA机构发布并维护着CRLs,并对CRLs签名以防止篡改。当浏览器访问一个HTTPS站点时,若是选择用CRLs的方式,那么浏览器会从服务器下发的证书中取得CRLs的URI,下载CRLs,查询其中是否包含待检验证书的序列号。若是包含,就表明此证书已被撤销。其工做原理以下图所示:web
这种方式存在一些不足:后端
增长了HTTPS建连时间,由于浏览器要去下载CRLs。浏览器
CA维护的CRLs文件会愈来愈大,由于会不断把撤销的证书信息加入。缓存
解析CRLs并查询,若是CRLs文件很大的话,开销很大。服务器
时效性差,CRLs更新周期通常从5天到14天不等。websocket
CRLs只支持EV证书,不支持OV和DV证书。网络
若是因为网络等缘由致使客户端没法成功下载CRLs,默认证书未被撤销。
因为CRLs的缺点,在线证书状态协议OCSP(Online Certificate Status Protocol, RFC 6960)应运而生。当浏览器尝试访问一个HTTPS站点时,浏览器从证书中提取OCSP server(CA专门用来处理ocsp请求的服务器,也称OCSP Responser)的URI,向该OCSP server发送一个携带证书的序列号的请求,OCSP server则返回带有目标证书状态的响应。证书状态有三种:good、revoked、unknown。浏览器就能获知证书状态并采起后续动做了。另外,OCSP server的响应也会被签名,以防止被篡改。
OCSP实现了实时查询,而且须要较小的网络带宽,客户端的解析开销也比CRLs小不少。可是它存在如下问题:
每一个客户端都会独立针对证书发送一个OCSP请求,OCSP server 负载大。
侵犯客户端隐私,OCSP server得知用户访问了哪些站点。
只支持EV证书,不支持OV和DV证书。
与OCSP方式中由客户端向OCSP server发起请求不一样,OCSP Stapling是由web服务器向OCSP server周期性地查询证书状态,得到一个带有时间戳和签名的OCSP response并缓存它。当有客户端发起链接请求时,web服务器会把这个response在TLS握手过程当中发给客户端。因为有签名的存在,web服务器没法篡改,所以客户端就能得知证书是否已被撤销了。
OCSP Stapling把客户端的查询压力转移到本身身上,访问站点的信息不会泄漏给OCSP server,从而隐私获得了保护。同时因为web server会进行response的缓存,从而减轻了OCSP server的压力。
OCSP stapling把客户端的查询压力转移到本身身上,访问站点的信息不会泄漏给OCSP server,从而隐私获得了保护。同时因为web server会进行response的缓存,从而减轻了OCSP server的压力。
OCSP Stapling存在的问题是:
一次只能发送一个OCSP response,不支持证书链(注:Multiple Certificate Status Request Extension, RFC 6961 解决了这个问题,一次能够发送多个response)。
不是全部的浏览器都支持。
咱们使用了Node.JS主力开发人员Fedor Indutny贡献的开源项目:ocsp(https://github.com/indutny/ocsp)。使用开源项目中提供的cache.js,编写代码以下:
server.on('OCSPRequest', function(cert, issuer, cb) { ocsp.getOCSPURI(cert, function(err, uri) { if (err) return cb(err); var req = ocsp.request.generate(cert, issuer); var options = { url: uri, ocsp: req.data }; cache.request(req.id, options, cb); }); });
要测试OCSP Stapling的效果,咱们推荐使用openssl命令行工具:
openssl s_client -connect example.org:443 -status(example.org是待测试域名),输出信息包括下图中信息:
咱们看到图中包含了OCSP response信息,而且OCSP Response Status是successful。
也可使用www.ssllabs.com进行评测,结果以下:
下面咱们用wireshark抓一下数据包,看看ocsp stapling的相关网络交互行为,其中服务端ip为10.18.6.21,客户端ip为:10.18.6.35,OCSP server ip为:182.50.136.239。
上图是客户端链接服务端时,服务端与OCSP server之间的数据包往来:服务端向OCSP server发起了OCSP request请求,并收到了OCSP response,response状态为successful。
上图是在客户端抓取的与服务端之间的数据包,能够看到服务端经过TLS向客户端发送了证书状态。
若是服务端因为种种缘由没法链接到OCSP server呢?咱们进行了一个测试,修改服务器上的hosts文件,将OCSP server的域名绑定到本地,这样服务端就不能完成OCSP Stapling了。
由上面两图所示,wireshark抓不到服务端与OCSP server之间的数据包了,也就是没进行OCSP Stapling,浏览器也连不上服务端了。这是不正确的,服务端链接OCSP server失败没法得到OCSP response时,应该把验证证书状态的工做转移给客户端进行,而不该直接致使链接握手失败。
官方给的文档和案例中并无处理这个问题,咱们这里给出一个解决方案,已经过咱们的测试和验证。忽略获取OCSP response的失败,退化为由客户端进行证书撤销状态的验证,修改代码以下:
server.on('OCSPRequest', function(cert, issuer, cb) { ocsp.getOCSPURI(cert, function(err, uri) { if (err) return cb(err); var req = ocsp.request.generate(cert, issuer); var options = { url: uri, ocsp: req.data } cache.request(req.id, options, function(err,response) { if (err) { /* ignore err */ cb(); return; } cb(null, response); }); }); });
在客户端抓包结果以下:
咱们看到客户端本身进行了OCSP查询,获得responseStatus为successful,并与服务端成功创建了链接。
咱们在测试的过程当中发现了另外一个问题:服务器端会频繁访问OCSP server,也就是说OCSP response并未正确地使用缓存。咱们作了以下的改进:
server.on('OCSPRequest', function(cert, issuer, cb) { ocsp.getOCSPURI(cert, function(err, uri) { if (err) return cb(err); var req = ocsp.request.generate(cert, issuer); var options = { url: uri, ocsp: req.data }; if (cache.cache.hasOwnProperty(req.id)) { return cb(null, cache.cache[req.id].response); } else { cache.request(req.id, options, function(err,response) { if (err) { /* ignore err */ cb(); return; } cb(null, response); }); } }); });
判断cache中是否包含证书的response,若是有,直接将response返回给客户端;若是缓存周期到了,旧的OCSP response会从缓存中删除,新的客户端请求到来时,就会走cache.request()查询新的OCSP response并缓存。
咱们对改进后的代码进行了测试。开源项目中定义缓存更新时间为36小时。为了加快测试速度,咱们修改更新时间为2分钟,测试发现缓存未被清除,而且程序抛出异常,以下图所示:
定位异常代码进行debug,咱们发现异常致使缓存的OCSP response不能被删除,返回给客户端的回应还是过时的OCSP response。咱们对代码作了以下修改:
上图是咱们在github上提交的Pull requests。通过测试,OCSP Stapling正常工做了。
在开发的过程当中,咱们遇到了许多问题,屡次和原做者Fedor Indutny沟通都获得了迅速的响应和支持。咱们也fix了一些问题并提交了patch,但愿能为广大node.js开发者略尽绵薄之力。感谢开源社区的力量!