diff -r 000000000000 -r 4eba9c11703f web/Zend/OpenId.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/Zend/OpenId.php Mon Dec 13 18:29:26 2010 +0100 @@ -0,0 +1,753 @@ + 1 ? $dir : '') + . '/' + . $url; + } + } + } + return $url; + } + + /** + * Converts variable/value pairs into URL encoded query string + * + * @param array $params variable/value pairs + * @return string URL encoded query string + */ + static public function paramsToQuery($params) + { + foreach($params as $key => $value) { + if (isset($query)) { + $query .= '&' . $key . '=' . urlencode($value); + } else { + $query = $key . '=' . urlencode($value); + } + } + return isset($query) ? $query : ''; + } + + /** + * Normalizes URL according to RFC 3986 to use it in comparison operations. + * The function gets URL argument by reference and modifies it. + * It returns true on success and false of failure. + * + * @param string &$id url to be normalized + * @return bool + */ + static public function normalizeUrl(&$id) + { + // RFC 3986, 6.2.2. Syntax-Based Normalization + + // RFC 3986, 6.2.2.2 Percent-Encoding Normalization + $i = 0; + $n = strlen($id); + $res = ''; + while ($i < $n) { + if ($id[$i] == '%') { + if ($i + 2 >= $n) { + return false; + } + ++$i; + if ($id[$i] >= '0' && $id[$i] <= '9') { + $c = ord($id[$i]) - ord('0'); + } else if ($id[$i] >= 'A' && $id[$i] <= 'F') { + $c = ord($id[$i]) - ord('A') + 10; + } else if ($id[$i] >= 'a' && $id[$i] <= 'f') { + $c = ord($id[$i]) - ord('a') + 10; + } else { + return false; + } + ++$i; + if ($id[$i] >= '0' && $id[$i] <= '9') { + $c = ($c << 4) | (ord($id[$i]) - ord('0')); + } else if ($id[$i] >= 'A' && $id[$i] <= 'F') { + $c = ($c << 4) | (ord($id[$i]) - ord('A') + 10); + } else if ($id[$i] >= 'a' && $id[$i] <= 'f') { + $c = ($c << 4) | (ord($id[$i]) - ord('a') + 10); + } else { + return false; + } + ++$i; + $ch = chr($c); + if (($ch >= 'A' && $ch <= 'Z') || + ($ch >= 'a' && $ch <= 'z') || + $ch == '-' || + $ch == '.' || + $ch == '_' || + $ch == '~') { + $res .= $ch; + } else { + $res .= '%'; + if (($c >> 4) < 10) { + $res .= chr(($c >> 4) + ord('0')); + } else { + $res .= chr(($c >> 4) - 10 + ord('A')); + } + $c = $c & 0xf; + if ($c < 10) { + $res .= chr($c + ord('0')); + } else { + $res .= chr($c - 10 + ord('A')); + } + } + } else { + $res .= $id[$i++]; + } + } + + if (!preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?#]*)?((?:[?](?:[^#]*))?)((?:#.*)?)$|', $res, $reg)) { + return false; + } + $scheme = $reg[1]; + $auth = $reg[2]; + $host = $reg[3]; + $port = $reg[4]; + $path = $reg[5]; + $query = $reg[6]; + $fragment = $reg[7]; /* strip it */ + + if (empty($scheme) || empty($host)) { + return false; + } + + // RFC 3986, 6.2.2.1. Case Normalization + $scheme = strtolower($scheme); + $host = strtolower($host); + + // RFC 3986, 6.2.2.3. Path Segment Normalization + if (!empty($path)) { + $i = 0; + $n = strlen($path); + $res = ""; + while ($i < $n) { + if ($path[$i] == '/') { + ++$i; + while ($i < $n && $path[$i] == '/') { + ++$i; + } + if ($i < $n && $path[$i] == '.') { + ++$i; + if ($i < $n && $path[$i] == '.') { + ++$i; + if ($i == $n || $path[$i] == '/') { + if (($pos = strrpos($res, '/')) !== false) { + $res = substr($res, 0, $pos); + } + } else { + $res .= '/..'; + } + } else if ($i != $n && $path[$i] != '/') { + $res .= '/.'; + } + } else { + $res .= '/'; + } + } else { + $res .= $path[$i++]; + } + } + $path = $res; + } + + // RFC 3986,6.2.3. Scheme-Based Normalization + if ($scheme == 'http') { + if ($port == 80) { + $port = ''; + } + } else if ($scheme == 'https') { + if ($port == 443) { + $port = ''; + } + } + if (empty($path)) { + $path = '/'; + } + + $id = $scheme + . '://' + . $auth + . $host + . (empty($port) ? '' : (':' . $port)) + . $path + . $query; + return true; + } + + /** + * Normalizes OpenID identifier that can be URL or XRI name. + * Returns true on success and false of failure. + * + * Normalization is performed according to the following rules: + * 1. If the user's input starts with one of the "xri://", "xri://$ip*", + * or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs + * are used in the canonical form, and URI-authority XRIs are further + * considered URL identifiers. + * 2. If the first character of the resulting string is an XRI Global + * Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be + * treated as an XRI. + * 3. Otherwise, the input SHOULD be treated as an http URL; if it does + * not include a "http" or "https" scheme, the Identifier MUST be + * prefixed with the string "http://". + * 4. URL identifiers MUST then be further normalized by both following + * redirects when retrieving their content and finally applying the + * rules in Section 6 of [RFC3986] to the final destination URL. + * @param string &$id identifier to be normalized + * @return bool + */ + static public function normalize(&$id) + { + $id = trim($id); + if (strlen($id) === 0) { + return true; + } + + // 7.2.1 + if (strpos($id, 'xri://$ip*') === 0) { + $id = substr($id, strlen('xri://$ip*')); + } else if (strpos($id, 'xri://$dns*') === 0) { + $id = substr($id, strlen('xri://$dns*')); + } else if (strpos($id, 'xri://') === 0) { + $id = substr($id, strlen('xri://')); + } + + // 7.2.2 + if ($id[0] == '=' || + $id[0] == '@' || + $id[0] == '+' || + $id[0] == '$' || + $id[0] == '!') { + return true; + } + + // 7.2.3 + if (strpos($id, "://") === false) { + $id = 'http://' . $id; + } + + // 7.2.4 + return self::normalizeURL($id); + } + + /** + * Performs a HTTP redirection to specified URL with additional data. + * It may generate redirected request using GET or POST HTTP method. + * The function never returns. + * + * @param string $url URL to redirect to + * @param array $params additional variable/value pairs to send + * @param Zend_Controller_Response_Abstract $response + * @param string $method redirection method ('GET' or 'POST') + */ + static public function redirect($url, $params = null, + Zend_Controller_Response_Abstract $response = null, $method = 'GET') + { + $url = Zend_OpenId::absoluteUrl($url); + $body = ""; + if (null === $response) { + require_once "Zend/Controller/Response/Http.php"; + $response = new Zend_Controller_Response_Http(); + } + + if ($method == 'POST') { + $body = "
\n"; + $body .= "\n"; + } else if (is_array($params) && count($params) > 0) { + if (strpos($url, '?') === false) { + $url .= '?' . self::paramsToQuery($params); + } else { + $url .= '&' . self::paramsToQuery($params); + } + } + if (!empty($body)) { + $response->setBody($body); + } else if (!$response->canSendHeaders()) { + $response->setBody(""); + } else { + $response->setRedirect($url); + } + $response->sendResponse(); + if (self::$exitOnRedirect) { + exit(); + } + } + + /** + * Produces string of random byte of given length. + * + * @param integer $len length of requested string + * @return string RAW random binary string + */ + static public function randomBytes($len) + { + $key = ''; + for($i=0; $i < $len; $i++) { + $key .= chr(mt_rand(0, 255)); + } + return $key; + } + + /** + * Generates a hash value (message digest) according to given algorithm. + * It returns RAW binary string. + * + * This is a wrapper function that uses one of available internal function + * dependent on given PHP configuration. It may use various functions from + * ext/openssl, ext/hash, ext/mhash or ext/standard. + * + * @param string $func digest algorithm + * @param string $data data to sign + * @return string RAW digital signature + * @throws Zend_OpenId_Exception + */ + static public function digest($func, $data) + { + if (function_exists('openssl_digest')) { + return openssl_digest($data, $func, true); + } else if (function_exists('hash')) { + return hash($func, $data, true); + } else if ($func === 'sha1') { + return sha1($data, true); + } else if ($func === 'sha256') { + if (function_exists('mhash')) { + return mhash(MHASH_SHA256 , $data); + } + } + require_once "Zend/OpenId/Exception.php"; + throw new Zend_OpenId_Exception( + 'Unsupported digest algorithm "' . $func . '".', + Zend_OpenId_Exception::UNSUPPORTED_DIGEST); + } + + /** + * Generates a keyed hash value using the HMAC method. It uses ext/hash + * if available or user-level PHP implementation, that is not significantly + * slower. + * + * @param string $macFunc name of selected hashing algorithm (sha1, sha256) + * @param string $data data to sign + * @param string $secret shared secret key used for generating the HMAC + * variant of the message digest + * @return string RAW HMAC value + */ + static public function hashHmac($macFunc, $data, $secret) + { +// require_once "Zend/Crypt/Hmac.php"; +// return Zend_Crypt_Hmac::compute($secret, $macFunc, $data, Zend_Crypt_Hmac::BINARY); + if (function_exists('hash_hmac')) { + return hash_hmac($macFunc, $data, $secret, 1); + } else { + if (Zend_OpenId::strlen($secret) > 64) { + $secret = self::digest($macFunc, $secret); + } + $secret = str_pad($secret, 64, chr(0x00)); + $ipad = str_repeat(chr(0x36), 64); + $opad = str_repeat(chr(0x5c), 64); + $hash1 = self::digest($macFunc, ($secret ^ $ipad) . $data); + return self::digest($macFunc, ($secret ^ $opad) . $hash1); + } + } + + /** + * Converts binary representation into ext/gmp or ext/bcmath big integer + * representation. + * + * @param string $bin binary representation of big number + * @return mixed + * @throws Zend_OpenId_Exception + */ + static protected function binToBigNum($bin) + { + if (extension_loaded('gmp')) { + return gmp_init(bin2hex($bin), 16); + } else if (extension_loaded('bcmath')) { + $bn = 0; + $len = Zend_OpenId::strlen($bin); + for ($i = 0; $i < $len; $i++) { + $bn = bcmul($bn, 256); + $bn = bcadd($bn, ord($bin[$i])); + } + return $bn; + } + require_once "Zend/OpenId/Exception.php"; + throw new Zend_OpenId_Exception( + 'The system doesn\'t have proper big integer extension', + Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH); + } + + /** + * Converts internal ext/gmp or ext/bcmath big integer representation into + * binary string. + * + * @param mixed $bn big number + * @return string + * @throws Zend_OpenId_Exception + */ + static protected function bigNumToBin($bn) + { + if (extension_loaded('gmp')) { + $s = gmp_strval($bn, 16); + if (strlen($s) % 2 != 0) { + $s = '0' . $s; + } else if ($s[0] > '7') { + $s = '00' . $s; + } + return pack("H*", $s); + } else if (extension_loaded('bcmath')) { + $cmp = bccomp($bn, 0); + if ($cmp == 0) { + return "\0"; + } else if ($cmp < 0) { + require_once "Zend/OpenId/Exception.php"; + throw new Zend_OpenId_Exception( + 'Big integer arithmetic error', + Zend_OpenId_Exception::ERROR_LONG_MATH); + } + $bin = ""; + while (bccomp($bn, 0) > 0) { + $bin = chr(bcmod($bn, 256)) . $bin; + $bn = bcdiv($bn, 256); + } + if (ord($bin[0]) > 127) { + $bin = "\0" . $bin; + } + return $bin; + } + require_once "Zend/OpenId/Exception.php"; + throw new Zend_OpenId_Exception( + 'The system doesn\'t have proper big integer extension', + Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH); + } + + /** + * Performs the first step of a Diffie-Hellman key exchange by generating + * private and public DH values based on given prime number $p and + * generator $g. Both sides of key exchange MUST have the same prime number + * and generator. In this case they will able to create a random shared + * secret that is never send from one to the other. + * + * @param string $p prime number in binary representation + * @param string $g generator in binary representation + * @param string $priv_key private key in binary representation + * @return mixed + */ + static public function createDhKey($p, $g, $priv_key = null) + { + if (function_exists('openssl_dh_compute_key')) { + $dh_details = array( + 'p' => $p, + 'g' => $g + ); + if ($priv_key !== null) { + $dh_details['priv_key'] = $priv_key; + } + return openssl_pkey_new(array('dh'=>$dh_details)); + } else { + $bn_p = self::binToBigNum($p); + $bn_g = self::binToBigNum($g); + if ($priv_key === null) { + $priv_key = self::randomBytes(Zend_OpenId::strlen($p)); + } + $bn_priv_key = self::binToBigNum($priv_key); + if (extension_loaded('gmp')) { + $bn_pub_key = gmp_powm($bn_g, $bn_priv_key, $bn_p); + } else if (extension_loaded('bcmath')) { + $bn_pub_key = bcpowmod($bn_g, $bn_priv_key, $bn_p); + } + $pub_key = self::bigNumToBin($bn_pub_key); + + return array( + 'p' => $bn_p, + 'g' => $bn_g, + 'priv_key' => $bn_priv_key, + 'pub_key' => $bn_pub_key, + 'details' => array( + 'p' => $p, + 'g' => $g, + 'priv_key' => $priv_key, + 'pub_key' => $pub_key)); + } + } + + /** + * Returns an associative array with Diffie-Hellman key components in + * binary representation. The array includes original prime number 'p' and + * generator 'g', random private key 'priv_key' and corresponding public + * key 'pub_key'. + * + * @param mixed $dh Diffie-Hellman key + * @return array + */ + static public function getDhKeyDetails($dh) + { + if (function_exists('openssl_dh_compute_key')) { + $details = openssl_pkey_get_details($dh); + if (isset($details['dh'])) { + return $details['dh']; + } + } else { + return $dh['details']; + } + } + + /** + * Computes the shared secret from the private DH value $dh and the other + * party's public value in $pub_key + * + * @param string $pub_key other party's public value + * @param mixed $dh Diffie-Hellman key + * @return string + * @throws Zend_OpenId_Exception + */ + static public function computeDhSecret($pub_key, $dh) + { + if (function_exists('openssl_dh_compute_key')) { + $ret = openssl_dh_compute_key($pub_key, $dh); + if (ord($ret[0]) > 127) { + $ret = "\0" . $ret; + } + return $ret; + } else if (extension_loaded('gmp')) { + $bn_pub_key = self::binToBigNum($pub_key); + $bn_secret = gmp_powm($bn_pub_key, $dh['priv_key'], $dh['p']); + return self::bigNumToBin($bn_secret); + } else if (extension_loaded('bcmath')) { + $bn_pub_key = self::binToBigNum($pub_key); + $bn_secret = bcpowmod($bn_pub_key, $dh['priv_key'], $dh['p']); + return self::bigNumToBin($bn_secret); + } + require_once "Zend/OpenId/Exception.php"; + throw new Zend_OpenId_Exception( + 'The system doesn\'t have proper big integer extension', + Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH); + } + + /** + * Takes an arbitrary precision integer and returns its shortest big-endian + * two's complement representation. + * + * Arbitrary precision integers MUST be encoded as big-endian signed two's + * complement binary strings. Henceforth, "btwoc" is a function that takes + * an arbitrary precision integer and returns its shortest big-endian two's + * complement representation. All integers that are used with + * Diffie-Hellman Key Exchange are positive. This means that the left-most + * bit of the two's complement representation MUST be zero. If it is not, + * implementations MUST add a zero byte at the front of the string. + * + * @param string $str binary representation of arbitrary precision integer + * @return string big-endian signed representation + */ + static public function btwoc($str) + { + if (ord($str[0]) > 127) { + return "\0" . $str; + } + return $str; + } + + /** + * Returns lenght of binary string in bytes + * + * @param string $str + * @return int the string lenght + */ + static public function strlen($str) + { + if (extension_loaded('mbstring') && + (((int)ini_get('mbstring.func_overload')) & 2)) { + return mb_strlen($str, 'latin1'); + } else { + return strlen($str); + } + } + +}