PHPでSSLを無効化してTLSでHTTP通信する方法
概要
ある決済サーバーで突然、HTTPS通信が出来なくなった。
原因はSSL3.0の脆弱性が発見され、SSL2.0とSSL3.0による通信が拒否されたため。解決方法はTLS1.0以降のプロトコルで通信してくださいとのこと。
調べてみると、twitterやfacebookのAPIを使ってる人達も影響が出て困っているとのこと。知らんかったー。
ということで、プログラムを書き換えることになった。
解決法その1
PHPのHTTP RequestでXMLをPOST通信している場合のソースコードはこんな感じになると思う。
require_once 'HTTP/Request.php'; $xml_str = "<xml>hoge</xml>"; $req = new HTTP_Request(); $req->setURL("https://hogehoge.com"); $req->setMethod(HTTP_REQUEST_METHOD_POST); $req->addRawPostData($xml_str); $req->sendRequest(); $xml = simplexml_load_string($req->getResponseBody()); var_dump($xml);
残念ながら、HTTP RequestにはSSLをTLSに切り替えるメソッドは存在してない。だからPEARを直接編集する必要がある。
/pear/HTTP/Request.php の707行目辺りを以下のように修正する。
$host = 'ssl://' . $host; ↓↓↓ $host = 'tls://' . $host;
この方法は対応が簡単だし、一応TLS通信できるようにはなるんだけど、PEARのHttpRequestを使っているプログラム全体が影響を受けちゃうので良くない。そこで第2の方法。
解決法その2
PHPのCURLでSSLを無効化してTLSでHTTP通信してみる。以下、先程と同じくXMLをPOST通信している場合のソースコード。
$xml_str = "<xml>hoge</xml>"; $ch = curl_init("https://hogehoge.com"); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSLVERSION, 1); // ← 超重要! curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, 'TLSv1'); // ← 超重要! curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml')); curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_str); $xml = simplexml_load_string(curl_exec($ch)); curl_close($ch); var_dump($xml);
ポイントは「CURLOPT_SSLVERSION」の値を「1」に設定しているところ。これを「CURL_SSLVERSION_TLSv1」や「CURL_SSLVERSION_TLSv1_0」とかに設定しても自分の環境ではダメだった。
あと「CURLOPT_SSL_CIPHER_LIST」の値を「TLSv1」に設定するのも注意。上記の「CURLOPT_SSLVERSION」だけではSSLからTLSに切り替わらなかったからだ。
ダメだった方法
最初はHTTP_Request2を使っていたのだけどダメだった。一応、ソースを載せておく。
try { require_once 'HTTP/Request2.php'; $xml_str = "<xml>hoge</xml>"; $request = new HTTP_request2(); $request->setConfig('ssl_verify_peer', false); $request->setConfig('ssl_verify_host', false); $request->setUrl('https://hogehoge.com'); $request->setMethod(HTTP_Request2::METHOD_POST); $request->setBody($xml_str); $response = $request->send(); $xml = simplexml_load_string($response->getBody()); } catch(Exception $e) { echo '捕捉した例外: ', $e->getMessage(), "\n"; }
ssl_verify_peer(相手サーバーの証明書)とssl_verify_host(自サーバーの証明書)の検証を無効にしておけばいけるのかなと思ったけどhttps接続できなかった。
この辺、キチンと勉強しないとなあ。。
おまけ
phpのバージョンが5.6以上ならストリームラッパーのコンテキストオプションでSSL/TLSのバージョンを指定できるらしい(未検証)。
プログラムはこんな感じ。
stream_context_set_default([ 'ssl' => [ 'crypto_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT, ], ]); echo file_get_contents('https://hogehoe.com');
詳しくはコチラへどうぞ。
追記
tls1.1またはtls1.2はopensslのバージョンが1.0.0以上じゃないと対応してない。
もし上記のコードでうまく動かない場合はopensslのバージョンを確認してみるべし。