File indexing completed on 2024-04-28 06:00:03

0001 <?php
0002 
0003 /**
0004  * Zend Framework
0005  *
0006  * LICENSE
0007  *
0008  * This source file is subject to the new BSD license that is bundled
0009  * with this package in the file LICENSE.txt.
0010  * It is also available through the world-wide-web at this URL:
0011  * http://framework.zend.com/license/new-bsd
0012  * If you did not receive a copy of the license and are unable to
0013  * obtain it through the world-wide-web, please send an email
0014  * to license@zend.com so we can send you a copy immediately.
0015  *
0016  * @category   Zend
0017  * @package    Zend_OpenId
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 /**
0024  * @see Zend_Controller_Response_Abstract
0025  */
0026 // require_once "Zend/Controller/Response/Abstract.php";
0027 
0028 /** @see Zend_Crypt_Math */
0029 // require_once 'Zend/Crypt/Math.php';
0030 
0031 /**
0032  * Static class that contains common utility functions for
0033  * {@link Zend_OpenId_Consumer} and {@link Zend_OpenId_Provider}.
0034  *
0035  * This class implements common utility functions that are used by both
0036  * Consumer and Provider. They include functions for Diffie-Hellman keys
0037  * generation and exchange, URL normalization, HTTP redirection and some others.
0038  *
0039  * @category   Zend
0040  * @package    Zend_OpenId
0041  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0042  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0043  */
0044 class Zend_OpenId
0045 {
0046     /**
0047      * Default Diffie-Hellman key generator (1024 bit)
0048      */
0049     const DH_P   = 'dcf93a0b883972ec0e19989ac5a2ce310e1d37717e8d9571bb7623731866e61ef75a2e27898b057f9891c2e27a639c3f29b60814581cd3b2ca3986d2683705577d45c2e7e52dc81c7a171876e5cea74b1448bfdfaf18828efd2519f14e45e3826634af1949e5b535cc829a483b8a76223e5d490a257f05bdff16f2fb22c583ab';
0050 
0051     /**
0052      * Default Diffie-Hellman prime number (should be 2 or 5)
0053      */
0054     const DH_G   = '02';
0055 
0056     /**
0057      * OpenID 2.0 namespace. All OpenID 2.0 messages MUST contain variable
0058      * openid.ns with its value.
0059      */
0060     const NS_2_0 = 'http://specs.openid.net/auth/2.0';
0061 
0062     /**
0063      * Allows enable/disable stoping execution of PHP script after redirect()
0064      */
0065     static public $exitOnRedirect = true;
0066 
0067     /**
0068      * Alternative request URL that can be used to override the default
0069      * selfUrl() response
0070      */
0071     static public $selfUrl = null;
0072 
0073     /**
0074      * Sets alternative request URL that can be used to override the default
0075      * selfUrl() response
0076      *
0077      * @param string $selfUrl the URL to be set
0078      * @return string the old value of overriding URL
0079      */
0080     static public function setSelfUrl($selfUrl = null)
0081     {
0082         $ret = self::$selfUrl;
0083         self::$selfUrl = $selfUrl;
0084         return $ret;
0085     }
0086 
0087     /**
0088      * Returns a full URL that was requested on current HTTP request.
0089      *
0090      * @return string
0091      */
0092     static public function selfUrl()
0093     {
0094         if (self::$selfUrl !== null) {
0095             return self::$selfUrl;
0096         } if (isset($_SERVER['SCRIPT_URI'])) {
0097             return $_SERVER['SCRIPT_URI'];
0098         }
0099         $url = '';
0100         $port = '';
0101         if (isset($_SERVER['HTTP_HOST'])) {
0102             if (($pos = strpos($_SERVER['HTTP_HOST'], ':')) === false) {
0103                 if (isset($_SERVER['SERVER_PORT'])) {
0104                     $port = ':' . $_SERVER['SERVER_PORT'];
0105                 }
0106                 $url = $_SERVER['HTTP_HOST'];
0107             } else {
0108                 $url = substr($_SERVER['HTTP_HOST'], 0, $pos);
0109                 $port = substr($_SERVER['HTTP_HOST'], $pos);
0110             }
0111         } else if (isset($_SERVER['SERVER_NAME'])) {
0112             $url = $_SERVER['SERVER_NAME'];
0113             if (isset($_SERVER['SERVER_PORT'])) {
0114                 $port = ':' . $_SERVER['SERVER_PORT'];
0115             }
0116         }
0117         if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
0118             $url = 'https://' . $url;
0119             if ($port == ':443') {
0120                 $port = '';
0121             }
0122         } else {
0123             $url = 'http://' . $url;
0124             if ($port == ':80') {
0125                 $port = '';
0126             }
0127         }
0128 
0129         $url .= $port;
0130         if (isset($_SERVER['HTTP_X_ORIGINAL_URL'])) { 
0131             // IIS with Microsoft Rewrite Module
0132             $url .= $_SERVER['HTTP_X_ORIGINAL_URL'];
0133         } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
0134             // IIS with ISAPI_Rewrite 
0135             $url .= $_SERVER['HTTP_X_REWRITE_URL'];
0136         } elseif (isset($_SERVER['REQUEST_URI'])) {
0137             $query = strpos($_SERVER['REQUEST_URI'], '?');
0138             if ($query === false) {
0139                 $url .= $_SERVER['REQUEST_URI'];
0140             } else {
0141                 $url .= substr($_SERVER['REQUEST_URI'], 0, $query);
0142             }
0143         } else if (isset($_SERVER['SCRIPT_URL'])) {
0144             $url .= $_SERVER['SCRIPT_URL'];
0145         } else if (isset($_SERVER['REDIRECT_URL'])) {
0146             $url .= $_SERVER['REDIRECT_URL'];
0147         } else if (isset($_SERVER['PHP_SELF'])) {
0148             $url .= $_SERVER['PHP_SELF'];
0149         } else if (isset($_SERVER['SCRIPT_NAME'])) {
0150             $url .= $_SERVER['SCRIPT_NAME'];
0151             if (isset($_SERVER['PATH_INFO'])) {
0152                 $url .= $_SERVER['PATH_INFO'];
0153             }
0154         }
0155         return $url;
0156     }
0157 
0158     /**
0159      * Returns an absolute URL for the given one
0160      *
0161      * @param string $url absilute or relative URL
0162      * @return string
0163      */
0164     static public function absoluteUrl($url)
0165     {
0166         if (empty($url)) {
0167             return Zend_OpenId::selfUrl();
0168         } else if (!preg_match('|^([^:]+)://|', $url)) {
0169             if (preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?]*)?((?:[?](?:[^#]*))?(?:#.*)?)$|', Zend_OpenId::selfUrl(), $reg)) {
0170                 $scheme = $reg[1];
0171                 $auth = $reg[2];
0172                 $host = $reg[3];
0173                 $port = $reg[4];
0174                 $path = $reg[5];
0175                 $query = $reg[6];
0176                 if ($url[0] == '/') {
0177                     return $scheme
0178                         . '://'
0179                         . $auth
0180                         . $host
0181                         . (empty($port) ? '' : (':' . $port))
0182                         . $url;
0183                 } else {
0184                     $dir = dirname($path);
0185                     return $scheme
0186                         . '://'
0187                         . $auth
0188                         . $host
0189                         . (empty($port) ? '' : (':' . $port))
0190                         . (strlen($dir) > 1 ? $dir : '')
0191                         . '/'
0192                         . $url;
0193                 }
0194             }
0195         }
0196         return $url;
0197     }
0198 
0199     /**
0200      * Converts variable/value pairs into URL encoded query string
0201      *
0202      * @param array $params variable/value pairs
0203      * @return string URL encoded query string
0204      */
0205     static public function paramsToQuery($params)
0206     {
0207         foreach($params as $key => $value) {
0208             if (isset($query)) {
0209                 $query .= '&' . $key . '=' . urlencode($value);
0210             } else {
0211                 $query = $key . '=' . urlencode($value);
0212             }
0213         }
0214         return isset($query) ? $query : '';
0215     }
0216 
0217     /**
0218      * Normalizes URL according to RFC 3986 to use it in comparison operations.
0219      * The function gets URL argument by reference and modifies it.
0220      * It returns true on success and false of failure.
0221      *
0222      * @param string &$id url to be normalized
0223      * @return bool
0224      */
0225     static public function normalizeUrl(&$id)
0226     {
0227         // RFC 3986, 6.2.2.  Syntax-Based Normalization
0228 
0229         // RFC 3986, 6.2.2.2 Percent-Encoding Normalization
0230         $i = 0;
0231         $n = strlen($id);
0232         $res = '';
0233         while ($i < $n) {
0234             if ($id[$i] == '%') {
0235                 if ($i + 2 >= $n) {
0236                     return false;
0237                 }
0238                 ++$i;
0239                 if ($id[$i] >= '0' && $id[$i] <= '9') {
0240                     $c = ord($id[$i]) - ord('0');
0241                 } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
0242                     $c = ord($id[$i]) - ord('A') + 10;
0243                 } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
0244                     $c = ord($id[$i]) - ord('a') + 10;
0245                 } else {
0246                     return false;
0247                 }
0248                 ++$i;
0249                 if ($id[$i] >= '0' && $id[$i] <= '9') {
0250                     $c = ($c << 4) | (ord($id[$i]) - ord('0'));
0251                 } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
0252                     $c = ($c << 4) | (ord($id[$i]) - ord('A') + 10);
0253                 } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
0254                     $c = ($c << 4) | (ord($id[$i]) - ord('a') + 10);
0255                 } else {
0256                     return false;
0257                 }
0258                 ++$i;
0259                 $ch = chr($c);
0260                 if (($ch >= 'A' && $ch <= 'Z') ||
0261                     ($ch >= 'a' && $ch <= 'z') ||
0262                     $ch == '-' ||
0263                     $ch == '.' ||
0264                     $ch == '_' ||
0265                     $ch == '~') {
0266                     $res .= $ch;
0267                 } else {
0268                     $res .= '%';
0269                     if (($c >> 4) < 10) {
0270                         $res .= chr(($c >> 4) + ord('0'));
0271                     } else {
0272                         $res .= chr(($c >> 4) - 10 + ord('A'));
0273                     }
0274                     $c = $c & 0xf;
0275                     if ($c < 10) {
0276                         $res .= chr($c + ord('0'));
0277                     } else {
0278                         $res .= chr($c - 10 + ord('A'));
0279                     }
0280                 }
0281             } else {
0282                 $res .= $id[$i++];
0283             }
0284         }
0285 
0286         if (!preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?#]*)?((?:[?](?:[^#]*))?)((?:#.*)?)$|', $res, $reg)) {
0287             return false;
0288         }
0289         $scheme = $reg[1];
0290         $auth = $reg[2];
0291         $host = $reg[3];
0292         $port = $reg[4];
0293         $path = $reg[5];
0294         $query = $reg[6];
0295         $fragment = $reg[7]; /* strip it */ /* ZF-4358 Fragment retained under OpenID 2.0 */
0296 
0297         if (empty($scheme) || empty($host)) {
0298             return false;
0299         }
0300 
0301         // RFC 3986, 6.2.2.1.  Case Normalization
0302         $scheme = strtolower($scheme);
0303         $host = strtolower($host);
0304 
0305         // RFC 3986, 6.2.2.3.  Path Segment Normalization
0306         if (!empty($path)) {
0307             $i = 0;
0308             $n = strlen($path);
0309             $res = "";
0310             while ($i < $n) {
0311                 if ($path[$i] == '/') {
0312                     ++$i;
0313                     while ($i < $n && $path[$i] == '/') {
0314                         ++$i;
0315                     }
0316                     if ($i < $n && $path[$i] == '.') {
0317                         ++$i;
0318                         if ($i < $n && $path[$i] == '.') {
0319                             ++$i;
0320                             if ($i == $n || $path[$i] == '/') {
0321                                 if (($pos = strrpos($res, '/')) !== false) {
0322                                     $res = substr($res, 0, $pos);
0323                                 }
0324                             } else {
0325                                     $res .= '/..';
0326                             }
0327                         } else if ($i != $n && $path[$i] != '/') {
0328                             $res .= '/.';
0329                         }
0330                     } else {
0331                         $res .= '/';
0332                     }
0333                 } else {
0334                     $res .= $path[$i++];
0335                 }
0336             }
0337             $path = $res;
0338         }
0339 
0340         // RFC 3986,6.2.3.  Scheme-Based Normalization
0341         if ($scheme == 'http') {
0342             if ($port == 80) {
0343                 $port = '';
0344             }
0345         } else if ($scheme == 'https') {
0346             if ($port == 443) {
0347                 $port = '';
0348             }
0349         }
0350         if (empty($path)) {
0351             $path = '/';
0352         }
0353 
0354         $id = $scheme
0355             . '://'
0356             . $auth
0357             . $host
0358             . (empty($port) ? '' : (':' . $port))
0359             . $path
0360             . $query
0361             . $fragment;
0362         return true;
0363     }
0364 
0365     /**
0366      * Normalizes OpenID identifier that can be URL or XRI name.
0367      * Returns true on success and false of failure.
0368      *
0369      * Normalization is performed according to the following rules:
0370      * 1. If the user's input starts with one of the "xri://", "xri://$ip*",
0371      *    or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs
0372      *    are used in the canonical form, and URI-authority XRIs are further
0373      *    considered URL identifiers.
0374      * 2. If the first character of the resulting string is an XRI Global
0375      *    Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be
0376      *    treated as an XRI.
0377      * 3. Otherwise, the input SHOULD be treated as an http URL; if it does
0378      *    not include a "http" or "https" scheme, the Identifier MUST be
0379      *    prefixed with the string "http://".
0380      * 4. URL identifiers MUST then be further normalized by both following
0381      *    redirects when retrieving their content and finally applying the
0382      *    rules in Section 6 of [RFC3986] to the final destination URL.
0383      * @param string &$id identifier to be normalized
0384      * @return bool
0385      */
0386     static public function normalize(&$id)
0387     {
0388         $id = trim($id);
0389         if (strlen($id) === 0) {
0390             return true;
0391         }
0392 
0393         // 7.2.1
0394         if (strpos($id, 'xri://$ip*') === 0) {
0395             $id = substr($id, strlen('xri://$ip*'));
0396         } else if (strpos($id, 'xri://$dns*') === 0) {
0397             $id = substr($id, strlen('xri://$dns*'));
0398         } else if (strpos($id, 'xri://') === 0) {
0399             $id = substr($id, strlen('xri://'));
0400         }
0401 
0402         // 7.2.2
0403         if ($id[0] == '=' ||
0404             $id[0] == '@' ||
0405             $id[0] == '+' ||
0406             $id[0] == '$' ||
0407             $id[0] == '!') {
0408             return true;
0409         }
0410 
0411         // 7.2.3
0412         if (strpos($id, "://") === false) {
0413             $id = 'http://' . $id;
0414         }
0415 
0416         // 7.2.4
0417         return self::normalizeURL($id);
0418     }
0419 
0420     /**
0421      * Performs a HTTP redirection to specified URL with additional data.
0422      * It may generate redirected request using GET or POST HTTP method.
0423      * The function never returns.
0424      *
0425      * @param string $url URL to redirect to
0426      * @param array $params additional variable/value pairs to send
0427      * @param Zend_Controller_Response_Abstract $response
0428      * @param string $method redirection method ('GET' or 'POST')
0429      */
0430     static public function redirect($url, $params = null,
0431         Zend_Controller_Response_Abstract $response = null, $method = 'GET')
0432     {
0433         $url = Zend_OpenId::absoluteUrl($url);
0434         $body = "";
0435         if (null === $response) {
0436             // require_once "Zend/Controller/Response/Http.php";
0437             $response = new Zend_Controller_Response_Http();
0438         }
0439 
0440         if ($method == 'POST') {
0441             $body = "<html><body onLoad=\"document.forms[0].submit();\">\n";
0442             $body .= "<form method=\"POST\" action=\"$url\">\n";
0443             if (is_array($params) && count($params) > 0) {
0444                 foreach($params as $key => $value) {
0445                     $body .= '<input type="hidden" name="' . $key . '" value="' . $value . "\">\n";
0446                 }
0447             }
0448             $body .= "<input type=\"submit\" value=\"Continue OpenID transaction\">\n";
0449             $body .= "</form></body></html>\n";
0450         } else if (is_array($params) && count($params) > 0) {
0451             if (strpos($url, '?') === false) {
0452                 $url .= '?' . self::paramsToQuery($params);
0453             } else {
0454                 $url .= '&' . self::paramsToQuery($params);
0455             }
0456         }
0457         if (!empty($body)) {
0458             $response->setBody($body);
0459         } else if (!$response->canSendHeaders()) {
0460             $response->setBody("<script language=\"JavaScript\"" .
0461                  " type=\"text/javascript\">window.location='$url';" .
0462                  "</script>");
0463         } else {
0464             $response->setRedirect($url);
0465         }
0466         $response->sendResponse();
0467         if (self::$exitOnRedirect) {
0468             exit();
0469         }
0470     }
0471 
0472     /**
0473      * Produces string of random byte of given length.
0474      *
0475      * @param integer $len length of requested string
0476      * @return string RAW random binary string
0477      */
0478     static public function randomBytes($len)
0479     {
0480         return (string) Zend_Crypt_Math::randBytes($len);
0481     }
0482 
0483     /**
0484      * Generates a hash value (message digest) according to given algorithm.
0485      * It returns RAW binary string.
0486      *
0487      * This is a wrapper function that uses one of available internal function
0488      * dependent on given PHP configuration. It may use various functions from
0489      *  ext/openssl, ext/hash, ext/mhash or ext/standard.
0490      *
0491      * @param string $func digest algorithm
0492      * @param string $data data to sign
0493      * @return string RAW digital signature
0494      * @throws Zend_OpenId_Exception
0495      */
0496     static public function digest($func, $data)
0497     {
0498         if (function_exists('openssl_digest')) {
0499             return openssl_digest($data, $func, true);
0500         } else if (function_exists('hash')) {
0501             return hash($func, $data, true);
0502         } else if ($func === 'sha1') {
0503             return sha1($data, true);
0504         } else if ($func === 'sha256') {
0505             if (function_exists('mhash')) {
0506                 return mhash(MHASH_SHA256 , $data);
0507             }
0508         }
0509         // require_once "Zend/OpenId/Exception.php";
0510         throw new Zend_OpenId_Exception(
0511             'Unsupported digest algorithm "' . $func . '".',
0512             Zend_OpenId_Exception::UNSUPPORTED_DIGEST);
0513     }
0514 
0515     /**
0516      * Generates a keyed hash value using the HMAC method. It uses ext/hash
0517      * if available or user-level PHP implementation, that is not significantly
0518      * slower.
0519      *
0520      * @param string $macFunc name of selected hashing algorithm (sha1, sha256)
0521      * @param string $data data to sign
0522      * @param string $secret shared secret key used for generating the HMAC
0523      *  variant of the message digest
0524      * @return string RAW HMAC value
0525      */
0526     static public function hashHmac($macFunc, $data, $secret)
0527     {
0528 //        // require_once "Zend/Crypt/Hmac.php";
0529 //        return Zend_Crypt_Hmac::compute($secret, $macFunc, $data, Zend_Crypt_Hmac::BINARY);
0530         if (function_exists('hash_hmac')) {
0531             return hash_hmac($macFunc, $data, $secret, true);
0532         } else {
0533             if (Zend_OpenId::strlen($secret) > 64) {
0534                 $secret = self::digest($macFunc, $secret);
0535             }
0536             $secret = str_pad($secret, 64, chr(0x00));
0537             $ipad = str_repeat(chr(0x36), 64);
0538             $opad = str_repeat(chr(0x5c), 64);
0539             $hash1 = self::digest($macFunc, ($secret ^ $ipad) . $data);
0540             return self::digest($macFunc, ($secret ^ $opad) . $hash1);
0541         }
0542     }
0543 
0544     /**
0545      * Converts binary representation into ext/gmp or ext/bcmath big integer
0546      * representation.
0547      *
0548      * @param string $bin binary representation of big number
0549      * @return mixed
0550      * @throws Zend_OpenId_Exception
0551      */
0552     static protected function binToBigNum($bin)
0553     {
0554         if (extension_loaded('gmp')) {
0555             return gmp_init(bin2hex($bin), 16);
0556         } else if (extension_loaded('bcmath')) {
0557             $bn = 0;
0558             $len = Zend_OpenId::strlen($bin);
0559             for ($i = 0; $i < $len; $i++) {
0560                 $bn = bcmul($bn, 256);
0561                 $bn = bcadd($bn, ord($bin[$i]));
0562             }
0563             return $bn;
0564         }
0565         // require_once "Zend/OpenId/Exception.php";
0566         throw new Zend_OpenId_Exception(
0567             'The system doesn\'t have proper big integer extension',
0568             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
0569     }
0570 
0571     /**
0572      * Converts internal ext/gmp or ext/bcmath big integer representation into
0573      * binary string.
0574      *
0575      * @param mixed $bn big number
0576      * @return string
0577      * @throws Zend_OpenId_Exception
0578      */
0579     static protected function bigNumToBin($bn)
0580     {
0581         if (extension_loaded('gmp')) {
0582             $s = gmp_strval($bn, 16);
0583             if (strlen($s) % 2 != 0) {
0584                 $s = '0' . $s;
0585             } else if ($s[0] > '7') {
0586                 $s = '00' . $s;
0587             }
0588             return pack("H*", $s);
0589         } else if (extension_loaded('bcmath')) {
0590             $cmp = bccomp($bn, 0);
0591             if ($cmp == 0) {
0592                 return "\0";
0593             } else if ($cmp < 0) {
0594                 // require_once "Zend/OpenId/Exception.php";
0595                 throw new Zend_OpenId_Exception(
0596                     'Big integer arithmetic error',
0597                     Zend_OpenId_Exception::ERROR_LONG_MATH);
0598             }
0599             $bin = "";
0600             while (bccomp($bn, 0) > 0) {
0601                 $bin = chr(bcmod($bn, 256)) . $bin;
0602                 $bn = bcdiv($bn, 256);
0603             }
0604             if (ord($bin[0]) > 127) {
0605                 $bin = "\0" . $bin;
0606             }
0607             return $bin;
0608         }
0609         // require_once "Zend/OpenId/Exception.php";
0610         throw new Zend_OpenId_Exception(
0611             'The system doesn\'t have proper big integer extension',
0612             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
0613     }
0614 
0615     /**
0616      * Performs the first step of a Diffie-Hellman key exchange by generating
0617      * private and public DH values based on given prime number $p and
0618      * generator $g. Both sides of key exchange MUST have the same prime number
0619      * and generator. In this case they will able to create a random shared
0620      * secret that is never send from one to the other.
0621      *
0622      * @param string $p prime number in binary representation
0623      * @param string $g generator in binary representation
0624      * @param string $priv_key private key in binary representation
0625      * @return mixed
0626      */
0627     static public function createDhKey($p, $g, $priv_key = null)
0628     {
0629         if (function_exists('openssl_dh_compute_key')) {
0630             $dh_details = array(
0631                     'p' => $p,
0632                     'g' => $g
0633                 );
0634             if ($priv_key !== null) {
0635                 $dh_details['priv_key'] = $priv_key;
0636             }
0637             return openssl_pkey_new(array('dh'=>$dh_details));
0638         } else {
0639             $bn_p        = self::binToBigNum($p);
0640             $bn_g        = self::binToBigNum($g);
0641             if ($priv_key === null) {
0642                 $priv_key    = self::randomBytes(Zend_OpenId::strlen($p));
0643             }
0644             $bn_priv_key = self::binToBigNum($priv_key);
0645             if (extension_loaded('gmp')) {
0646                 $bn_pub_key  = gmp_powm($bn_g, $bn_priv_key, $bn_p);
0647             } else if (extension_loaded('bcmath')) {
0648                 $bn_pub_key  = bcpowmod($bn_g, $bn_priv_key, $bn_p);
0649             }
0650             $pub_key     = self::bigNumToBin($bn_pub_key);
0651 
0652             return array(
0653                 'p'        => $bn_p,
0654                 'g'        => $bn_g,
0655                 'priv_key' => $bn_priv_key,
0656                 'pub_key'  => $bn_pub_key,
0657                 'details'  => array(
0658                     'p'        => $p,
0659                     'g'        => $g,
0660                     'priv_key' => $priv_key,
0661                     'pub_key'  => $pub_key));
0662         }
0663     }
0664 
0665     /**
0666      * Returns an associative array with Diffie-Hellman key components in
0667      * binary representation. The array includes original prime number 'p' and
0668      * generator 'g', random private key 'priv_key' and corresponding public
0669      * key 'pub_key'.
0670      *
0671      * @param mixed $dh Diffie-Hellman key
0672      * @return array
0673      */
0674     static public function getDhKeyDetails($dh)
0675     {
0676         if (function_exists('openssl_dh_compute_key')) {
0677             $details = openssl_pkey_get_details($dh);
0678             if (isset($details['dh'])) {
0679                 return $details['dh'];
0680             }
0681         } else {
0682             return $dh['details'];
0683         }
0684     }
0685 
0686     /**
0687      * Computes the shared secret from the private DH value $dh and the other
0688      * party's public value in $pub_key
0689      *
0690      * @param string $pub_key other party's public value
0691      * @param mixed $dh Diffie-Hellman key
0692      * @return string
0693      * @throws Zend_OpenId_Exception
0694      */
0695     static public function computeDhSecret($pub_key, $dh)
0696     {
0697         if (function_exists('openssl_dh_compute_key')) {
0698             $ret = openssl_dh_compute_key($pub_key, $dh);
0699             if (ord($ret[0]) > 127) {
0700                 $ret = "\0" . $ret;
0701             }
0702             return $ret;
0703         } else if (extension_loaded('gmp')) {
0704             $bn_pub_key = self::binToBigNum($pub_key);
0705             $bn_secret  = gmp_powm($bn_pub_key, $dh['priv_key'], $dh['p']);
0706             return self::bigNumToBin($bn_secret);
0707         } else if (extension_loaded('bcmath')) {
0708             $bn_pub_key = self::binToBigNum($pub_key);
0709             $bn_secret  = bcpowmod($bn_pub_key, $dh['priv_key'], $dh['p']);
0710             return self::bigNumToBin($bn_secret);
0711         }
0712         // require_once "Zend/OpenId/Exception.php";
0713         throw new Zend_OpenId_Exception(
0714             'The system doesn\'t have proper big integer extension',
0715             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
0716     }
0717 
0718     /**
0719      * Takes an arbitrary precision integer and returns its shortest big-endian
0720      * two's complement representation.
0721      *
0722      * Arbitrary precision integers MUST be encoded as big-endian signed two's
0723      * complement binary strings. Henceforth, "btwoc" is a function that takes
0724      * an arbitrary precision integer and returns its shortest big-endian two's
0725      * complement representation. All integers that are used with
0726      * Diffie-Hellman Key Exchange are positive. This means that the left-most
0727      * bit of the two's complement representation MUST be zero. If it is not,
0728      * implementations MUST add a zero byte at the front of the string.
0729      *
0730      * @param string $str binary representation of arbitrary precision integer
0731      * @return string big-endian signed representation
0732      */
0733     static public function btwoc($str)
0734     {
0735         if (ord($str[0]) > 127) {
0736             return "\0" . $str;
0737         }
0738         return $str;
0739     }
0740 
0741     /**
0742      * Returns lenght of binary string in bytes
0743      *
0744      * @param string $str
0745      * @return int the string lenght
0746      */
0747     static public function strlen($str)
0748     {
0749         if (extension_loaded('mbstring') &&
0750             (((int)ini_get('mbstring.func_overload')) & 2)) {
0751             return mb_strlen($str, 'latin1');
0752         } else {
0753             return strlen($str);
0754         }
0755     }
0756 
0757 }