証明書チェーンと認証局
提示された証明書と検証側の信頼設定が、それぞれ何を担うかを説明します。
説明
証明書は公開鍵と名前などの情報を発行者の署名で結び付けます。検証側は対象の証明書から発行者をたどり、あらかじめ信頼しているCAへつながる経路を検証します。自己署名されているだけで、自動的に信頼されるわけではありません。
サーバー、中間CA、ルートCAの3枚を調べます。OpenSSLで経路を検証した後、中間証明書や信頼するルートを変え、失敗する深さと原因を確認します。
提示された証明書と検証側の信頼設定が、それぞれ何を担うかを説明します。
1サーバー証明書の名前と発行者
固定の演習用証明書を読みます。subjectは証明書の主体、issuerは発行者の名前です。
from sc08 import *
leaf = certificate("leaf")サーバー証明書を読み込む
print(leaf.subject.rfc4514_string())
print(leaf.issuer発行者名を取得する.rfc4514_string())
print(leaf.serial_number)主体はLbox leaf、発行者はLbox intermediate、シリアル番号は30です。番号は発行者の範囲で証明書を特定する値です。
2中間CAからルートへたどる
中間証明書の発行者と、ルートの主体名を比較します。名前の一致だけで署名検証が済むわけではありません。
middle = certificate("intermediate")中間CA証明書を取得する
root = certificate("root")
print(middle.subject.rfc4514_string(),middle.issuer.rfc4514_string())
print(root.subjectルートの主体を確認する.rfc4514_string(),root.issuer.rfc4514_string())中間の発行者はLbox rootです。ルートは主体名と発行者名が同じ自己発行の証明書で、この試料は自身の鍵で署名しています。
3CA制約と公開鍵を確認する
Basic ConstraintsのcaがTrueならCAとしての証明書です。path_lengthは下に置ける中間CAの数を制約します。
for c in (leaf,middle,root): print(c.subject.rfc4514_string(),c.extensions.get_extension_for_class(x509.BasicConstraintsCA属性と経路長制約を取得する).value)
print(leaf.public_key().public_bytes_raw()葉の公開鍵を表示する.hex())葉はCAではなく、中間はpath_length=0、ルートは1です。中間の下へ別の中間CAを増やす構成は、この制約に合いません。
4信頼するルートまで検証する
checkはOpenSSL verifyを呼び、終了コードと検証行を返します。時刻は2026-11-13、名前はapi.example.test、用途はTLSサーバーに固定します。
print(check()ルート・中間・葉を使って検証する)終了コード0でleaf.pem: OKです。署名の経路だけでなく、ここでは指定した名前・時刻・用途も合っています。
5中間証明書を渡さない
信頼するルートは同じまま、中間を経路構築へ渡す設定だけ外します。
print(check(intermediate=False中間証明書を経路候補から外す))深さ0、つまり葉の発行者が見つかりません。ルートを持っていても、中間の公開鍵を取得できなければ経路を構築できません。
6別のルートだけを信頼する
中間は提示しますが、信頼するCAを無関係なwrongへ変更します。
print(check(ca="wrong"信頼するルートを変更する))深さ1で中間の発行者へつながりません。サーバーが送ってきた証明書をすべて信頼先へ追加する対応は、検証の目的を損ないます。
7自己署名の検証と信頼設定を分ける
試料ルートの署名を、その公開鍵で検証します。これは証明書の署名の整合性を調べる操作です。
root.public_key().verifyルートの自己署名を検証する(root.signature,root.tbs_certificate_bytes)
print(root.subject == root.issuer)
print(check(ca="root")確認済みのルートへ戻して検証する)自己署名の整合性と、検証側がこのルートを信頼すると決めることは別です。最後は確認済みのrootへ設定を戻し、経路が通ることを確かめました。
まとめ
経路は葉から中間を経て、検証側が信頼するCAへつなげます。
CA制約・署名・利用条件をそれぞれ検証します。
自己署名が正しいだけで、そのCAを信頼してよいとは決まりません。
操作履歴と解説を振り返れます。
Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻
from sc08 import *
leaf = certificate("leaf")
print(leaf.subject.rfc4514_string())
print(leaf.issuer.rfc4514_string())
print(leaf.serial_number)CN=Lbox leaf CN=Lbox intermediate 30
middle = certificate("intermediate")
root = certificate("root")
print(middle.subject.rfc4514_string(),middle.issuer.rfc4514_string())
print(root.subject.rfc4514_string(),root.issuer.rfc4514_string())CN=Lbox intermediate CN=Lbox root CN=Lbox root CN=Lbox root
for c in (leaf,middle,root): print(c.subject.rfc4514_string(),c.extensions.get_extension_for_class(x509.BasicConstraints).value)
print(leaf.public_key().public_bytes_raw().hex())CN=Lbox leaf <BasicConstraints(ca=False, path_length=None)> CN=Lbox intermediate <BasicConstraints(ca=True, path_length=0)> CN=Lbox root <BasicConstraints(ca=True, path_length=1)> ed4928c628d1c2c6eae90338905995612959273a5c63f93636c14614ac8737d1
print(check())(0, ['leaf.pem: OK'])
print(check(intermediate=False))(2, ['error 20 at 0 depth lookup: unable to get local issuer certificate'])
print(check(ca="wrong"))(2, ['error 20 at 1 depth lookup: unable to get local issuer certificate'])
root.public_key().verify(root.signature,root.tbs_certificate_bytes)
print(root.subject == root.issuer)
print(check(ca="root"))True (0, ['leaf.pem: OK'])
演習完了