続 IO::Socket::SSL で証明書の検証をする

この間の日記の続き。
コメントに貴重な情報をいただいたので、少し調べてみた。まだうまくいっていないので経過報告といったところだが、ひとつ変なことに気がついたので、記しておく。

検証のために書いたコードは Gist に置いた。まず、imap.mail.me.com の証明書を検証するために必要な、「Verisign Class 3 Public Primary Certification Authority - G3」と「VeriSign Class 3 Public Primary Certification Authority - G5」の証明書だけを入れたファイルを使い、検証をさせてみた。$Net::SSLeay::trace = 3 にしても、検証過程が読みとれなかったので、コールバックを使って経過を確認してみた。
すると、最初に呼ばれた際の Issuer (正確には Issuer と Subject をつないだもののようで、二つめの「/C=US」以降は Subject と思われる)が

/C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority/C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5

となっている。Issuer の OU は、「VeriSign Class 3 Public Primary Certification Authority - G5」になるべきところではないのか。なぜか「Class 3 Public Primary Certification Authority」になっている。これはおかしい(たぶん)。
エラー番号の「20」は、openssl/x509_vfy.h を見ると、「X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY」のことらしい。Issuer の証明書が見つからなかった、ってことかな。
では、ca-bundle_tiny.crt に入っている証明書がおかしいのかと思い、G5 の部分だけを抜きだしたファイルを作成し、

openssl x509 -in 05_verisign_class3_g5.crt -text

で内容を表示させてみると、

Issuer: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5

Subject: C=US, O=VeriSign, Inc., OU=VeriSign Trust Network, OU=(c) 2006 VeriSign, Inc. - For authorized use only, CN=VeriSign Class 3 Public Primary Certification Authority - G5

と、こちらは正常っぽい? 元になった certdata.txt でも同じなので、たぶんこれは問題なさそう?

ということで、なんだか Net::SSLeay (あるいはリンクされてる OpenSSL) のバグっぽい感じもするのだけど、そもそも「Class 3 Public Primary Certification Authority」がどこからきたもの? ってのが謎。imap.gmail.com では、「Google Internet Authority G2」→「GeoTrust Global CA」→「Equifax Secure Certificate Authority」の順できれいにつながってる感じなのに。
Mac OS X だけの問題かと思って Windows 上の ActivePerl で試してみたが、同じ症状だった。IO::Socket::SSL を使わず、Net::SSLeay だけで検証させてみても同じ結果。余計にわからなくなってしまった...。

追記

OpenSSL でも試した。

$ openssl s_client -CAfile ca-bundle.crt -connect imap.mail.me.com:993 -showcerts
CONNECTED(00000003)
depth=2 /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
verify error:num=20:unable to get local issuer certificate
verify return:0
$ openssl s_client -CAfile email.crt -connect imap.mail.me.com:993 -showcerts
CONNECTED(00000003)
depth=3 /C=US/O=VeriSign, Inc./OU=Class 3 Public Primary Certification Authority
verify return:1
depth=2 /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=(c) 2006 VeriSign, Inc. - For authorized use only/CN=VeriSign Class 3 Public Primary Certification Authority - G5
verify return:1
depth=1 /C=US/O=VeriSign, Inc./OU=VeriSign Trust Network/OU=Terms of use at https://www.verisign.com/rpa (c)10/CN=VeriSign Class 3 Secure Server CA - G3
verify return:1
depth=0 /C=US/ST=California/L=Cupertino/O=Apple Inc./CN=*.mail.me.com
verify return:1

となり、OpenSSL でも同じ状態なのがわかった。ここで表示されている G5 は、G5(-2021) のほうみたい。ca-bundle.crt には G3、G5(2036) しか入ってないので検証ができず、email.crt には G3、G5(2036)、無印が入っており、G5(2021) はないが、無印のがあるので検証できたったことなのかな。
そうすると、OpenSSL 自体の問題ってこと? あるいはサーバ側の問題?

追記 2

ふと、mk-ca-bundle.pl がダウンロードしてくる certdata.txt にはいくつか種類があったなと思いだし、-d nss を指定した際に参照されるファイル を見てみたところ、上記の無印のが、SERVER_AUTH TRASTED_DELEGATOR になってることを発見。
nss と release の違いがわからないが、比較すると十数箇所違いがあった。nss と release のどちらを使うべきなのか不明だが、とりあえず nss の方を使えばエラーは回避できた。
いったんは、これか。

追記 4

NSS プロジェクトのページ から変更をたどると、
https://hg.mozilla.org/projects/nss/rev/d936c1e1c51e
で変更されていた。この変更は NSS_3_16_3_PLUS_BRANCH のブランチにおけるもの。この変更が mozilla-release にも反映されているようだ。
変更内容の詳細は NSIS 3.16.3 のリリースノート が詳しい。1024 ビットのルート証明書について、Web サイトの認証目的から外したということのようだ。
「nss」を指定したときにダウンロードされるものは、
https://hg.mozilla.org/projects/nss/file/a163e09dc4d5/lib/ckfw/builtins/certdata.txt
のファイルで、こちらは NSS_3_16_2_BRANCH のもの。最終のコミットを比較すると、NSS_3_16_2_BRANCH の certdata.txt は 2014-04-01 14:07 +0200、mozilla-release のものは 2014-08-06 20:22 +0200 なので、release の方が新しい。
新しいものを採用するという考え方に立てば、やはり release を使うべきということになるのか。しかし、上記の NSS プロジェクトのページを見ると、3.16.2 のブランチも更新があり、メンテナンスされているようなのでとりあえずはこれでいけそうかな。
「tip」って何なのか気になって調べてみたら、Mercurial の FAQ にいきあたった。最後に更新された changeset を指すってことか。現在は、3.16.2 のブランチの方が最後に更新されているのでそっちを見てるってことね。そうすると、3.16.3 のブランチの方に更新が入ると同じ問題が起こるってことか。んー、やはり一時的な対応にしかならないか...。