最新のPHPニュース

PHPのOpenSSL関数を使って、OpenIDとTypeKey認証を実装するコード

2007年02月13日

Wez Furlong氏による、OpenIDとTypeKey認証を行うための、OpenSSLエクステンションのパッチが公開されています。このパッチを使用すると、PHPのアプリケーションにて、OpenIDとTypeKey認証を簡単に実装できます。

パッチ自体は、このURLからダウンロードできます。Furlong氏のサーバー上でのPHP5にて動作を確認されているという事ですが、PHP4でも動作するだろうと、同氏は述べています。

Associateリクエスト

Associateリクエストを行い、キーを生成、交換することで、OpenIDサーバーとの関係を構築できます。このリクエストの結果は、同じサーバーを認証する場合の、ユーザー認証に使用できます。このコードは以下のようになります。

<?php
  $assoc = array(); 
  $crypto = array(); 
  $dh = openssl_dh_generate_key(OPENID_P_VALUE, '2'); 
  foreach (openssl_dh_get_params($dh) as $n => $v) { 
    $crypto[$n] = openssl_bignum_to_string($v, 10); 
  } 
  $params = array( 
     'openid.mode' => 'associate', 
     'openid.assoc_type' => 'HMAC-SHA1', 
     'openid.session_type' => 'DH-SHA1', 
     'openid.dh_modulus' => base64_encode( 
                openssl_bignum_to_string(OPENID_P_VALUE)), 
     'openid.dh_gen' => base64_encode( 
                openssl_bignum_to_string('2')), 
     'openid.dh_consumer_public' => base64_encode( 
                openssl_bignum_to_string($crypto['pub_key'])), 
  ); 
  $r = perform_openid_rpc($server, $params); // サーバーと通信する
  if ($r['session_type'] == 'DH-SHA1') { 
    $s_pub = openssl_bignum_from_bin( 
               base64_decode($r['dh_server_public'])); 
    $dh_sec = openssl_dh_compute_key($dh, $s_pub); 
    if ($dh_sec === false) { 
      do { 
        $err = openssl_error_string(); 
        if ($err === false) { 
          break; 
        } 
        echo "$err<br>\n"; 
      } while (true); 
    } 
    $sh_sec = sha1($dh_sec, true); 
    $enc_mac = base64_decode($r['enc_mac_key']); 
    $secret = ''; 
    for ($i = 0; $i < strlen($enc_mac); $i++) { 
      $secret .= chr(ord($enc_mac[$i]) ^ ord($sh_sec[$i])); 
    } 
    $assoc['secret'] = $secret; 
    $assoc['handle']  = $r['assoc_handle']; 
    $assoc['assoc_type'] = $r['assoc_type']; 
    $assoc['expires'] = time() + $r['expires_in']; 
  } else { 
    $assoc = false; 
  } 
?>

OpenIDサーバーにて認証を行ったら、先ほど$returnURLに指定したURLに、リダイレクトで戻ってきます。

<?php 
    $assoc = $this->associate($args['srv']); 
    $token_contents = ''; 
    /* token_contentsハッシュの名前は、接頭辞が付いていないことに注意が必要です */
    foreach (explode(',', $_GET['openid_signed']) as $name) { 
      if ($name == 'return_to') { 
        $token_contents .= "$name:" . $_GET['openid_return_to'] . "\n"; 
      } else { 
        $token_contents .= "$name:" . $_GET["openid_" . str_replace('.', '_', $name)] . "\n"; 
      } 
    } 
    $x = hash_hmac('sha1', $token_contents, $assoc['secret'], true); 
    $hash = base64_encode($x); 
    if ($hash === $_GET['openid_sig']) { 
      // 認証が成功した
      return true; 
    } 
    /* 何らかの理由で認証が失敗した */ 
    $params = array(); 
    $signed = explode(',', $_GET['openid_signed']); 
    $signed = array_merge($signed, array('assoc_handle', 'sig', 'signed', 'invalidate_handle')); 
    foreach ($signed as $name) { 
      $k = "openid_" . str_replace('.', '_', $name); 
      if (array_key_exists($k, $_GET)) { 
        $params["openid.$name"] = $_GET[$k]; 
      } 
    } 
    $server = $args['srv']; 
    /* 証明書が壊れている。
     * openid.modeをcheck_authentificationにして、認証のチェックを行う
     */ 
    $params['openid.mode'] = 'check_authentication'; 
    $res = perform_openid_rpc($server, $params); 
    if (isset($res['invalidate_handle'])) { 
      if ($res['invalidate_handle'] === $assoc['handle']) { 
        $this->associate($server, true); 
      } 
    } 
    return $res['is_valid'] === 'true'; 
?>

TypeKeyへの対応

TypeKeyからリダイレクトで戻ってきた際、シグネチャーを検証するスクリプトは以下の通りになります。

<?php 
    $keydata = array(); 
    $regkeys = cache::httpGet('http://www.typekey.com/extras/regkeys.txt', 24*60*60); 
    if ($regkeys === false) { 
       die("urgh"); 
    } 
    foreach (explode(' ', $regkeys) as $pair) { 
      list($k, $v) = explode('=', trim($pair)); 
      $keydata[$k] = $v; 
    } 
    $sig = str_replace(' ', '+', $_GET['sig']); 
    $email = $_GET['email']; 
    $name = $_GET['name']; 
    $nick = $_GET['nick']; 
    $ts = $_GET['ts']; 
    $msg = "$email::$name::$nick::$ts::" . TYPEKEY_TOKEN; 
    if (time() - $ts > 300) { 
      die("possible replay"); 
    } 
    list($r_sig, $s_sig) = explode(':', $sig, 2); 
    $r_sig = base64_decode($r_sig); 
    $s_sig = base64_decode($s_sig); 
    $valid = openssl_dsa_verify(sha1($msg, true), 
                     openssl_bignum_from_bin($r_sig), 
                     openssl_bignum_from_bin($s_sig), 
                     $keydata['p'], $keydata['q'], 
                     $keydata['g'], $keydata['pub_key']); 
?>

関連リンク

この記事へのトラックバックURL