エラーメッセージ
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
別の表記
- curl: (60) SSL certificate problem: self signed certificate in certificate chain
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
OpenSSL 3.0 以降は「self-signed」、OpenSSL 1.1.1 はハイフンなしの「self signed」と出力するため、ログや検索結果には両方の表記が現れます。新しい curl のリリース(ecdsa.com のテストでは 8.22.0)では「SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)」と表記されます。Windows の Schannel でビルドされた curl は、このケースを「SEC_E_UNTRUSTED_ROOT」として報告します。
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
このエラーの意味
curl はサーバーが送ったチェーンを自己署名のルート証明書までたどりましたが、そのルートが curl の CA バンドルに含まれていません。「unable to get local issuer certificate」とは異なり、チェーンは完全です。curl にはルートが見えていて、それを信頼する理由がないだけです。実際には、このルートはほぼ必ず社内 CA か、TLS を傍受するプロキシのものです。
発生する理由
TLS を検査するプロキシやウイルス対策ソフトが接続を再署名している
よくあるZscaler、Netskope、Fortinet、Palo Alto や多くのウイルス対策製品は、TLS を終端し、独自のルートが発行した証明書を提示して、そのルートもチェーンに含めて送信します。ブラウザーは OS を通じてそのルートを信頼しますが、WSL、Docker、Git Bash、CI ランナー内の curl は独自のバンドルを読み込むため、そのルートを拒否します。証明書の発行者には、サイトの本来の CA ではなく、プロキシのベンダー名や自社名が記載されています。
サービスがプライベートルートを使い、チェーンに含めて送信している
よくある社内サービス、Kubernetes の Ingress、アプライアンスは、社内のルートや自作のルートから発行された証明書を使い、そのルートを返すチェーンに含めていることがよくあります。ルートを送信しても、信頼されるようにはなりません。トラストアンカーは、あらかじめクライアント側に存在していなければなりません。
プライベートルートが別のストアにインストールされている
ときどきルートは Windows の証明書ストア、macOS のキーチェーン、Java の cacerts に追加されていますが、この curl は /etc/ssl/certs/ca-certificates.crt や、Git for Windows、Homebrew、conda に同梱されたコピーなどの OpenSSL バンドルを読み込んでいます。
クライアントのパブリックルートストアが古い
まれまれに、一度も更新されていない古いクライアントが持っていない新しいパブリックルートを、サーバーが送信することがあります。ca-certificates パッケージを更新すれば解決します。ルートが本当にパブリックなものであれば、ほかのマシンではサイトを問題なく検証できます。
修正方法
- 1.
誰のルートなのかを確認する
チェーンの最後の証明書を確認してください。サブジェクトがプロキシやセキュリティベンダー、または自社の名前であれば、このマシンでそのルートを信頼するのが解決策です。管理下にないネットワークで予期しない名前が表示された場合は、作業を中止してください。誰かが接続を傍受しています。
bash # Print the subject and issuer of every certificate the server (or the proxy) sends openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last entry has s: == i: — that self-signed root is what curl does not trust. - 2.
社内またはプロキシのルートをシステムのストアに追加する
IT 部門からルート証明書を入手し(または管理対象マシンのトラストストアからエクスポートし)、curl が使う OS のバンドルにインストールしてください。コンテナーではビルド時に必要です。WSL では Windows だけでなく、Linux ディストリビューションの中にも必要です。
bash # Debian, Ubuntu, Alpine sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # RHEL, Fedora, Amazon Linux sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust - 3.
1 つのコマンドまたは 1 つの環境に限ってルートを信頼する
--cacert と CURL_CA_BUNDLE は curl のデフォルトのバンドルを置き換えます。そのため、同じプロセスが公開サイトとも通信する場合は、パブリックルートとプライベートルートの両方を含むファイルを指定してください。
bash curl --cacert corp-root-ca.pem https://api.example.com/ # Keep public roots too: build one bundle with both cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem export CURL_CA_BUNDLE=/opt/certs/bundle.pem - 4.
サーバー管理者:ルートの送信に頼らない
リーフ証明書と中間証明書を返し、プライベートルートは代わりに構成管理を通じてクライアントに配布してください。ルートを含めてもバイト数が無駄になるだけで、信頼されていないルートが信頼されるようになることは決してありません。
bash # Serve leaf + intermediates only; clients must already have the root cat api.example.com.crt intermediate.crt > fullchain.pem - 5.
一時的な診断:検証を無効にして発行者を確認する
-k は証明書のチェックを無効にするので、詳細出力でサーバーが提示するサブジェクトと発行者を確認できます。ルートの特定にだけ使ってください。スクリプトに残したままにすると、-k によって経路上の誰もが、トークンやダウンロードしたインストーラーを含む通信を読み取り、改ざんできるようになります。
bash # Diagnostic only: shows who issued the certificate even though curl distrusts it curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'
関連するエラー
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)