总感觉有更好的办法,就作为抛砖引玉发出来吧。欢迎多多吐槽。
需求#
在國內網路環境下,網站的 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 通過以下方式簡化了這一過程:
- 在 TLS 握手請求時,客戶端請求服務端提供證書狀態。
- 服務端定期從 CA 的 OCSP 伺服器獲取簽名的證書狀態響應並緩存。
- 當有新的 TLS 握手請求時,服務端將緩存的 OCSP 響應「附帶」在證書後一起發給客戶端。
這種機制避免了 客戶端直接請求 OCSP 伺服器,從而減少了 TLS 握手的延遲。同時,它也減少了 OCSP 伺服器的負載,因為它不再需要為每個客戶端請求提供響應。(也不用怕 ocsp 伺服器被牆了)
下面是這個過程的簡化示意圖:
下面這個是阿里雲的,可能更好的圖:
使用 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)
})
})
})
可能存在的問題#
- 伺服器緩存的 OCSP 資訊可能會過期,需要定期更新。此處的 ocsp 庫已經幫我們做了這個事情。
- 伺服器與 CA 的 OCSP 伺服器之間的網路可能會出現問題,導致無法獲取到 OCSP 資訊。此時應當退化為客戶端請求 OCSP 伺服器的方式。伺服器方不應當因為無法獲取到 OCSP 資訊而拒絕客戶端的請求。
參考資料#
- 提升 TLS 性能 30%?談談在 Node.JS 上的 OSCP Stapling 實踐 - 寫得很好的一篇文章,思想是沒有過時的,但是 ocsp 這個庫已經不維護了。
- How to enable ocsp? - 我一直在想,是因為外國人的網路環境比較好,所以他們不會遇到這個問題嗎?資料是真的少,又沒有這個相關的庫。