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