ノラプログラマーの技術メモ

ネットで調べても出てこなかった情報を載せていきたい技術系ブログ。

PHPでSSLを無効化してTLSでHTTP通信する方法

概要

ある決済サーバーで突然、HTTPS通信が出来なくなった。

原因はSSL3.0の脆弱性が発見され、SSL2.0とSSL3.0による通信が拒否されたため。解決方法はTLS1.0以降のプロトコルで通信してくださいとのこと。

調べてみると、twitterfacebookAPIを使ってる人達も影響が出て困っているとのこと。知らんかったー。

ということで、プログラムを書き換えることになった。

解決法その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にはSSLTLSに切り替えるメソッドは存在してない。だからPEARを直接編集する必要がある。

/pear/HTTP/Request.php の707行目辺りを以下のように修正する。

$host = 'ssl://' . $host;
 ↓↓↓
$host = 'tls://' . $host;

この方法は対応が簡単だし、一応TLS通信できるようにはなるんだけど、PEARのHttpRequestを使っているプログラム全体が影響を受けちゃうので良くない。そこで第2の方法。

解決法その2

PHPCURLSSLを無効化して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に切り替わらなかったからだ。

苦労したけど、無事にPHPTLS通信できるようになりましたー。

ダメだった方法

最初は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のバージョンを確認してみるべし。