なんとなくもっと良い方法がある気がするので、試しに発表してみます。ご意見をお待ちしています。
要件#
国内のネットワーク環境では、ウェブサイトの 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 ステープリングの導入#
OCSP ステープリングは、オンライン証明書チェックの最適化された方法であり、ウェブサイトサーバーがクライアントに代わって事前に OCSP 認証結果を取得し「添付」(ステープル)することで、各クライアントが OCSP サーバーを個別に照会する必要を回避します。これにより、ハンドシェイクプロセス中の遅延が減少し、OCSP サーバーの負荷も軽減されます。
OCSP ステープリングの動作メカニズム#
従来のモードでは、クライアントは TLS ハンドシェイクプロセス中に CA の OCSP サーバーと追加のラウンドトリップ通信を行い、サーバー証明書の有効性を確認する必要があります。OCSP ステープリングは、以下の方法でこのプロセスを簡素化します:
- TLS ハンドシェイクリクエスト時に、クライアントがサーバーに証明書の状態を提供するよう要求します。
- サーバーは定期的に CA の OCSP サーバーから署名された証明書状態応答を取得し、キャッシュします。
- 新しい TLS ハンドシェイクリクエストがあると、サーバーはキャッシュされた OCSP 応答を証明書と一緒にクライアントに「添付」して送信します。
このメカニズムにより、クライアントが直接 OCSP サーバーにリクエストすることを回避し、TLS ハンドシェイクの遅延を減少させます。同時に、OCSP サーバーの負荷も軽減され、各クライアントリクエストに対して応答を提供する必要がなくなります。(ocsp サーバーがブロックされることを心配する必要もありません)
以下はこのプロセスの簡略図です:
以下は阿里云の、より良い図かもしれません:

ocsp ライブラリを使用して OCSP ステープリングを有効にする#
Node.js サーバーで OCSP ステープリングを実装するために ocsp ライブラリを使用できます。このライブラリはもはやメンテナンスされていないため、TypeScript ユーザーは この pr の修正を参照する必要があるかもしれません。インストールが完了したら、次のようにサーバーを構成できます:
既存の node サーバーに基づいて、サーバーの OCSPRequest イベントを追加し、そのイベント内で OSCP 情報を返すだけです。
import ocsp from "ocsp"
// OCSP のキャッシュを作成します(OCSP ステープリングリクエストに応答するために使用されます)
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 ステープリングの実践について - よく書かれた記事で、考え方は古くありませんが、ocsp ライブラリはもはやメンテナンスされていません。
- OCSP を有効にする方法は? - 外国のネットワーク環境が良いため、彼らはこの問題に直面しないのではないかとずっと考えていました。資料は本当に少なく、この関連のライブラリもありません。