banner
niracler

niracler

长门大明神会梦到外星羊么?
github
email
steam_profiles
douban
nintendo switch
tg_channel
twitter_id

優化網站的 TLS 性能:在 Node.js 的 HTTP Server 中通過 OCSP Stapling 解決證書驗證問題

总感觉有更好的办法,就作为抛砖引玉发出来吧。欢迎多多吐槽。

需求#

在國內網路環境下,網站的 TLS 安全證書可能因為 DNS 問題而導致 Online Certificate Status Protocol(OCSP)伺服器無法正常訪問。OCSP 負責實時驗證網路中 SSL/TLS 證書的吊銷狀態。

例如,當我們嘗試通過 curl 命令行工具訪問一個使用 schannel(Windows 原生的 TLS 庫)的網站時,由於證書狀態不能被在線驗證,就可能遭遇類似下面這樣的錯誤:

我此處用的是 digicert 的證書,ocsp 伺服器是 ocsp.digicert.com。在該伺服器被牆或者無法訪問的情況下,導致證書狀態驗證失敗。若是用過 let‘s encrypt 證書的同學,應該更加深受其害吧~~

$ curl -v https://hub.x-cmd.com
*   Trying 47.113.155.92:443...
* Connected to hub.x-cmd.com (47.113.155.92) port 443 (#0)
* schannel: disabled automatic use of client certificate
* ALPN: offers http/1.1
* schannel: next InitializeSecurityContext failed: Unknown error (0x80092013) - 由於吊銷伺服器已脫機,吊銷功能無法檢查吊銷。
* Closing connection 0
* schannel: shutting down SSL/TLS connection with hub.x-cmd.com port 443
curl: (35) schannel: next InitializeSecurityContext failed: Unknown error (0x80092013) - 由於吊銷伺服器已脫機,吊銷功能 無法檢查吊銷。

ps. schannel 版本的 curl 會默認啟動「證書狀態驗證」,而 openssl 版本的 curl 則不會。所以用 openssl 版本的 curl 即使驗證不通過,也是能正常獲取結果的。

解決方案概覽:引入 OCSP Stapling#

OCSP Stapling 是一種優化的在線證書檢查方法,它通過讓網站伺服器代替客戶端提前獲取並「附帶」(staple)OCSP 認證結果,避免了每個客戶端分別查詢 OCSP 伺服器的需要。這不僅減少了握手過程中的延遲,還降低了 OCSP 伺服器的負載。

OCSP Stapling 工作機制#

在傳統模式下,客戶端需要在 TLS 握手過程中與 CA 的 OCSP 伺服器進行額外的回合通訊來確認服務端證書的有效性。OCSP Stapling 通過以下方式簡化了這一過程:

  1. 在 TLS 握手請求時,客戶端請求服務端提供證書狀態。
  2. 服務端定期從 CA 的 OCSP 伺服器獲取簽名的證書狀態響應並緩存。
  3. 當有新的 TLS 握手請求時,服務端將緩存的 OCSP 響應「附帶」在證書後一起發給客戶端。

這種機制避免了 客戶端直接請求 OCSP 伺服器,從而減少了 TLS 握手的延遲。同時,它也減少了 OCSP 伺服器的負載,因為它不再需要為每個客戶端請求提供響應。(也不用怕 ocsp 伺服器被牆了)

下面是這個過程的簡化示意圖:

Mermaid Loading...

下面這個是阿里雲的,可能更好的圖:
可能更好的圖

使用 ocsp 庫開啟 OCSP Stapling#

我們可以使用 ocsp 庫來在 Node.js 伺服器上實現 OCSP Stapling。由於該庫已不再維護,TypeScript 用戶可能需要參考 這個 pr 中的修復。安裝完成後,您可以按以下方式配置伺服器:

在原有的 node server 的基礎上,添加 server 的 OCSPRequest 事件,然後在事件中返回 OSCP 資訊即可。

import ocsp from "ocsp"

// Create cache for OCSP (it'll be used to respond to OCSP stapling requests)
const cache = new ocsp.Cache()

server.on('OCSPRequest', (cert, issuer, cb) => {
    ocsp.getOCSPURI(cert, function (err, uri) {
        if (err) return cb(err, Buffer.alloc(0))
        if (uri === null || uri === undefined) return cb(null, Buffer.alloc(0))

        const req = ocsp.request.generate(cert, issuer)
        cache.probe(req.id.toString(), function (err, cached) {
            if (err) return cb(err, Buffer.alloc(0))
            if (cached !== false) return cb(null, cached.response)

            const options = {
                url: uri,
                ocsp: Buffer.from(req.data)
            }

            cache.request(req.id, options, cb)
        })
    })
})

可能存在的問題#

  1. 伺服器緩存的 OCSP 資訊可能會過期,需要定期更新。此處的 ocsp 庫已經幫我們做了這個事情。
  2. 伺服器與 CA 的 OCSP 伺服器之間的網路可能會出現問題,導致無法獲取到 OCSP 資訊。此時應當退化為客戶端請求 OCSP 伺服器的方式。伺服器方不應當因為無法獲取到 OCSP 資訊而拒絕客戶端的請求。

參考資料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。