証明書の失効確認とCRL・OCSP
証明書の期限と、失効情報の状態・鮮度が別の検査であることを説明します。
説明
証明書は有効期限の途中でも失効することがあります。CRLは発行者が示す失効一覧、OCSPは個々の証明書に対する状態応答です。応答の署名、対象、有効な時刻範囲を確認してから状態を判断します。
同じ試料証明書について、通常の経路検証とCRLを含む検証を比較します。次に署名付きOCSP応答のgood・revoked・unknownと古い応答を調べ、状態不明を有効と決めつけない処理を確認します。
証明書の期限と、失効情報の状態・鮮度が別の検査であることを説明します。
1失効情報を使わず経路を検証する
固定時刻2026-11-13は葉証明書の有効期間内です。まず失効情報を与えない場合を確認します。
from sc08 import *
print(check()署名経路と利用条件を検証する)OKですが、このコマンドには失効確認を指定していません。経路検証が通ったことから、失効していないと結論付けることはできません。
2CRLの発行者と失効番号を確認する
中間CAが署名したCRLを読みます。証明書番号30は11月10日に失効しています。
crl = x509.load_pem_x509_crlCRLを読み込む((HERE/"revoked.crl.pem").read_bytes())
print(crl.issuer.rfc4514_string())
print([(r.serial_number失効した証明書の番号を読む,r.revocation_date_utc.isoformat()) for r in crl])
print(crl.last_update_utc.isoformat(),crl.next_update_utc.isoformat())このCRLの発行者は葉証明書の発行者に対応します。一覧の番号は、その発行者の証明書番号と照合します。
3CRLを指定して検証する
同じ検証へcrl_checkを加えます。CRLの署名と鮮度もOpenSSLが検査します。
print(check(crl=TrueCRLによる失効確認を追加する))certificate revokedとなりました。証明書の有効期限が先でも、失効情報が利用拒否の根拠になります。
4OCSP応答の対象を読む
ここからは別々に作った状態応答を比較します。同一時点に三つの状態が同時に正しいという意味ではありません。
good = response("good")署名付きgood応答を読み込む
print(good.serial_number,good.hash_algorithm.name)
print(good.issuer_name_hash.hex())
print(good.issuer_key_hash発行者公開鍵のハッシュを確認する.hex())
print(good.this_update_utc.isoformat(),good.next_update_utc.isoformat())対象は番号と発行者名・公開鍵のハッシュで特定します。番号30だけで、異なる発行者の証明書と混同しないようにします。
5署名と対象と時刻を検査する
statusは信頼した発行者の公開鍵で署名を確認し、対象証明書との一致と時刻範囲を検査してから状態を返します。
print(status(good)good応答の署名・対象・鮮度を確認する)
print(status(response("revoked"))失効応答を確認する)
print(status(response("unknown"))状態不明の応答を確認する)GOOD、REVOKED、UNKNOWNです。goodはこの応答の失効状態に関する情報で、名前・用途・経路など他の検証を省略する理由にはなりません。
6古いgood応答を提示する
検証時刻をnextUpdateより後へ進めます。署名が正しいことと情報が新しいことは別です。
print(status(good,when="2026-12-02"応答の更新期限後へ時刻を進める))staleです。古いgoodをいつまでも使うと、後から発生した失効を反映できません。
7状態不明と取得失敗を有効へ変換しない
この例の業務方針は、GOODが確認できた場合だけ先へ進むことです。取得失敗をNoneとして同じ表で評価します。
states = [status(good),status(response("revoked")),status(response("unknown")),status(good,"2026-12-02"),None]
print([(s,s == "GOOD"例示した業務方針で受付を判定する) for s in states])この方針ではGOOD以外を受け入れません。利用継続を優先する別の方針を採る場合も、失効なしを確認した状態と、確認できなかった状態を記録上分ける必要があります。
まとめ
証明書の期限内でも、失効情報によって利用を拒否します。
状態応答は署名・対象・鮮度を検証して利用します。
unknownや取得失敗はgoodの同義語ではありません。
操作履歴と解説を振り返れます。
Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻
from sc08 import *
print(check())(0, ['leaf.pem: OK'])
crl = x509.load_pem_x509_crl((HERE/"revoked.crl.pem").read_bytes())
print(crl.issuer.rfc4514_string())
print([(r.serial_number,r.revocation_date_utc.isoformat()) for r in crl])
print(crl.last_update_utc.isoformat(),crl.next_update_utc.isoformat())CN=Lbox intermediate [(30, '2026-11-10T00:00:00+00:00')] 2026-11-11T00:00:00+00:00 2026-12-01T00:00:00+00:00
print(check(crl=True))(2, ['error 23 at 0 depth lookup: certificate revoked'])
good = response("good")
print(good.serial_number,good.hash_algorithm.name)
print(good.issuer_name_hash.hex())
print(good.issuer_key_hash.hex())
print(good.this_update_utc.isoformat(),good.next_update_utc.isoformat())30 sha256 f742cc0df675f568b1e40f6e2b09bfa5cfc253cb7261a802c161a05289db0a96 6a3803d5f059902a1c6dafbc9ba4729212f7caac08634cc3ae76b27529f03827 2026-11-11T00:00:00+00:00 2026-12-01T00:00:00+00:00
print(status(good))
print(status(response("revoked")))
print(status(response("unknown")))GOOD REVOKED UNKNOWN
print(status(good,when="2026-12-02"))stale
states = [status(good),status(response("revoked")),status(response("unknown")),status(good,"2026-12-02"),None]
print([(s,s == "GOOD") for s in states])[('GOOD', True), ('REVOKED', False), ('UNKNOWN', False), ('stale', False), (None, False)]演習完了