X.509証明書の検証手順とありがちな脆弱性

御社におかれてはTLSスタックのテストは十分に実施しておられますか。拙稿[改造する人のためのJSSE](https://qiita.com/n-i-e/items/cb6b110b249e5c63c6f1)をお読みになった御社はもちろんばっちりだと思いますが、世の中では相変わらず不十分なテストと脆弱なコードが溢れかえっている今日この頃です。そんなわけで本稿のお題は証明書の検証！

# 基本的な仕組みを復習する

証明書の検証というのは具体的に何をするのかを復習します。以下の説明では、TLSサーバ証明書をクライアントで検証するケースで説明します。これ以外のケース（TLSクライアント証明書をサーバ側で検証するとか、コードサイニング証明書を検証するとか）は、適宜読み替えてください。

## 証明書チェーン（候補）の構築

TLSのServerCertificateメッセージで、サーバ証明書がダウンロードされて来るわけですが、まずは証明書のSubjectフィールドとIssuerフィールドに注目し、下位証明書のIssuerが上位証明書のSubjectに一致するように並べます。例えば https://qiita.com/ の証明書の場合は、こうなります。

```
Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
　↑（上位、証明する側）
　↓（下位、証明される側）
Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
Subject: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G3
　↑（上位、証明する側）
　↓（下位、証明される側）
Issuer: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G3
Subject: OU=GT01505012, OU=See www.rapidssl.com/resources/cps (c)15, OU=Domain Control Validated - RapidSSL(R), CN=*.qiita.com
```

証明書の出所は問いません。TLSのメッセージとしてダウンロードされる証明書でも、最初からクライアントに内蔵されている証明書でも、他のTLSセッションでダウンロードしてブラウザにキャッシュされていた証明書でも、証明書は証明書です。また、SubjectとIssuerが同一文言の証明書は自己署名証明書と呼ばれ、これより上位の証明書はないという意味になります。

さて、こうして一列に並んだ証明書の並びが、証明書チェーンの候補です。ここからクライアントによる検証を経て合格すれば、晴れて証明書チェーンとして扱われます。

__ここで注意！__ 証明書チェーン候補は、1本とは限りません。複数本ということもあります。それどころか、検証を経て有効とされた証明書チェーンすら、複数本という場合があります。このことをうっかり忘れて不用意なコードを開発すると、CVE番号を振られる憂き目に遭います。

##証明書チェーンの検証

上記で出そろった証明書チェーン候補を検証します。ここでの注意事項は、もし検証で不合格と出ても、それは単にその証明書チェーン候補が無効というだけの話ですので、処理としては、その候補を捨てて次の候補の検証に進めばよいことです。__まったくもって例外を吐いて落ちるに値しません。__

この検証、具体的な検証項目は多岐に渡るのですが、以下、主なものを示します。

- サーバ証明書の場合
TLSサーバ証明書の場合、証明書チェーンの最下位がサーバ証明書です。基本的に、サーバ証明書のSubjectフィールドの``CN``がホスト名を表します（例外もあります）。 https://qiita.com/ の場合であれば、Subjectフィールドの``CN=*.qiita.com``という文言が、これに該当します。

- CA証明書の場合
証明書チェーンの最下位以外は全部CA証明書です。CA証明書は、基本制約 (Basic Constraints) フィールドにCAというフラグが立っていることが必須です。CAフラグのないCA証明書は無効です。CAは非常に強力な権限で、取り扱いを間違えるとPKIの根幹を揺るがす事態になりかねませんので、CAフラグの発行は慎重に、検証は厳密に、行わなければなりません。

- 署名の整合性
下位証明書の本文と署名フィールドの組み合わせを、上位証明書の公開鍵で検証します。合っていれば合格です。

- タイムスタンプ
Not BeforeフィールドとNot Afterフィールドの間に現在時刻が入っていれば合格です。

- Root証明書
最上位の自己署名証明書が「Root証明書」であれば合格です。Root証明書かどうかのフラグは、あらかじめクライアントに内蔵されています。

- 失効
当然ですが、失効した証明書は無効です。クライアントが失効情報を手に入れる方法としては、CRL ([RFC5280](https://tools.ietf.org/html/rfc5280)/[Google翻訳](https://rfcs.web.fc2.com/rfc5280.html))、OCSP ([RFC6960](https://rfcs.web.fc2.com/rfc6960.html)/[Google翻訳](https://rfcs.web.fc2.com/rfc6960.html))、OCSP Stapling ([RFC6961](https://tools.ietf.org/html/rfc6961)/[Google翻訳](https://rfcs.web.fc2.com/rfc6961.html)) があります。何らかの方法で手に入れて、該当する証明書があれば無効と扱います。

こんなところです。これで全部ではなく、もっといろいろありますが、本稿では省略します。

# ありがちなケース

証明書の検証手順がわかったところで、ではどんなケースを想定したテストを書けばよいのかというのを説明します。もちろん、ここで述べるのが全部ではありません。あらゆるケースを想定して、網羅的なテストを書くようにして下さい。

## 勝手な証明書
請求されてもいないのに勝手に証明書を発行するという攻撃があります。このような勝手な証明書に基づく虚偽の証明書チェーンは、Root証明書につながっていませんので、普通は単に無効と扱われるだけですが、クライアントの検証ロジックに不備があると、誤動作を誘発する可能性があります。また、勝手な証明書が勝手なCRLを発行することもありますが、これも無効と扱うのが正しい処理です。

## 相互認証

```
Issuer: A
Subject: B
```

と、

```
Issuer: B
Subject: A
```

という2通の証明書があるとします。要するに証明書チェーンが循環している状態です。攻撃者が勝手に発行した証明書同士で循環している場合もあるし、カタギな正規の認証局同士が相互認証している場合もありますが、いずれにしても、証明書検証ロジックを開発するにあたっては、こういうケースもきちんと考慮しないと、CVE番号を振られる憂き目に遭います。

## クロスルート証明書

クロスルートとは何かというと、再び https://qiita.com/ を例に持ち出しますが、上記で説明した証明書チェーン以外に、下記のようなチェーンもあるのです。さっきのと微妙に違いますでしょ！これがクロスルートです。

```
Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
Subject: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
　↑（上位、証明する側）
　↓（下位、証明される側）
Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
　↑（上位、証明する側）
　↓（下位、証明される側）
Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
Subject: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G3
　↑（上位、証明する側）
　↓（下位、証明される側）
Issuer: C=US, O=GeoTrust Inc., CN=RapidSSL SHA256 CA - G3
Subject: OU=GT01505012, OU=See www.rapidssl.com/resources/cps (c)15, OU=Domain Control Validated - RapidSSL(R), CN=*.qiita.com
```

``GeoTrust Global CA``の運営が始まったばかりの頃は、このRoot証明書を内蔵するクライアントが少なかったので、既に広く使われていた``Equifax Secure Certificate Authority``から「クロスルート証明書」の発行を受けていました。これを使えば、``GeoTrust Global CA``が中間認証局と扱われ、検証を通るというわけです。このようなクロスルート証明書の発行はよくあることです。

で、ここで一つ問題がありまして、それはEV証明書の判定です。証明書検証ロジックによっては、クロスルート証明書を読み込むか読み込まないかの違いによって、EV証明書を正しくEVと認識せず、URL欄に緑のヤツが出ない場合があるらしいのです。最近の認証局は合併・買収の繰り返しで多数のクロスチェーン証明書を発行して複雑なことになりがちで、例えばシマンテックのページには[わかりにくいQ&A](https://knowledge.symantec.com/jp/support/ssl-certificates-support/index?page=content&id=SO28069)が掲載されていたりします。こういう複雑なケースでも正しく判定できる検証ロジックを開発することが必要です。

#Q&A

__Q. TLSスタックなんてインフラレベルのテストを当社でやらなければいけないのか。当社はただ、既製品のTLSスタックを使ってアプリを開発しているだけだ。__
A. はい（断言）。

__Q. 証明書の検証不備の脆弱性なんて本当にあるのか。__
A. いやまったく次から次へと出るわ出るわ。

- JVNVU#91754464 (2016/10/03)
iOS 版「U by BB&T」に SSL サーバ証明書の検証不備の脆弱性
https://jvn.jp/vu/JVNVU91754464/

- JVNVU#99474230 (2016/09/27)
OpenSSL に複数の脆弱性
https://jvn.jp/vu/JVNVU99474230/

- JVNVU#99160787 (2015/07/10)
OpenSSL に証明書チェーンの検証不備の脆弱性
https://jvn.jp/vu/JVNVU99160787/

- JVNVU#90369988 (2014/09/04)
複数の Android アプリに SSL 証明書を適切に検証しない脆弱性
https://jvn.jp/vu/JVNVU90369988/

- JVNVU#389795 (2012/09/18)
Windows Phone 7 に SSL サーバ証明書の検証不備の脆弱性
https://jvn.jp/vu/JVNVU389795/

上記はあくまで一例です。脆弱性はほかにもまだまだあります。

__Q. 証明書チェーンの検証手順を詳しく知りたい。__
A. ソースコードを頑張って解読する話になってしまいますが、JSSE (Java) なら[sun.security.ssl.X509TrustManager#checkTrustedメソッド](http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/8b04ee324a1a/src/share/classes/sun/security/ssl/X509TrustManagerImpl.java#l237)あたり、OpenSSLなら[verify_chain関数](https://github.com/openssl/openssl/blob/master/crypto/x509/x509_vfy.c#L207)あたりを起点に読み込んで行く感じになるかと思います。

以上！PKIは掘れば掘るほど細々とした闇がたくさんある世界ですが、幸運を祈ります。

# 参考文献

ITU-T Recommendation X.509 (1997)
Information technology - Open Systems Interconnection - The Directory: Authentication framework
https://www.itu.int/rec/T-REC-X.509-199708-S/en

RFC5280 (2008)
Internet X.509 Public Key Infrastructure Certificate and Certificate Revocation List (CRL) Profile
https://tools.ietf.org/html/rfc5280 / [Google翻訳](https://rfcs.web.fc2.com/rfc5280.html)