Check that your cafile/capath settings〜なるエラーに騙された話


問題

古いシステムをPHP 7.4に上げようとしていました。アクセスするのにクライアント証明書が必要な社内向けAPIがありまして、このアクセスがうまくいきません。

エラーメッセージはこんな感じ:

warning: file_get_contents(): Unable to set local cert chain file `xxx.pem`:
 Check that your cafile/capath settings include details of your certificate
 and its issuer in foo.php on line XX

調査

このエラーを見た私は、「cafile/capathの設定をチェックしろ? たしかに社内向けプライベート認証局とか使うので変な設定してるしなあ...」と思ってそのあたりをいくらチェックしてもおかしいところは見当たりません。

でもこれ古いシステムではちゃんと使えてるやつだし、と思ってSSLコンテキストオプションのマニュアルを眺めていたら、7.2から使えるsecurity_levelという設定があるのに気がつきました。

とりあえず下げてみるかとおもって0にするとこれが当たりでアクセスできるようになり、さらに試すと1まではアクセス可、2で上記のエラーになることが判明しました。

security_levelの値についてはSSL_CTX_set_security_levelのマニュアルを見ろと書いてあるのでそちらを見ますと、

Level 1
The security level corresponds to a minimum of 80 bits of security. Any parameters offering below 80 bits of security are excluded. As a result RSA, DSA and DH keys shorter than 1024 bits and ECC keys shorter than 160 bits are prohibited. All export ciphersuites are prohibited since they all offer less than 80 bits of security. SSL version 2 is prohibited. Any ciphersuite using MD5 for the MAC is also prohibited.

Level 2
Security level set to 112 bits of security. As a result RSA, DSA and DH keys shorter than 2048 bits and ECC keys shorter than 224 bits are prohibited. In addition to the level 1 exclusions any ciphersuite using RC4 is also prohibited. SSL version 3 is also not allowed. Compression is disabled.

とあり、Level 1だとRSA 1024ビット以上、Level 2だとRSA 2048ビット以上とありますが、使ってる証明書は2048ビットでこれは外れ。しかしさらに読み進めるとNOTESのところに

WARNING at this time setting the security level higher than 1 for general internet use is likely to cause considerable interoperability issues and is not recommended. This is because the SHA1 algorithm is very widely used in certificates and will be rejected at levels higher than 1 because it only offers 80 bits of security.

とあり、署名にSHA1使ってるとlevel 1までだよと書いてありました。確認すると例のAPIの管理元からもらったクライアント証明書はSHA1で署名されていてこれが問題だとわかりました。

エラーメッセージおかしくない?

一応問題は解決したのですが、この問題にcafile/capathは結局全然関係ないのでなんか納得いきません。

例のエラーメッセージでphpのソースをgrepすると以下のようになっていました。(ext/openssl/xp_ssl.c)

            if (SSL_CTX_use_certificate_chain_file(ctx, resolved_path_buff) != 1) {
                php_error_docref(NULL, E_WARNING,
                    "Unable to set local cert chain file `%s'; Check that your cafile/capath "
                    "settings include details of your certificate and its issuer",
                    certfile);
                return FAILURE;
            }

つまりこのエラーメッセージは単にSSL_CTX_use_certificate_chain_fileに失敗したことを示しているだけでエラーの内容については何も見ていません。cafile/capathがどうのというのはいわば当てずっぽうです。

openssl_error_stringを使いましょう

実はopensslはエラーについての情報を内部で保持していて、エラーメッセージを取り出すAPIが用意されています。phpのopenssl拡張にも、openssl_error_stringという関数が用意されています。

エラーになったfile_get_contentsのあとでこれを使ってエラーを出力してやると、

error:140AB18E:SSL routines:SSL_CTX_use_certificate:ca md too weak

と出まして、これ読んでもさっぱり意味が分かりませんが、'ca md too weak'でググるとsecurity levelとSHA1署名が問題であることがわかりますから、cafileの確認などに時間を浪費せずに早く正解にたどり着けたのではないかと思います。