OpenIDについて調べてみた

OAuthは調べ尽くし、OAuthを拡張した独自プロトコルも考案して、OAuthには満足した。
だから今度はOpenIDについて調べてみた。
 
OpenID Enabledとかいう所のライブラリを使うのが主流らしい。
しかしそのライブラリ、めちゃくちゃ汚い。
 
・とあるクラスを環境に応じてインスタンス化して返すだけのグローバルな関数が大量にある。
 => Class::factory() とかを使えば見やすい。
・関数やクラスがどのファイルに書かれてるか予想が付かない。
 => 是非ともPear準拠のディレクトリ構成にして欲しい。
 
で、ソースを小一時間読んでたらとってもイライラしてきたから、仕様書読んで自分でライブラリ作る事に。
とりあえず、discoveryとassociationをして、エンドユーザを飛ばす先のURLを生成する所までやってみた。
合ってるかは解らないけど、とりあえず飛んで返ってくるから、なんとなくそれっぽく出来てる気がする。
キャッシュさせた方がいいらしいけど、そこはまだ。
エンドユーザがプロバイダから値付きで帰ってきた時に、キャッシュしてないと先に進めなかったから今日はここまで。
めんどくさいから、キャッシュはMemcacheにでも入れようか。
 

Diffie-Hellman鍵共有

PearにあったCrypt_DiffieHellmanを使った。
 

Diffie-Hellman鍵共有とは

Crypt_DiffieHellmanのコメントと、↓この動画を見たらなんとなく解った。
 
YouTube - Diffie-Hellman鍵交換ってナニ? (part 1/2)
http://www.youtube.com/watch?v=M45Z2a50VDo

 
YouTube - Diffie-Hellman鍵交換ってナニ? (part 2/2)
http://www.youtube.com/watch?v=DonUjrAGrYM

 

Diffie-Hellman鍵交換のデフォルト値

仕様書にはこう書いてあった。

これは確認済みの素数で、Diffie-Hellman 鍵交換においてデフォルトのモジュラスとして用いられる。16 進表現では、以下の通りである。

DCF93A0B883972EC0E19989AC5A2CE310E1D37717E8D9571BB7623731866E61E
F75A2E27898B057F9891C2E27A639C3F29B60814581CD3B2CA3986D268370557
7D45C2E7E52DC81C7A171876E5CEA74B1448BFDFAF18828EFD2519F14E45E382
6634AF1949E5B535CC829A483B8A76223E5D490A257F05BDFF16F2FB22C583AB

 
16進数から10進数に変換するならPythonが便利。

