久々にどっぷりはまってしまった && ほとんど情報がなくて困ったので、解決策を備忘録として残しておこうと思います。
PHPのcurlでSSLのページにリクエストを送るとどうしてもエラーになってしまう
今回ハマったのは上記です。とあるECの案件で決済モジュールへのリクエストが必要で(当然SSL通信下で)、どれだけ設定を変えてもエラー、エラー、エラー…。ググっても情報は見つからず、何が原因かも分からないまま途方に暮れていたのですが、ふとしたことから閃き、そして解決まで至りました。原因は、↓でした。
NSS/3.13.1.0というSSL Versionを使ってるのがマズイようでした。 私のローカルでは、OpenSSLを使うようになっているのに、何故か今回のサーバではNSSを使う設定に。 また、SSLに問題があるのかと思って、
curl_setopt($handleundefined CURLOPT_SSL_VERIFYPEERundefined FALSE);
みたいな設定しましたが、これでもダメでした。証明書へのパスを渡したりしてもやっぱりダメ。 そもそもリクエストが送信できてない感じなんです。 file_get_contents($uri);だとちゃんとレスポンスが帰るのに…。
ちなみにさくらのVPSでCentOS x86_64、httpd、PHP、mysqlすべてyumで入れたものです。特におかしな設定はしておらず。
NSSとはなんぞや
Mozillaのサイトに詳細がありますね。
引用しますと、
OpenSSL はサーバサイド SSL、TLS、および汎用暗号化機能ライブラリを実装するオープンソースプロジェクトですが、PKCS #11 をサポートしていません。OpenSSL は Eric A. Young と Tim J. Hudson によって開発された SSLeay ライブラリに基づいており、Apache サーバで幅広く使用されています。Apache スタイルのライセンスが付与されています。
NSS は PKCS #11 や S/MIME をはじめとして、サーバおよびクライアント両方のアプリケーションをサポートしています。できるだけ多くの用途を可能にするため、NSS は Mozilla Public License と GNU General Public License の両方でライセンスされています。MPL 条項下または GPL 条項下のどちらでライセンスするかを選択することができます。
とのことです。NSSはサーバサイドだけでなく、クライアントサイドのアプリケーションでもSSLをサポートするような感じでしょうかね。Chromeも使ってるそうです。
Google Chrome が NSS を使うようになったのでSSL/TLS CipherSuite一覧を更新 - 自堕落な技術者の日記
まぁ、それはともかくとして、PHPのcurlでNSSが使われている理由が分かりません・・・。 そして何故かNSSだと、以下の様なcurlのPOST/PUTリクエストのみ失敗するらしいです。
Delayed responses for cURL SSL PUTs/POSTs (php) (NSS vs OpenSSL)
さすがStack Overflowさん・・・。失敗、というよりはレスポンスに遅延が見られる、という内容でしたが、現象はよく似ていて、これがヒントになりました。
ここから解決まであーだこーだやったログなどを書きます。
PHPをソースコードからインストールしてみよう(curlは組み込みだしね)
まずはyumで入れたPHPを削除。
# yum -y remove php php-common
それから、DLとコンパイルします。選択したのはPHP5.3.14です。
# ./configure ... --with-curl --with-openssl ...
って感じで、curlの有効化とOpenSSL使うよーってオプションを渡してコンパイルしました。結果は↓
変わってません。見事にNSS。試しにリクエストしてみましたが、やっぱりダメ。
libcurl.soがおかしいんじゃないの?
libcurlはyumで入れてたんですが、「そもそもこれがおかしいんじゃない?」と気づき、libcurlをソースコードから入れてみる事にしました。cURLのサイトでは、7.26が最新版だったので、これをOpenSSLを有効にしてlibcurl.soを生成してみることにしました。
# wget http://curl.haxx.se/download/curl-7.26.0.tar.gz
# tar xvfz curl-7.26.0.tar.gz
# cd curl-7.26.0
# ./configure --prefix=/usr/local
# make
# make install
特にオプションを指定せずにコンパイルしましたが、OpenSSLを使う設定になっていました。で、できたsoをコピー。
# cp /usr/local/libcurl.so* /usr/lib64/
この状態で、再度PHPをコンパイルし、apacheを再起動した後にphpinfo()を見てみると・・・
OpenSSLになりました!! そして試しにリクエストを送った所、エラーもなく正常にリクエストが成功しました。
と、いうわけで
原因はcurlがSSLにNSSを使っていたから、という原因特定に至りました。 とはいえ、なぜデフォルトの設定でNSSになっていたのか(他に立てたサーバはOpenSSLだったのに)、 そしてNSSだとなぜSSL通信先のPOSTリクエストに失敗するのか。
大事な部分は分からず終いだったわけですが、とりあえず通信ができて、レスポンスが帰ったので良しとします。。。
まとめ
パラメータが云々よりも、コンパイル条件をチェックした方がいい時もありますよ!!
ということですね。