Revision: 2731
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
at April 9, 2007 15:56 by Jargon
Initial Code
<? # Copyright (c) 2005 JP Sugarbroad # Permission is granted to use this in any way you want, provided that you # include the above copyright notice (or one with similar effect) in any work # including non-trivial parts of this one. # Change history: # 20050706.0: Fixed XTEA routines and signatures # 20050707.0: More XTEA fixes # 20050707.1: valid_root fix (thanks to meepbear) # 20050708.0: Fix cookie check # 20050708.1: Remove issued field from token # 20050713.0: Update to new protocol, added example login code # 20070110.0: Fix a bug in the XTEA implementation # (changed a = intval(a + b) to a = intval(a + (b)) define("SIGKEY", "something"); define("COOKIE_NAME", "secret"); define("COOKIE_VALUE", "cookie"); define("VALID_TIME", 5 * 60); # token validity time define("ASSOC_TIME", 15 * 60); # assocation validity time define("KEY_LEN", 20); # Don't touch me function t2utc($t) { return gmdate("Y-m-d\\TH:i:s\\Z", $t); } function utc2t($s) { return strtotime(str_replace("T", " ", $s)); } # * is NOT OK # *.x is NOT OK # *.x.<tld_list non-element> is NOT OK # Anything else is OK # List from: http://www.iana.org/gtld/gtld.htm $tld_list = array( "aero" => 1, "biz" => 1, "com" => 1, "coop" => 1, "edu" => 1, "gov" => 1, "info" => 1, "int" => 1, "mil" => 1, "museum" => 1, "name" => 1, "net" => 1, "org" => 1, "pro" => 1); function valid_wildcard($h) { switch (strrpos($h, "*")) { case false: # Not wildcard return true; case 0: # Wildcard break; default: # * not at start return false; } $h = explode(".", $h); if ($h[0] != "*") return false; # *xyz.stuff is bad switch (count($h)) { case 0: case 1: case 2: return false; case 3: return array_key_exists($h[-1], $tld_list); default: return true; } } function valid_root($root, $ret) { $root = parse_url($root); if (isset($root["fragment"])) return false; $ret = parse_url($ret); if ($root["scheme"] != $ret["scheme"]) return false; if ($root["port"] != $ret["port"]) return false; if (isset($root["user"]) && $root["user"] != $ret["user"]) return false; if (isset($root["pass"]) && $root["pass"] != $ret["pass"]) return false; if (isset($root["query"]) && $root["query"] != $ret["query"]) return false; $h = $root["host"]; if (!valid_wildcard($h)) return false; if ($h[0] == "*") { $hn = strlen($h) - 1; if (substr($h, 2) != substr($ret["host"], -$hn, $hn)) return false; } else { if ($h != $ret["host"]) return false; } $p1 = explode("/", rtrim($root["path"], "/")); $p2 = explode("/", rtrim($ret["path"], "/")); foreach ($p1 as $k => $v) { if ($p2[$k] != $v) return false; } return true; } function randbytes($n) { $r = fopen("/dev/urandom", "rb"); $s = fread($r, $n); fclose($r); return $s; } function xtea_block($k, $v) { list(, $v0, $v1) = unpack("N*", $v); $sum = 0; $delta = 0x9E3779B9; for ($i = 0; $i < 32; $i++) { $v0 = intval($v0 + (($v1 << 4 ^ $v1 >> 5) + $v1 ^ $sum + $k[$sum & 3])); $sum = intval($sum + $delta); $v1 = intval($v1 + (($v0 << 4 ^ $v0 >> 5) + $v0 ^ $sum + $k[$sum >> 11 & 3])); } return pack("N2", $v0, $v1); } function xtea_encrypt($key, $data) { $key = array_merge(unpack("N*", str_pad($key, 16, chr(0)))); $v = randbytes(8); $out = $v; $i = 0; $l = strlen($data); while ($i < $l) { $v = xtea_block($key, $v); $p = substr($data, $i, 8); $i += 8; $v ^= $p; $out .= $v; } return $out; } function xtea_decrypt($key, $data) { $key = array_merge(unpack("N*", str_pad($key, 16, chr(0)))); $v = substr($data, 0, 8); $i = 8; $l = strlen($data); $out = ""; while ($i < $l) { $v = xtea_block($key, $v); $c = substr($data, $i, 8); $i += 8; $out .= $v ^ $c; $v = $c; } return $out; } define("HASH_LEN", 20); function hmac($key, $str) { $key = str_pad($key, 64, chr(0)); $ipad = $key ^ str_repeat(chr(0x36), 64); $opad = $key ^ str_repeat(chr(0x5C), 64); return pack("H*", sha1($opad . pack("H*", sha1($ipad . $str)))); } function sign_array($key, $data) { $token = ""; foreach (explode(",", $data["signed"]) as $f) { $token .= "$f:$data[$f]\n"; } return base64_encode(hmac($key, $token)); } function make_handle($expiry, $exposed, $key) { $token = pack("lc", $expiry, $exposed ? 1 : 0) . $key; return base64_encode(xtea_encrypt(SIGKEY, hmac(SIGKEY, $token) . $token)); } function check_handle($bh, $exposed_ok) { $handle = base64_decode($bh); # IV + HMAC + expiry + exposed if (!$handle || strlen($handle) < 8 + HASH_LEN + 5) return false; $handle = xtea_decrypt(SIGKEY, $handle); $data = substr($handle, HASH_LEN); if (hmac(SIGKEY, $data) != substr($handle, 0, HASH_LEN)) return false; $t = unpack("lexpiry/cexposed", $data); if ($t["expiry"] < time() || $t["exposed"] && !$exposed_ok) return false; return substr($data, 5); } function make_args($prefix, $data) { $url = ""; foreach ($data as $k => $v) { $url .= "$prefix$k=" . urlencode($v) . "&"; } return rtrim($url, "&"); } function continuation() { $url = ""; foreach ($_REQUEST as $k => $v) { if (strncmp($k, "openid_", 7) || $k == "openid_mode") continue; $url .= "&openid." . substr($k, 7) . "=" . urlencode($v); } return $url; } $ret = $_REQUEST["openid_return_to"]; if ($ret) { if (!preg_match("/^https?:/", $ret)) { header("HTTP/1.0 400 Bad Request"); ?><html> <head><title>Bad Request</title></head> <body><p>The OpenID endpoint received an invalid request.</p></body> </html><? exit; } if (strchr($ret, "?")) { $retp = "$ret&"; } else { $retp = "$ret?"; } } $self = "http://" . $_SERVER["SERVER_NAME"]; if ($_SERVER["SERVER_PORT"] != 80) $self .= ":" . $SERVER["SERVER_PORT"]; $self .= "$_SERVER[PHP_SELF]?"; function badreq($msg) { global $ret, $retp; if ($_SERVER["REQUEST_METHOD"] == "POST") { header("HTTP/1.0 400 Bad Request"); print "error:$msg\n"; exit; } if ($ret) { header("Location: " . $retp . "openid.mode=error&openid.error=" . urlencode($msg)); } else { header("HTTP/1.0 400 Bad Request"); } ?><html> <head><title>OpenID endpoint</title></head> <body><p>This is an OpenID server endpoint, not a human-readable resource. For more information, see <a href="http://openid.net/">http://openid.net/</a>.</p> <? if ($msg) { ?> <p>An error occurred processing your request: <?=htmlspecialchars($msg)?></body> <? } ?> </html><? exit; } $mode = $_REQUEST["openid_mode"]; switch ($mode) { case "check_authentication": $resp = array(); $sig = $_REQUEST["openid_signed"]; $resp["signed"] = $sig; foreach (explode(",", $sig) as $f) { $resp[$f] = $_REQUEST["openid_" . $f]; } $resp["mode"] = "id_res"; header("Content-Type: text/plain"); $key = check_handle($_REQUEST["openid_assoc_handle"], false); if ($key && $_REQUEST["openid_sig"] == sign_array($key, $resp)) { $l = max(0, utc2t($resp["valid_to"]) - time()); print "is_valid:" . ($l ? 1 : 0) . "\nlifetime:" . $l . "\n"; } else { print "is_valid:0\nlifetime:0\n"; } $ih = $_REQUEST["openid_invalidate_handle"]; if ($ih && !check_handle($ih, true)) { print "invalidate_handle:$ih\n"; } exit; case "associate": $t = $_REQUEST["openid_assoc_type"]; if (isset($t) && $t != "HMAC-SHA1") badreq("Unknown association type"); $t = time(); $e = $t + ASSOC_TIME; $r = randbytes(KEY_LEN); $handle = make_handle($e, true, $r); header("Content-Type: text/plain"); print "assoc_type:HMAC-SHA1\nassoc_handle:" . $handle . "\nissued:" . t2utc($t) . # COMPAT "\nexpiry:" . t2utc($e) . # COMPAT "\nexpires_in:" . ASSOC_TIME . "\nmac_key:" . base64_encode($r) . "\n"; exit; case "login": case "checkid_immediate": case "checkid_setup": if ($_SERVER["REQUEST_METHOD"] != "GET") { badreq("Mode $mode requires GET method"); } break; case null: badreq(null); default: badreq("Unknown mode $mode"); } if (!$ret) badreq("return_to required"); # If we have checkid_setup issue a redirect with mode login, then we don't # have to make this a function. But that's an extra redirect we don't need. function login() { global $mode, $retp, $self; # Temporary code setcookie(COOKIE_NAME, COOKIE_VALUE, 0, $_SERVER["PHP_SELF"]); $_COOKIE[COOKIE_NAME] = COOKIE_VALUE; $mode = "checkid_immediate"; # login.php should set the cookie correctly and then send the user back to # the return_to parameter. Don't use a redirect, that can cause a redirect # loop. Change checkid_immediate to login_cancel if they cancel the login. # (Use substr_replace, not str_replace, to avoid corrupting the # continuation.) # $url = $self . "openid.mode=checkid_immediate" . continuation(); # header("Location: http://host/path/to/login.php?return_to=" . urlencode($url)); # exit; } if ($mode == "login") login(); if ($mode == "login_cancel") { header("Location: " . $retp . "openid.mode=cancel"); exit; } if ($_COOKIE[COOKIE_NAME] != COOKIE_VALUE) { if ($mode == "checkid_setup") { login(); } else { $url = "http://$_SERVER[SERVER_NAME]$_SERVER[PHP_SELF]?openid.mode=login" . continuation(); $url = $retp . "openid.mode=id_res&openid.user_setup_url=" . urlencode($url); header("Location: $url"); ?><html> <head><title>Login required</title></head> <body><p>You need to <a href="<?=htmlspecialchars($url)?>">log in</a> to be authenticated.</p></body> </html><? exit; } } $id = $_REQUEST["openid_identity"]; #if ($id != "http://taral.net/") badreq("Unrecognized identity"); $root = $_REQUEST["openid_trust_root"]; if (!$root || !valid_root($root, $ret)) { $root = $ret; } switch ($root) { case "http://www.lifewiki.net/": case "http://*.danga.com/openid/demo/": break; default: badreq("Requester not trusted"); } $t = time(); $resp = array( "mode" => "id_res", "identity" => $id, "issued" => t2utc($t), # COMPAT "valid_to" => t2utc($t + VALID_TIME), "return_to" => $ret, "signed" => "mode,issued,valid_to,identity,return_to"); $handle = $_REQUEST["openid_assoc_handle"]; if ($handle) { $key = check_handle($handle, true); if ($key == false) $resp["invalidate_handle"] = $handle; } if (!$key) { $key = randbytes(KEY_LEN); $handle = make_handle($t + ASSOC_TIME, false, $key); } $resp["sig"] = sign_array($key, $resp); $resp["assoc_handle"] = $handle; $url = $retp . make_args("openid.", $resp); header("Location: $url"); ?><html> <head><title>Authentication OK</title></head> <body><p>Authentication complete. <a href="<?=htmlspecialchars($url)?>">Click here to proceed</a></p></body> </html>
Initial URL
Initial Description
Not sure if this works yet
Initial Title
Single-Page OpenID server
Initial Tags
php
Initial Language
PHP