サーバー証明書の検証
署名が正しい証明書を利用できない条件を、証明書の各フィールドと対応付けます。
説明
正しいCAが発行した証明書でも、接続先の名前、利用時刻、用途が合っている必要があります。TLSサーバー証明書を、別ホストやクライアント認証へそのまま流用できるとは限りません。
一つの証明書のSAN、有効期間、拡張鍵用途を調べます。その証明書を変えずに、検証する接続先・時刻・用途を一つずつ変えてOpenSSLの結果を比較します。
署名が正しい証明書を利用できない条件を、証明書の各フィールドと対応付けます。
1SANに記載された接続先を読む
SANはSubject Alternative Nameです。この証明書にはapi.example.testというDNS名を一つだけ記載しています。
from sc08 import *
leaf = certificate("leaf")
print(leaf.extensions.get_extension_for_class(x509.SubjectAlternativeNameSAN拡張を取得する).value.get_values_for_type(x509.DNSNameDNS名を取り出す))このSANにother.example.testやIPアドレスはありません。接続先の参照名との対応を検査します。
2名前が一致する接続を検証する
信頼先と中間証明書を用意し、SANに含まれる名前を指定します。
print(check(host="api.example.test"期待する接続先を指定する))終了コード0です。接続先の参照名がこの証明書に対応しています。
3別ホストとして検証する
証明書は同じまま、接続先名だけ変更します。
print(check(host="other.example.test"SANにない名前を期待する))hostname mismatchです。証明書が信頼できるCAの発行でも、別ホストの身元確認へは使えません。
4有効期間をUTCで確認する
notBeforeとnotAfterは証明書の有効期間です。ローカルのタイムゾーンと混同しないようUTCで表示します。
print(leaf.not_valid_before_utc開始時刻を表示する.isoformat())
print(leaf.not_valid_after_utc終了時刻を表示する.isoformat())この試料は2026-01-01から2027-01-01までです。期限の判定には検証側の正しい時刻も必要です。
5期間の前後で検証する
時刻以外の条件を保って、開始前と終了後を比較します。
print(check(when="2025-12-31"有効開始前で検証する))
print(check(when="2028-01-01"有効終了後で検証する))開始前はnot yet valid、終了後はhas expiredです。通信相手が同じ鍵を持っていても期間条件は別に検査します。
6拡張鍵用途を読む
Extended Key Usageで、TLSサーバー認証のOIDを確認します。
print([oid.dotted_stringOIDを数字で表示する for oid in leaf.extensions.get_extension_for_class(x509.ExtendedKeyUsage拡張鍵用途を取得する).value])1.3.6.1.5.5.7.3.1はserverAuthです。この証明書にはclientAuthを指定していません。
7クライアント認証用途で検証する
同じ証明書をTLSクライアント認証として検証します。
print(check(purpose="sslclient"クライアント認証として検証する))
print(check(purpose="sslserver"指定されたサーバー用途へ戻す))クライアント用途はunsuitable certificate purpose、サーバー用途はOKです。用途を確認せず、署名の成功だけで受け入れる処理を避けます。
まとめ
SANを接続先名と照合します。
有効期間は検証時刻に対して評価します。
拡張鍵用途も確認し、使う場面に適合する証明書を受け入れます。
操作履歴と解説を振り返れます。
Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻
from sc08 import *
leaf = certificate("leaf")
print(leaf.extensions.get_extension_for_class(x509.SubjectAlternativeName).value.get_values_for_type(x509.DNSName))['api.example.test']
print(check(host="api.example.test"))(0, ['leaf.pem: OK'])
print(check(host="other.example.test"))(2, ['error 62 at 0 depth lookup: hostname mismatch'])
print(leaf.not_valid_before_utc.isoformat())
print(leaf.not_valid_after_utc.isoformat())2026-01-01T00:00:00+00:00 2027-01-01T00:00:00+00:00
print(check(when="2025-12-31"))
print(check(when="2028-01-01"))(2, ['error 9 at 0 depth lookup: certificate is not yet valid or the system clock is incorrect']) (2, ['error 10 at 0 depth lookup: certificate has expired'])
print([oid.dotted_string for oid in leaf.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value])['1.3.6.1.5.5.7.3.1']
print(check(purpose="sslclient"))
print(check(purpose="sslserver"))(2, ['error 26 at 0 depth lookup: unsuitable certificate purpose']) (0, ['leaf.pem: OK'])
演習完了