提高TLS 性能30%?談談在 Node.JS 上的 OSCP Stapling 實踐

做者:鞠朕
野狗科技後端工程師
野狗官博: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

OCSP Stapling的前因後果

咱們知道HTTPS站點基於符合PKI(public key infrastructure )的X.509證書證實本身的身份合法性,但由於信息變動,證書私鑰泄露等緣由,證書可能在過時以前就被撤銷。那麼訪問HTTPS站點的客戶端如何判斷服務端下發的證書是否已被撤銷呢?這個問題的解決之路上前後出現了三個技術方案:CRLs、OCSP、 OCSP Stapling。github

CRLs

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,默認證書未被撤銷。

OCSP

因爲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 Stapling

與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)。

  • 不是全部的瀏覽器都支持。

OCSP Stpling在Node.JS中的實現

咱們使用了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開發者略盡綿薄之力。感謝開源社區的力量!

相關文章
相關標籤/搜索