博客 / 詳情

返回

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

作者:鞠朕
野狗科技後端工程師
野狗官博:https://blog.wilddog.com/
野狗官網:https://www.wilddog.com/
公眾訂閲號:wilddogbaas

圖片描述

根據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,我們在研究的過程中踩過一些坑,也積累了一些經驗,在此分享給大家。

OCSP Stapling的來龍去脈

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

CRLs

CRLs(certificate revocation lists)也就是證書撤銷列表。簽發證書的CA機構發佈並維護着CRLs,並對CRLs簽名以防止篡改。當瀏覽器訪問一個HTTPS站點時,如果選擇用CRLs的方式,那麼瀏覽器會從服務器下發的證書中取得CRLs的URI,下載CRLs,查詢其中是否包含待檢驗證書的序列號。如果包含,就代表此證書已被撤銷。其工作原理如下圖所示:

圖片描述

這種方式存在一些不足:

  • 增加了HTTPS建連時間,因為瀏覽器要去下載CRLs。

  • CA維護的CRLs文件會越來越大,因為會不斷把撤銷的證書信息加入。

  • 解析CRLs並查詢,如果CRLs文件很大的話,開銷很大。

  • 時效性差,CRLs更新週期一般從5天到14天不等。

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

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.