* @copyright 2005-2008 Janrain, Inc. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache */ /** * Needed for random number generation */ require_once 'Auth/OpenID/CryptUtil.php'; /** * Need Auth_OpenID::bytes(). */ require_once 'Auth/OpenID.php'; /** * The superclass of all big-integer math implementations * @access private * @package OpenID */ class Auth_OpenID_MathLibrary { /** * Given a long integer, returns the number converted to a binary * string. This function accepts long integer values of arbitrary * magnitude and uses the local large-number math library when * available. * * @param integer $long The long number (can be a normal PHP * integer or a number created by one of the available long number * libraries) * @return string $binary The binary version of $long */ function longToBinary($long) { $cmp = $this->cmp($long, 0); if ($cmp < 0) { $msg = __FUNCTION__ . " takes only positive integers."; trigger_error($msg, E_USER_ERROR); return null; } if ($cmp == 0) { return "\x00"; } $bytes = array(); while ($this->cmp($long, 0) > 0) { array_unshift($bytes, $this->mod($long, 256)); $long = $this->div($long, pow(2, 8)); } if ($bytes && ($bytes[0] > 127)) { array_unshift($bytes, 0); } $string = ''; foreach ($bytes as $byte) { $string .= pack('C', $byte); } return $string; } /** * Given a binary string, returns the binary string converted to a * long number. * * @param string $binary The binary version of a long number, * probably as a result of calling longToBinary * @return integer $long The long number equivalent of the binary * string $str */ function binaryToLong($str) { if ($str === null) { return null; } // Use array_merge to return a zero-indexed array instead of a // one-indexed array. $bytes = array_merge(unpack('C*', $str)); $n = $this->init(0); if ($bytes && ($bytes[0] > 127)) { trigger_error("bytesToNum works only for positive integers.", E_USER_WARNING); return null; } foreach ($bytes as $byte) { $n = $this->mul($n, pow(2, 8)); $n = $this->add($n, $byte); } return $n; } function base64ToLong($str) { $b64 = base64_decode($str); if ($b64 === false) { return false; } return $this->binaryToLong($b64); } function longToBase64($str) { return base64_encode($this->longToBinary($str)); } /** * Returns a random number in the specified range. This function * accepts $start, $stop, and $step values of arbitrary magnitude * and will utilize the local large-number math library when * available. * * @param integer $start The start of the range, or the minimum * random number to return * @param integer $stop The end of the range, or the maximum * random number to return * @param integer $step The step size, such that $result - ($step * * N) = $start for some N * @return integer $result The resulting randomly-generated number */ function rand($stop) { static $duplicate_cache = array(); // Used as the key for the duplicate cache $rbytes = $this->longToBinary($stop); if (array_key_exists($rbytes, $duplicate_cache)) { list($duplicate, $nbytes) = $duplicate_cache[$rbytes]; } else { if ($rbytes[0] == "\x00") { $nbytes = Auth_OpenID::bytes($rbytes) - 1; } else { $nbytes = Auth_OpenID::bytes($rbytes); } $mxrand = $this->pow(256, $nbytes); // If we get a number less than this, then it is in the // duplicated range. $duplicate = $this->mod($mxrand, $stop); if (count($duplicate_cache) > 10) { $duplicate_cache = array(); } $duplicate_cache[$rbytes] = array($duplicate, $nbytes); } do { $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes); $n = $this->binaryToLong($bytes); // Keep looping if this value is in the low duplicated range } while ($this->cmp($n, $duplicate) < 0); return $this->mod($n, $stop); } } /** * Exposes BCmath math library functionality. * * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided * by the BCMath extension. * * @access private * @package OpenID */ class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{ var $type = 'bcmath'; function add($x, $y) { return bcadd($x, $y); } function sub($x, $y) { return bcsub($x, $y); } function pow($base, $exponent) { return bcpow($base, $exponent); } function cmp($x, $y) { return bccomp($x, $y); } function init($number, $base = 10) { return $number; } function mod($base, $modulus) { return bcmod($base, $modulus); } function mul($x, $y) { return bcmul($x, $y); } function div($x, $y) { return bcdiv($x, $y); } /** * Same as bcpowmod when bcpowmod is missing * * @access private */ function _powmod($base, $exponent, $modulus) { $square = $this->mod($base, $modulus); $result = 1; while($this->cmp($exponent, 0) > 0) { if ($this->mod($exponent, 2)) { $result = $this->mod($this->mul($result, $square), $modulus); } $square = $this->mod($this->mul($square, $square), $modulus); $exponent = $this->div($exponent, 2); } return $result; } function powmod($base, $exponent, $modulus) { if (function_exists('bcpowmod')) { return bcpowmod($base, $exponent, $modulus); } else { return $this->_powmod($base, $exponent, $modulus); } } function toString($num) { return $num; } } /** * Exposes GMP math library functionality. * * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided * by the GMP extension. * * @access private * @package OpenID */ class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{ var $type = 'gmp'; function add($x, $y) { return gmp_add($x, $y); } function sub($x, $y) { return gmp_sub($x, $y); } function pow($base, $exponent) { return gmp_pow($base, $exponent); } function cmp($x, $y) { return gmp_cmp($x, $y); } function init($number, $base = 10) { return gmp_init($number, $base); } function mod($base, $modulus) { return gmp_mod($base, $modulus); } function mul($x, $y) { return gmp_mul($x, $y); } function div($x, $y) { return gmp_div_q($x, $y); } function powmod($base, $exponent, $modulus) { return gmp_powm($base, $exponent, $modulus); } function toString($num) { return gmp_strval($num); } } /** * Define the supported extensions. An extension array has keys * 'modules', 'extension', and 'class'. 'modules' is an array of PHP * module names which the loading code will attempt to load. These * values will be suffixed with a library file extension (e.g. ".so"). * 'extension' is the name of a PHP extension which will be tested * before 'modules' are loaded. 'class' is the string name of a * {@link Auth_OpenID_MathWrapper} subclass which should be * instantiated if a given extension is present. * * You can define new math library implementations and add them to * this array. */ function Auth_OpenID_math_extensions() { $result = array(); if (!defined('Auth_OpenID_BUGGY_GMP')) { $result[] = array('modules' => array('gmp', 'php_gmp'), 'extension' => 'gmp', 'class' => 'Auth_OpenID_GmpMathWrapper'); } $result[] = array('modules' => array('bcmath', 'php_bcmath'), 'extension' => 'bcmath', 'class' => 'Auth_OpenID_BcMathWrapper'); return $result; } /** * Detect which (if any) math library is available */ function Auth_OpenID_detectMathLibrary($exts) { $loaded = false; foreach ($exts as $extension) { if (extension_loaded($extension['extension'])) { return $extension; } } return false; } /** * {@link Auth_OpenID_getMathLib} checks for the presence of long * number extension modules and returns an instance of * {@link Auth_OpenID_MathWrapper} which exposes the module's * functionality. * * Checks for the existence of an extension module described by the * result of {@link Auth_OpenID_math_extensions()} and returns an * instance of a wrapper for that extension module. If no extension * module is found, an instance of {@link Auth_OpenID_MathWrapper} is * returned, which wraps the native PHP integer implementation. The * proper calling convention for this method is $lib = * Auth_OpenID_getMathLib(). * * This function checks for the existence of specific long number * implementations in the following order: GMP followed by BCmath. * * @return Auth_OpenID_MathWrapper $instance An instance of * {@link Auth_OpenID_MathWrapper} or one of its subclasses * * @package OpenID */ function Auth_OpenID_getMathLib() { // The instance of Auth_OpenID_MathWrapper that we choose to // supply will be stored here, so that subseqent calls to this // method will return a reference to the same object. static $lib = null; if (isset($lib)) { return $lib; } if (Auth_OpenID_noMathSupport()) { $null = null; return $null; } // If this method has not been called before, look at // Auth_OpenID_math_extensions and try to find an extension that // works. $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions()); if ($ext === false) { $tried = array(); foreach (Auth_OpenID_math_extensions() as $extinfo) { $tried[] = $extinfo['extension']; } $triedstr = implode(", ", $tried); Auth_OpenID_setNoMathSupport(); $result = null; return $result; } // Instantiate a new wrapper $class = $ext['class']; $lib = new $class(); return $lib; } function Auth_OpenID_setNoMathSupport() { if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) { define('Auth_OpenID_NO_MATH_SUPPORT', true); } } function Auth_OpenID_noMathSupport() { return defined('Auth_OpenID_NO_MATH_SUPPORT'); }