証明書チェーンをどこまで信じるか
この文書の目的
呼び出し側が提示した証明書チェーンを、サーバーが「証拠」として扱ってよい条件を説明します。
プラットフォーム証明(Android Key Attestation / Apple App Attest)や mTLS のように、相手が持ってきた証明書の中身を判断材料にする場面で必要になる考え方です。
詳細な検証手順は 証明書チェーンの検証 にあります。
チェーンは「入力」であって「証拠」ではない
証明書チェーンは、呼び出し側が自由に組み立てられるただのデータです。パースが通ることも、中身が立派に見えることも、何の保証にもなりません。
攻撃者が自分で作れるもの
├─ 証明書の subject 名
├─ 拡張領域の中身(アプリ名、チャレンジ、セキュリティレベル …)
├─ 有効期限
└─ チェーンの並び順と長さ
証拠になるのは、サーバーが事前に決めて持っている信頼点(ルート)まで、途切れずに遡れたときだけです。逆に言えば、ルートに届くまでの間に読み取った値は、まだ何も意味しません。
だから検証は「上から下へ」ではなく「信頼点から出発して、リーフまで降りてこられるか」という形になります。
「署名が繋がっている」だけでは足りない
ここが直感に反するところです。
チェーンの各証明書が「次の証明書の鍵で署名されている」ことを確認しても、それだけでは chain of trust は成立しません。
鍵は、署名する相手を選びません。 署名鍵はどんなデータにでも署名できます。証明書もデータなので、普通の署名鍵で、別の証明書を作れてしまいます。
攻撃者が持っているもの
正規の端末で作った署名鍵 K1
K1 に対する正規のチェーン [ L1(K1) , 中間CA , ルート ]
↑ L1 は「末端」の証明書(CA ではない)
攻撃者がやること
K1 で、自分の鍵 K0 を subject とする偽の証明書 L0 を署名
提示するチェーン [ L0(K0) , L1(K1) , 中間CA , ルート ]
↑ 偽物 ↑ L1 が「発行者」として振る舞っている
このチェーンは、全てのリンクの署名検証を通ります。ルートも本物です。有効期限も正しい。
それでも受け入れてはいけません。L1 は末端の証明書であって、他の証明書を発行する権限を持っていないからです。
発行者は「発行してよい」と名乗っている必要がある
X.509 は、この権限を証明書自身の中に書きます。
| 拡張 | 意味 |
|---|---|
BasicConstraints cA=TRUE | この証明書は他の証明書を発行してよい |
BasicConstraints pathLenConstraint | 自分より下に何段の CA を許すか |
KeyUsage keyCertSign | この鍵は証明書への署名に使ってよい |
発行者として使う証明書に、これらを要求する。 それが上の攻撃を止める唯一の箇所です。署名が繋がっているかどうかとは別の検査であり、片方だけでは意味がありません。
先ほどの例では L1 に cA=TRUE が無いので、L1 が発行者の位置に現れた時点で拒否されます。