メインコンテンツまでスキップ

証明書チェーンをどこまで信じるか

この文書の目的​

呼び出し側が提示した証明書チェーンを、サーバーが「証拠」として扱ってよい条件を説明します。

プラットフォーム証明(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 が発行者の位置に現れた時点で拒否されます。


リーフを読むのは、最後​

検証の順序も重要です。

1. 信頼点まで遡れるか        ← ここを通るまで
2. 各発行者は発行してよいか 中身は一切読まない
3. 有効期限
─────────────────────────
4. ここで初めてリーフの中身を読む
チャレンジ / アプリ識別子 / 公開鍵 / セキュリティレベル …

リーフの拡張領域は、攻撃者が最も自由に書ける場所です。そこを先に読んで分岐すると、まだ信頼していないデータで判断することになります。


関連ドキュメント​