$ python
Python 2.5.5 (r255:77872, Mar 31 2010, 21:03:05)
[GCC 4.1.2 20080704 (Red Hat 4.1.2-46)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> int('DCF93A0B883972EC0E19989AC5A2CE310E1D37717E8D9571BB7623731866E61EF75A2E27898B057F9891C2E27A639C3F29B60814581CD3B2CA3986D2683705577D45C2E7E52DC81C7A171876E5CEA74B1448BFDFAF18828EFD2519F14E45E3826634AF1949E5B535CC829A483B8A76223E5D490A257F05BDFF16F2FB22C583AB', 16)
155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443L
>>>

 

XRI

@freeXRIって所でXRI発行してもらえた。
多分、ドメインでいう所のサブドメインみたいな。

@id*eth0jp

 
@freeXRI
http://www.freexri.com/
 
ZIGOROuのOpenID Short ClinicOpenIDとXRI――XRIの基本 - ITmedia エンタープライズ
http://www.itmedia.co.jp/enterprise/articles/0805/23/news007.html
 

Yadis

HTTPヘッダのX-XRDS-Locationで指定されてるXRDSファイルと、XRI形式のxri.netから返ってくるXRDSファイルのフォーマットがちょっと違った。
よく解らないからとりあえずX-XRDS-Locationだけに対応させた。
この解析方法も合ってるかは不明。
 
XRI Resolution のメモ - Yet Another Hackadelic
http://d.hatena.ne.jp/ZIGOROu/20080814/1218730541
 

気になる所

openid.assoc_typeとopenid.session_typeのアルゴリズムは統一しなくて大丈夫なのか

openid.assoc_typeをHMAC-SHA1openid.session_typeをDH-SHA256とかチグハグな設定にしてもいいの?って話。
返ってくるenc_mac_keyはopenid.session_typeに合った長さ返ってくるからいいのかも知れないけど。
 

まだopenid.assoc_typeのアルゴリズムで何もしてない

いずれ出てくるのかも知れないけど。
 

ソース

OpenID.php
<?php
require_once('CURL_Request.php');
require_once('Crypt/DiffieHellman.php');


class Yadis
{
	private $_data = array();

	public function __construct()
	{
	}

	public function add($type, $uri, $priority=null)
	{
		if (!isset($this->_data[$type])) {
			$this->_data[$type] = array();
		}
		if (isset($priority)) {
			$this->_data[$type][$priority] = $uri;
		} else {
			$this->_data[$type][] = $uri;
		}
	}

	public function get($type)
	{
		if (isset($this->_data[$type])) {
			ksort($this->_data[$type]);
			foreach ($this->_data[$type] as $uri) {
				return $uri;
			}
		}
		return null;
	}

	public static function parse($xrds_data)
	{
		$yadis = new Yadis();
		$xrds = new SimpleXMLElement($xrds_data);
		foreach ($xrds->XRD->Service as $service) {
			if (isset($service->attributes()->priority)) {
				$priority = (int)$service->attributes()->priority;
				$uri = (string)$service->URI;
				foreach ($service->Type as $type) {
					$yadis->add((string)$type, $uri, $priority);
				}
			}
		}
		foreach ($xrds->XRD->Service as $service) {
			if (!isset($service->attributes()->priority)) {
				$uri = (string)$service->URI;
				foreach ($service->Type as $type) {
					$yadis->add((string)$type, $uri, null);
				}
			}
		}
		return $yadis;
	}
}


class OpenID_Exception extends Exception
{
	private $_detail = null;

	public function __construct($message, $detail=null, $code=0)
	{
		$this->_detail = $detail;
		parent::__construct($message, $code);
	}

	public function getDetail()
	{
		return $this->_detail;
	}
}


class OpenID_Consumer
{
	// const
	const DH_DEFAULT_MODULUS = '155172898181473697471232257763715539915724801966915404479707795314057629378541917580651227423698188993727816152646631438561595825688188889951272158842675419950341258706556549803580104870537681476726513255747040765857479291291572334510643245094715007229621094194349783925984760375594985848253359305585439638443';
	const DH_DEFAULT_GENERATOR = '2';

	// property
	private $_assoc_type = 'HMAC-SHA1';
	private $_session_type = 'DH_SHA1';

	// resource
	private $_dh = null;
	private $_discovery = null;
	private $_association = null;
	private $_op_server = null;


	/* Setter */

	public function setAssocType($type)
	{
		if (!in_array($type, array('no-encryption', 'HMAC-SHA1', 'HMAC-SHA256'))) {
			throw new OpenID_Exception('Not implemented association type: '.$type);
		}
		$this->_assoc_type = $type;
	}

	public function setSessionType($type)
	{
		if (!in_array($type, array(null, 'DH-SHA1', 'DH-SHA256'))) {
			throw new OpenID_Exception('Not implemented session type: '.$type);
		}
		$this->_session_type = $type;
	}


	/* Getter */

	public function getAssocType()
	{
		return $this->_assoc_type;
	}

	public function getSessionType()
	{
		return $this->_session_type;
	}

	public function getAssocAlgo()
	{
		if (strpos($this->_assoc_type, 'HMAC')===0) {
			return substr($this->_assoc_type, 5);
		}
		return null;
	}

	public function getSessionAlgo()
	{
		if (!is_null($this->_session_type)) {
			return substr($this->_session_type, 3);
		}
		return null;
	}


	/* Process */

	public function discovery($identifier, $return_url=null)
	{
		// XRI
		if (strpos($identifier, 'xri://')===0) {
			$identifier = substr($identifier, 6);
		}
		if (in_array(substr($identifier, 0, 1), array('@', '='))) {
			$xrds_url = sprintf('http://xri.net/%s?_xrd_r=application/xrds+xml', $identifier);
			throw new OpenID_Exception('XRI is not implemented.');
		// URL
		} else {
			// Get XRDS URL
			if (!preg_match('#^https?://.+#', $identifier)) {
				$identifier = 'http://'.$identifier;
			}
			OpenID_Utils::debug('discovery identifier', $identifier);
			$curl = new CURL_Request();
			$curl->setURL($identifier);
			$curl->setHeader('Accept', 'application/xrds+xml');
			$curl->exec();
			$xrds_url = $curl->getHeader()->get('X-XRDS-Location');
			if (is_null($xrds_url)) {
				preg_match_all('/<meta [^>]+>/', $curl->getBody(), $meta_arr);
				$meta_arr = $meta_arr[0];
				foreach ($meta_arr as $meta) {
					if (preg_match('/http-equiv="X-XRDS-Location"/', $meta)) {
						$meta_content = array();
						if (preg_match('/content="([^"]+)"/', $meta, $meta_content)) {
							$xrds_url = $meta_content[1];
							break;
						}
					}
				}
			}
		}
		if (is_null($xrds_url)) {
			throw new OpenID_Exception('Not found XRDS URL.');
		}
		OpenID_Utils::debug('discovery xrds url', $xrds_url);

		// Get XRDS data
		$curl = new CURL_Request();
		$curl->setURL($xrds_url);
		$curl->exec();
		$xrds_data = $curl->getBody();
		OpenID_Utils::debug('discovery xrds data', $xrds_data);

		// Parse XRDS data
		$yadis = Yadis::parse($xrds_data);
		$this->_discovery = $yadis;
	}

	public function association()
	{
		$op_server = null;
		if ($this->_discovery) {
			$op_server = $this->_discovery->get('http://specs.openid.net/auth/2.0/server');
		}
		if (is_null($op_server)) {
			throw new OpenID_Exception('OP Server URL is not set.');
		}
		if (strpos($op_server, 'https://')===false && is_null($this->getSessionAlgo())) {
			throw new OpenID_Exception('HTTPS or DiffieHellman is required.');
		}
		$this->_op_server = $op_server;

		// paramater
		$params = array(
			'openid.ns' => 'http://specs.openid.net/auth/2.0',
			'openid.mode' => 'associate',
			'openid.assoc_type' => $this->_assoc_type,
			'openid.session_type' => $this->_session_type
		);
		// diffiehellman parameter
		if ($this->getSessionAlgo()) {
			$dh = new Crypt_DiffieHellman(OpenID_Consumer::DH_DEFAULT_MODULUS, OpenID_Consumer::DH_DEFAULT_GENERATOR);
			$dh->generateKeys();
			$dh_params = array(
				'openid.dh_consumer_public' => base64_encode($dh->getPublicKey(Crypt_DiffieHellman::BTWOC)),
				'openid.dh_modulus' => base64_encode($dh->getPrime(Crypt_DiffieHellman::BTWOC)),
				'openid.dh_gen' => base64_encode($dh->getGenerator(Crypt_DiffieHellman::BTWOC))
			);
			$params = array_merge($params, $dh_params);
		}
		ksort($params);

		OpenID_Utils::debug('association server url', $op_server);
		OpenID_Utils::debug('association params', $params);

		// request
		$curl = new CURL_Request();
		$curl->setURL($op_server);
		$curl->setBody($params);
		$curl->exec();
		$code = $curl->getHeader()->getCode();
		$resp = OpenID_Utils::parseKeyValue($curl->getBody());

		// response error
		if ($code!=200 || isset($resp['error'])) {
			$msg = 'Association error';
			if (isset($resp['error'])) {
				$msg .= ': '.$resp['error'];
			}
			throw new OpenID_Exception($msg, $resp);
		}
		OpenID_Utils::debug('association response', $resp);

		// share secret
		if ($this->getSessionAlgo()) {
			if (!isset($resp['dh_server_public'], $resp['enc_mac_key'])) {
				throw new OpenID_Exception('Association error: response error');
			}

			// shared secret
			$public_key = base64_decode($resp['dh_server_public']);
			$dh->computeSecretKey($public_key, Crypt_DiffieHellman::BINARY);
			$shared_secret = $dh->getSharedSecretKey(Crypt_DiffieHellman::BTWOC);
			$shared_secret_hash = hash($this->getSessionAlgo(), $shared_secret, true);

			// mac_key
			$enc_mac_key = base64_decode($resp['enc_mac_key']);
			$mac_key = '';
			$len = strlen($enc_mac_key);
			for ($i=0; $i<$len; $i++) {
				$mac_key .= chr(ord($enc_mac_key[$i]) ^ ord($shared_secret_hash[$i]));
			}
			$resp['mac_key'] = base64_encode($mac_key);
			OpenID_Utils::debug('association mac_key', $resp['mac_key']);
		}
		$this->_association = $resp;
		return $resp;
	}

	public function getAuthorizationParams($return_to=null, $realm=null)
	{
		// default return_to
		if (is_null($return_to)) {
			$return_to = OpenID_Utils::getDefaultReturnTo();
		}
		if (is_null($realm)) {
			$realm = $return_to;
		}
		// state less mode params
		$params = array(
			'openid.mode' => 'checkid_setup',
			'openid.ns' => 'http://specs.openid.net/auth/2.0',
			'openid.return_to' => $return_to,
			'openid.realm' => $realm,
			'openid.claimed_id' => 'http://specs.openid.net/auth/2.0/identifier_select',
			'openid.identity' => 'http://specs.openid.net/auth/2.0/identifier_select'
		);
		// state mode params
		if (isset($this->_association['assoc_handle'])) {
			$params['openid.assoc_handle'] = $this->_association['assoc_handle'];
		}
		ksort($params);
		OpenID_Utils::debug('getAuthorizationParam param', $params);
		return $params;
	}

	public function getAuthorizationURL($return_to=null, $realm=null)
	{
		$params = $this->getAuthorizationParams($return_to, $realm);
		return sprintf('%s?%s', $this->_op_server, http_build_query($params));
	}

	public function getAuthorizationForm($return_to=null, $realm=null)
	{
		$params = $this->getAuthorizationParams($return_to, $realm);
		$form = sprintf('<form name="openid_identifier" action="%s" method="POST">'."\n", $this->_op_server);
		foreach ($params as $key=>$value) {
			$key = htmlspecialchars($key, ENT_QUOTES);
			$value = htmlspecialchars($value, ENT_QUOTES);
			$form .= sprintf('<input type="hidden" name="%s" value="%s" />'."\n", $key, $value);
		}
		$form .= '</form>';
		return $form;
	}
}


class OpenID_Utils
{
	const DEBUG = true;
	private static $_params = null;

	public static function parseKeyValue($str)
	{
		$lines = preg_split("/(\r\n|\r|\n)/", $str);
		$result = array();
		foreach ($lines as $line) {
			$kv = explode(':', $line, 2);
			if (count($kv)==2) {
				$result[trim($kv[0], ' ')] = trim($kv[1], ' ');
			}
		}
		return $result;
	}

	public static function buildKeyValue($arr)
	{
		$result = '';
		foreach ($arr as $key=>$value) {
			$result .= sprintf("%s:%s\n", $key, $value);
		}
		return $result;
	}

	public static function getDefaultReturnTo()
	{
		$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']=='on') ? 'https' : 'http';
		$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost';
		$port = isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : '80';
		$path = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '/';
		$path = '/'.ltrim($path, '/');
		return sprintf('%s://%s:%s%s', $scheme, $host, $port, $path);
	}

	public static function getParam($key, $default=null)
	{
		if (is_null(self::$_params)) {
			self::$_params = array_merge($_GET, $_POST);
		}
		if (isset(self::$_params[$key])) {
			return self::$_params[$key];
		}
		return $default;
	}

	public static function debug($label, $data)
	{
		if (self::DEBUG) {
			echo "----- $label -----\n";
			echo trim(print_r($data, 1), "\r\n")."\n\n";
		}
	}
}



try {
	//$assoc_type = 'no-encryption';
	//$assoc_type = 'HMAC-SHA1';
	$assoc_type = 'HMAC-SHA256';

	//$session_type = 'DH-SHA1';
	$session_type = 'DH-SHA256';

	//$identifier = 'mixi.jp';
	$identifier = 'yahoo.co.jp';
	//$identifier = 'livedoor.com';
	//$identifier = 'xri://=zigorou';
	//$identifier = 'xri://@id*eth0jp';

	$openid = new OpenID_Consumer();
	$openid->setAssocType($assoc_type);
	$openid->setSessionType($session_type);
	$openid->discovery($identifier);
	$openid->association();
	echo $openid->getAuthorizationURL('http://localhost/')."\n";

} catch (OpenID_Exception $e) {
	echo "### OpenID_Exception\n";
	echo $e->getMessage()."\n";
	print_r($e->getDetail());

} catch (Exception $e) {
	echo "### Exception\n";
	echo $e->getMessage()."\n";
}

 

CURL_Request.php

HTTPリクエストはこれで。
ずっと倦厭してたけど、今回初めてCURLをまともに使ってみた。

<?php
class CURL_Header
{
	private $_protocol = null;
	private $_code = null;
	private $_message = null;
	private $_header = array();

	public function __construct($header=null)
	{
		if (isset($header)) {
			$eol = CURL_Request::getEOL($header);
			$arr = explode($eol, $header);
			foreach ($arr as $i=>$item) {
				if (0<strlen($item)) {
					if ($i==0) {
						list($this->_protocol, $this->_code, $this->_message) = explode(' ', $item, 3);
					} else {
						list($key, $value) = explode(':', $item, 2);
						$this->_header[trim($key)] = trim($value);
					}
				}
			}
		}
	}

	public function getProtocol()
	{
		return $this->_protocol;
	}

	public function getCode()
	{
		return $this->_code;
	}

	public function getMessage()
	{
		return $this->_message;
	}

	public function get($key=null)
	{
		if (is_null($key)) {
			return $this->_header;
		} else if (isset($this->_header[$key])) {
			return $this->_header[$key];
		}
		return null;
	}
}

class CURL_Request
{
	private $_ch = null;
	private $_header = array();

	private $_res_info = array();
	private $_res_head = array();
	private $_res_body = null;


	public function __construct($url=null, $method=null)
	{
		$this->_ch = curl_init($url);
		if (isset($method)) {
			$this->setMethod($method);
		}
		$this->setTimeout(5);
	}


	// set option

	public function setMethod($method)
	{
		$method = strtoupper($method);
		$this->setopt(CURLOPT_CUSTOMREQUEST, $method);
	}

	public function setURL($url)
	{
		curl_setopt($this->_ch, CURLOPT_URL, $url);
	}

	public function setHeader($key, $value=null)
	{
		if (is_array($key)) {
			$this->_header = array_merge($this->_header, $key);
		} else {
			$this->_header[$key] = $value;
		}
	}

	public function setBody($value)
	{
		$this->setopt(CURLOPT_POSTFIELDS, $value);
	}

	public function setTimeout($value, $ms=false)
	{
		if ($ms) {
			$this->setopt(CURLOPT_TIMEOUT_MS, $value);
		} else {
			$this->setopt(CURLOPT_TIMEOUT, $value);
		}
	}

	public function setAuth($username, $password)
	{
		$this->setopt(CURLOPT_USERPWD, $username.':'.$password);
	}

	public function setPort($port)
	{
		$this->setopt(CURLOPT_PORT, $port);
	}

	public function setopt($key, $value)
	{
		curl_setopt($this->_ch, $key, $value);
	}


	// request

	public function exec()
	{
		// init
		$this->_res_init = array();
		$this->_res_head = array();
		$this->_res_body = null;

		// set request headers
		$this->setopt(CURLOPT_HTTPHEADER, $this->_header);
		// follow location
		$this->setopt(CURLOPT_FOLLOWLOCATION, true);
		$this->setopt(CURLOPT_MAXREDIRS, 5);
		// get header flag
		$this->setopt(CURLOPT_HEADER, true);
		// return response flag
		$this->setopt(CURLOPT_RETURNTRANSFER, true);
		$this->setopt(CURLOPT_BINARYTRANSFER, true);

		// execute request
		$res = curl_exec($this->_ch);
		// get info
		$this->_res_info = curl_getinfo($this->_ch);
		// error
		if ($this->_res_info['total_time']==0) {
			throw new Exception('CURL_Request timeout');
		}

		// parse
		$continue = 0;
		for ($i=0; $i<=$this->_res_info['redirect_count']+$continue; $i++) {
			$eol = CURL_Request::getEOL($res);
			@list($head, $res) = explode($eol.$eol, $res, 2);
			if (!isset($head, $res)) {
				break;
			}
			array_unshift($this->_res_head, new CURL_Header($head));
			if ($this->getHeader()->getCode()==100) {
				$continue++;
			}
		}
		$this->_res_body = $res;
		return array(
			'info' => $this->_res_info,
			'head' => $this->_res_head,
			'body' => $res
		);
	}


	// response

	public function getInfo($key=null)
	{
		if (is_null($key)) {
			return $this->_res_info;
		}
		if (isset($this->_res_info[$key])) {
			return $this->_res_info[$key];
		}
		return null;
	}

	public function getHeader($i=0)
	{
		if (is_null($i)) {
			return $this->_res_head;
		}
		if (isset($this->_res_head[$i])) {
			return $this->_res_head[$i];
		}
		return null;
	}

	public function getBody()
	{
		return $this->_res_body;
	}


	// util

	public static function getEOL($data) {
		$eol = "\r\n";
		$index = null;
		$eol_arr = array("\r\n", "\r", "\n");
		foreach ($eol_arr as $eol_tmp) {
			$index_tmp = strpos($data, $eol_tmp);
			if ($index_tmp!==false && ($index==null || $index_tmp<$index)) {
				$eol = $eol_tmp;
				$index = $index_tmp;
			}
		}
		return $eol;
	}
}

 

実行結果

$ php OpenID.php
----- discovery identifier -----
http://yahoo.co.jp

----- discovery xrds url -----
http://open.login.yahoo.co.jp/openid20/www.yahoo.co.jp/xrds

----- discovery xrds data -----
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
    xmlns:xrds="xri://$xrds"
    xmlns:openid="http://openid.net/xmlns/1.0"
    xmlns="xri://$xrd*($v*2.0)">
  <XRD>
    <Service priority="0">
      <Type>http://specs.openid.net/auth/2.0/server</Type>
      <Type>http://specs.openid.net/extensions/pape/1.0</Type>
      <Type>http://openid.net/srv/ax/1.0</Type>
      <Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
      <URI>https://open.login.yahooapis.jp/openid/op/auth</URI>
    </Service>
  </XRD>
</xrds:XRDS>

----- association server url -----
https://open.login.yahooapis.jp/openid/op/auth

----- association params -----
Array
(
    [openid.assoc_type] => HMAC-SHA256
    [openid.dh_consumer_public] => AMSWp1eIDzYQRLI5tByfWXbbfxgZ+loOpu6gyzlOcM2WfpX0+xlMVuYbr/+XxLR3W5qGsR4yJWHA4DZQDW/xE9g/2RCchQVwr2epG8GulS8Zqg0G4l6ojSKLYB3nNcJIe491ZyVTSt46Z1i+qXxHPoQTsZKG1mjdcF1InZ2fAGag
    [openid.dh_gen] => Ag==
    [openid.dh_modulus] => ANz5OguIOXLsDhmYmsWizjEOHTdxfo2Vcbt2I3MYZuYe91ouJ4mLBX+YkcLiemOcPym2CBRYHNOyyjmG0mg3BVd9RcLn5S3IHHoXGHblzqdLFEi/368Ygo79JRnxTkXjgmY0rxlJ5bU1zIKaSDuKdiI+XUkKJX8Fvf8W8vsixYOr
    [openid.mode] => associate
    [openid.ns] => http://specs.openid.net/auth/2.0
    [openid.session_type] => DH-SHA256
)

----- association response -----
Array
(
    [ns] => http://specs.openid.net/auth/2.0
    [assoc_handle] => cM.nSuRQ43D2zXPK0iZjkSeddx.WMFX9kVHfi9ZLbKRpA_zIFFtQuY4rOqhZxiAxgLgLirDZBkCEfPaXG6StRGbINdOsygjvAs8E9f62HsMQN4VCIKedWwx8uBS.StyrV9do4XRSIA--
    [session_type] => DH-SHA256
    [assoc_type] => HMAC-SHA256
    [expires_in] => 14400
    [enc_mac_key] => 5EMa7vCoZspb3aVBre97wkQAXAgshK5s7rL/vWsWCCE=
    [dh_server_public] => EVhRMwS5+7FcNRFQLQ5Ukqynl9Xt/KnjjUOYQMHDArqAyfu14sqmtvFDlwxkJ12i1KAuUg9Qk4G5O4ynveGDac3sItVlxwlgZO0tSspUKouZShBQZEM57qGLQ8/xtE2jteUYBmpH6IIucQiZwoiKRodfZkvK2XQyll8Tb0O4A0o=
)

----- association mac_key -----
EsLGhFoU7MzTqmCzQE9+3rx7y7wfOqf4/3mLVOzuv9w=

----- getAuthorizationParam param -----
Array
(
    [openid.assoc_handle] => cM.nSuRQ43D2zXPK0iZjkSeddx.WMFX9kVHfi9ZLbKRpA_zIFFtQuY4rOqhZxiAxgLgLirDZBkCEfPaXG6StRGbINdOsygjvAs8E9f62HsMQN4VCIKedWwx8uBS.StyrV9do4XRSIA--
    [openid.claimed_id] => http://specs.openid.net/auth/2.0/identifier_select
    [openid.identity] => http://specs.openid.net/auth/2.0/identifier_select
    [openid.mode] => checkid_setup
    [openid.ns] => http://specs.openid.net/auth/2.0
    [openid.realm] => http://localhost/
    [openid.return_to] => http://localhost/
)

https://open.login.yahooapis.jp/openid/op/auth?openid.assoc_handle=cM.nSuRQ43D2zXPK0iZjkSeddx.WMFX9kVHfi9ZLbKRpA_zIFFtQuY4rOqhZxiAxgLgLirDZBkCEfPaXG6StRGbINdOsygjvAs8E9f62HsMQN4VCIKedWwx8uBS.StyrV9do4XRSIA--&openid.claimed_id=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.identity=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0%2Fidentifier_select&openid.mode=checkid_setup&openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.realm=http%3A%2F%2Flocalhost%2F&openid.return_to=http%3A%2F%2Flocalhost%2F

 

参考URL

Final: OpenID Authentication 2.0 - 最終版
http://openid-foundation-japan.github.com/openid-authentication.html
 
Welcome to OpenID Enabled!
http://openidenabled.com/