Help us understand the problem. What is going on with this article?

curl で unable to load client key: -8178 エラーが出た

要件

ルート証明書を使用して、とあるホストにアクセスする用事があった。
ゴールは、PHP の cURL 関数でアクセスできること。

環境

  • CentOS 7
  • php7.3
    • cURL 関数を使用

事象

php の cURL 関数でエラーが発生

証明書のパスを指定して cURL 関数を実行してみたところ、
以下のようなエラーが出てしまった。

# オプション定義(抜粋)
$ch = curl_init( "https://xxxx" );
curl_setopt( $ch, CURLOPT_SSLCERT, "./aaa.pem" );
curl_exec( $ch );

# 実行結果
cURL の戻り値:58
curl_error のメッセージ:
  unable to load client key: -8178 (SEC_ERROR_BAD_KEY)

調査

CUI の curl でアクセスしてみる

では、同じ URL に対して CUI の curl で試したらどうなるか確認。

# コマンド
curl -v https://xxxx --cacert ./aaa.pem

# 結果
< HTTP/1.1 200 OK
~省略~
*   CAfile: ./aaa.pem
  CApath: none
~省略~
curl: (58) unable to load client key: -8178 (SEC_ERROR_BAD_KEY)

同じエラーが出てしまった。

エラーメッセージを見てみる

curl のエラーの中に
CApath: none
という文言があった。

一つ上にある CAfile には引数の --cacert の値が入っているので、
別の引数で指定しないといけない?

調べてみると、--capath という引数で
CApath に証明書のディレクトリを指定できるらしい。
早速試してみる。

解決編

curl に capath オプションを指定してみる

# コマンド
curl -v https://xxxx --capath <aaa.pem が存在するディレクトリのパス>

# 結果
< HTTP/1.1 200 OK
~省略~
*   CAfile: /etc/...省略.../ca-bundle.crt
  CApath: <aaa.pem が存在するディレクトリのパス>
~省略~
URL のデータ

取得成功!
ちなみに、ディレクトリは絶対パスでも相対パスでも指定可。

PHP の cURL 関数でも試してみる

PHP の cURL 関数では CURLOPT_CAPATH というオプションで
証明書のパスを指定できるらしい。

# オプション定義
curl_setopt( $ch, CURLOPT_CAPATH, "<aaa.pem が存在するディレクトリのパス>" );

# 実行結果
成功(CUI と同じ)

ファイル名ではなくディレクトリの指定が必要だったんですね。

追記

OS の証明書ストアに登録する方法

独自の証明書をOSの証明書ストアに登録すれば、
CApath の指定が無くても読み込んでくれます。
 ※この方法が普通みたいですね;

# cp aaa.pem /usr/share/pki/ca-trust-source/anchors/
# update-ca-trust

ただし、この場合は独自証明書が OS 全体に適用されてしまうため、
ユースケースによっては、前述の様に証明書パスを指定した方が良いかもしれません。

ユースケースの例としては・・・

  • 独自証明書をサービス固有で定義したい場合
    • サービス毎にディレクトリを作っており、
      他のサービスに影響を与えたくない場合など
    • OS 自体にインストールしないので、
      Ansible などでの配布時に OS にほぼ影響を及ぼさない
  • 証明書を cron などで自動的に取得 & 配置 している場合
    • 外部サービスの証明書が頻繁に更新される場合、
      証明書ストアを更新するよりも自動化の手間が省ける

証明書ファイルを明示的に指定

CURLOPT_CAPATH を指定していれば、証明書ファイル名を指定しなくても
ディレクトリ内にあるファイルから自動的?に証明書を見つけてくれますが、
CURLOPT_CAINFO で明示的に証明書パスを指定することもできるようです。
 ※その場合でも CURLOPT_CAPATH は指定必須

# オプション例
curl_setopt( $ch, CURLOPT_CAINFO , "./aaa.pem" );
curl_setopt( $ch, CURLOPT_CAPATH, "<aaa.pem が存在するディレクトリのパス>" );

業務で長く使っていて色々なサーバの証明書が混在していたり、
有効期限切れのものが混ざっていたりしたら
この方が安全かもしれません。

hantyou-p
Web 開発全般携わっている開発者です。 ・バックエンドは Ruby より Python 派 ・自動化は Chef より Ansible 派 ・DB は RDB より Elasticsearch 派 その他、アークザラッド、歴史、シルクロード、TRPG に興味があります。
ap-com
エーピーコミュニケーションズは「エンジニアから時間を奪うものをなくす」ため、ITインフラ自動化のプロフェッショナルとして、クラウドも含めたインフラ自動化技術で顧客の課題を解決すると同時に、SI業務の課題を解決するプロダクト・サービスを提供するNeoSIer(ネオエスアイヤー)です。
https://www.ap-com.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away