LoginSignup
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-10-12

要件

ルート証明書を使用して、とあるホストにアクセスする用事があった。
ゴールは、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 が存在するディレクトリのパス>" );

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0