事象と原因
とあるフィードをcurlで取得しようとした際、以下SSLのエラーが出た。
しかし、ブラウザ(chrome)でフィードにアクセスすると正常に表示されている。
結論から言うと、原因は中間証明書が正しく設定されていないことだった。
本記事では中間証明書の確認方法を記載する。
$ curl https://example.jp/feed
curl: (60) Peer certificate cannot be authenticated with known CA certificates
More details here: http://curl.haxx.se/docs/sslcerts.html
curl performs SSL certificate verification by default, using a "bundle"
of Certificate Authority (CA) public keys (CA certs). If the default
bundle file isn't adequate, you can specify an alternate file
using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
the bundle, the certificate verification probably failed due to a
problem with the certificate (it might be expired, or the name might
not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
the -k (or --insecure) option.
証明書チェインの確認方法
証明書チェインの繋がりを確認する時はopenssl s_clientで証明書情報を表示したときの
---
Certificate chain
0 s:/CN=example.jp
i:/C=US/O=Let's Encrypt/CN=R3
-----BEGIN CERTIFICATE-----
をの所を見る。
この場合
「s:CN=example.jp」が証明対象のドメインで、
「i:/C=US/O=Let's Encrypt/CN=R3」がそのドメインを証明している認証局の情報になる
例えば、正しい証明書チェーンは以下のように繋がっている。
一番上の証明書の「CN=yahoo.jp」を証明している認証局「CN=Cybertrust Japan SureServer CA G4」が
次の証明書で「OU=Security Communication RootCA2」に証明されているのが分かると思う
正しい証明書は、このように数珠繋ぎで対象ドメインと認証局情報が繋がっている
最後の中間証明書を証明している「OU=Security Communication RootCA1」が、各クライアントに保存されているroot証明書になる
$ openssl s_client -connect yahoo.jp:443 -showcerts
CONNECTED(00000003)
(略)
---
Certificate chain ## CN=yahoo.jpの証明書をCN=Cybertrust Japan SureServer CA G4が発行している
0 s:/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Yahoo Japan Corporation/CN=yahoo.jp
i:/C=JP/O=Cybertrust Japan Co., Ltd./CN=Cybertrust Japan SureServer CA G4
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE----- ## Cybertrust Japan SureServer CA G4の証明書をSecurity Communication RootCA2が発行している
1 s:/C=JP/O=Cybertrust Japan Co., Ltd./CN=Cybertrust Japan SureServer CA G4
i:/C=JP/O=SECOM Trust Systems CO.,LTD./OU=Security Communication RootCA2
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE----- ## Security Communication RootCA2の証明書をSecurity Communication RootCA1が発行している
2 s:/C=JP/O=SECOM Trust Systems CO.,LTD./OU=Security Communication RootCA2
i:/C=JP/O=SECOM Trust.net/OU=Security Communication RootCA1
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE-----
---
Server certificate
subject=/C=JP/ST=Tokyo/L=Chiyoda-ku/O=Yahoo Japan Corporation/CN=yahoo.jp
issuer=/C=JP/O=Cybertrust Japan Co., Ltd./CN=Cybertrust Japan SureServer CA G4
---
(略)
注目したのは
- 証明対象のCN or OUと、それを証明している認証局の繋がりに途切れが無いこと
- 最後の証明書を発行しているのがroot認証局であること
の2点
1は、証明書の繋がりを追えば分かると思う
2は、正しい調査方法が分からなかったので、最後の証明書の発行元である「Security Communication RootCA1」を検索してroot証明書かどうかを調べた
もし1に問題が無く、2もroot証明書だった場合、クライアント側にroot証明書がインストールされていない可能性がある。
root証明書を追加 or 更新する方法はクライアント毎に異なるのでそれぞれ調べる
今回の事象の原因と解決
上記の確認方法を実際に試してみる。
openssl s_clientで今回エラーとなったドメインの証明書を確認したところ以下の結果となった。
$ openssl s_client -connect example.jp:443 -showcerts
CONNECTED(00000003)
depth=0 CN = example.jp
verify error:num=20:unable to get local issuer certificate
verify return:1
depth=0 CN = example.jp
verify error:num=27:certificate not trusted
verify return:1
depth=0 CN = example.jp
verify error:num=21:unable to verify the first certificate
verify return:1
---
Certificate chain
0 s:/CN=example.jp
i:/C=US/O=Let's Encrypt/CN=R3
-----BEGIN CERTIFICATE-----
(略)
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=example.jp
issuer=/C=US/O=Let's Encrypt/CN=R3
---
(略)
Start Time: 1617624606
Timeout : 300 (sec)
Verify return code: 21 (unable to verify the first certificate)
---
まず
1. 証明対象のCN or OUと、それを証明している認証局の繋がりに途切れが無いこと
を確認する。
上記の場合、証明書が1つしか入っていない
つまり中間証明書が設定されていないので認証局の繋がりに途切れは無かった。
※ 中間証明書が無い時点で怪しいが。。
次に
2. 最後の証明書を発行しているのがroot認証局であること
を確認する
適切なroot認証局の調べ方が分からなかったので、今回は「C=US/O=Let's Encrypt/CN=R3」を単純に検索して調べた
結果、「C=US/O=Let's Encrypt/CN=R3」はroot認証局ではなく中間証明書の認証局だった。
最後はroot認証局の証明書でなければいけないので、中間証明書が抜けていることが分かった。
フィードの提供元に中間証明書の抜けを指摘し、修正してもらったところ、フィードを取得できた。
なぜブラウザでは正常取得できたのか
どうやら、ブラウザによって中間証明書を補完してくれる機能があるらしい
https://jp.globalsign.com/support/faq/58.html
今回ブラウザで正常取得できていたのは、この機能に当たったためと思われる