PageRenderTime 128ms CodeModel.GetById 61ms app.highlight 26ms RepoModel.GetById 34ms app.codeStats 0ms

/library/Zend/OpenId.php

https://bitbucket.org/baruffaldi/website-2008-computer-shopping-3
PHP | 715 lines | 573 code | 26 blank | 116 comment | 129 complexity | 085ecc4e55554be72cc94938b6c6eae2 MD5 | raw file
  1<?php
  2
  3/**
  4 * Zend Framework
  5 *
  6 * LICENSE
  7 *
  8 * This source file is subject to the new BSD license that is bundled
  9 * with this package in the file LICENSE.txt.
 10 * It is also available through the world-wide-web at this URL:
 11 * http://framework.zend.com/license/new-bsd
 12 * If you did not receive a copy of the license and are unable to
 13 * obtain it through the world-wide-web, please send an email
 14 * to license@zend.com so we can send you a copy immediately.
 15 *
 16 * @category   Zend
 17 * @package    Zend_OpenId
 18 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 19 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 20 * @version    $Id: OpenId.php 9240 2008-04-18 13:06:29Z dmitry $
 21 */
 22
 23/**
 24 * @see Zend_OpenId_Exception
 25 */
 26require_once "Zend/OpenId/Exception.php";
 27
 28/**
 29 * @see Zend_Controller_Response_Abstract
 30 */
 31require_once "Zend/Controller/Response/Abstract.php";
 32
 33/**
 34 * Static class that contains common utility functions for
 35 * {@link Zend_OpenId_Consumer} and {@link Zend_OpenId_Provider}.
 36 *
 37 * This class implements common utility functions that are used by both
 38 * Consumer and Provider. They include functions for Diffie-Hellman keys
 39 * generation and exchange, URL normalization, HTTP redirection and some others.
 40 *
 41 * @category   Zend
 42 * @package    Zend_OpenId
 43 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 44 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 45 */
 46class Zend_OpenId
 47{
 48    /**
 49     * Default Diffie-Hellman key generator (1024 bit)
 50     */
 51    const DH_P   = 'dcf93a0b883972ec0e19989ac5a2ce310e1d37717e8d9571bb7623731866e61ef75a2e27898b057f9891c2e27a639c3f29b60814581cd3b2ca3986d2683705577d45c2e7e52dc81c7a171876e5cea74b1448bfdfaf18828efd2519f14e45e3826634af1949e5b535cc829a483b8a76223e5d490a257f05bdff16f2fb22c583ab';
 52
 53    /**
 54     * Default Diffie-Hellman prime number (should be 2 or 5)
 55     */
 56    const DH_G   = '02';
 57
 58    /**
 59     * OpenID 2.0 namespace. All OpenID 2.0 messages MUST contain variable
 60     * openid.ns with its value.
 61     */
 62    const NS_2_0 = 'http://specs.openid.net/auth/2.0';
 63
 64    /**
 65     * Allows enable/disable stoping execution of PHP script after redirect()
 66     */
 67    static public $exitOnRedirect = true;
 68
 69    /**
 70     * Returns a full URL that was requested on current HTTP request.
 71     *
 72     * @return string
 73     */
 74    static public function selfUrl()
 75    {
 76        if (isset($_SERVER['SCRIPT_URI'])) {
 77            return $_SERVER['SCRIPT_URI'];
 78        }
 79        $url = '';
 80        $port = '';
 81        if (isset($_SERVER['HTTP_HOST'])) {
 82            if (($pos = strpos($_SERVER['HTTP_HOST'], ':')) === false) {
 83                if (isset($_SERVER['SERVER_PORT'])) {
 84                    $port = ':' . $_SERVER['SERVER_PORT'];
 85                }
 86                $url = $_SERVER['HTTP_HOST'];
 87            } else {
 88                $url = substr($_SERVER['HTTP_HOST'], 0, $pos);
 89                $port = substr($_SERVER['HTTP_HOST'], $pos);
 90            }
 91        } else if (isset($_SERVER['SERVER_NAME'])) {
 92            $url = $_SERVER['SERVER_NAME'];
 93            if (isset($_SERVER['SERVER_PORT'])) {
 94                $port = ':' . $_SERVER['SERVER_PORT'];
 95            }
 96        }
 97        if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
 98            $url = 'https://' . $url;
 99            if ($port == ':443') {
100                $port = '';
101            }
102        } else {
103            $url = 'http://' . $url;
104            if ($port == ':80') {
105                $port = '';
106            }
107        }
108
109        $url .= $port;
110        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) {
111            $url .= $_SERVER['HTTP_X_REWRITE_URL'];
112        } elseif (isset($_SERVER['REQUEST_URI'])) {
113            $query = strpos($_SERVER['REQUEST_URI'], '?');
114            if ($query === false) {
115                $url .= $_SERVER['REQUEST_URI'];
116            } else {
117                $url .= substr($_SERVER['REQUEST_URI'], 0, $query);
118            }
119        } else if (isset($_SERVER['SCRIPT_URL'])) {
120            $url .= $_SERVER['SCRIPT_URL'];
121        } else if (isset($_SERVER['REDIRECT_URL'])) {
122            $url .= $_SERVER['REDIRECT_URL'];
123        } else if (isset($_SERVER['PHP_SELF'])) {
124            $url .= $_SERVER['PHP_SELF'];
125        } else if (isset($_SERVER['SCRIPT_NAME'])) {
126            $url .= $_SERVER['SCRIPT_NAME'];
127            if (isset($_SERVER['PATH_INFO'])) {
128                $url .= $_SERVER['PATH_INFO'];
129            }
130        }
131        return $url;
132    }
133
134    /**
135     * Returns an absolute URL for the given one
136     *
137     * @param string $url absilute or relative URL
138     * @return string
139     */
140    static public function absoluteUrl($url)
141    {
142        if (empty($url)) {
143            return Zend_OpenId::selfUrl();
144        } else if (!preg_match('|^([^:]+)://|', $url)) {
145            if (preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?]*)?((?:[?](?:[^#]*))?(?:#.*)?)$|', Zend_OpenId::selfUrl(), $reg)) {
146                $scheme = $reg[1];
147                $auth = $reg[2];
148                $host = $reg[3];
149                $port = $reg[4];
150                $path = $reg[5];
151                $query = $reg[6];
152                if ($url[0] == '/') {
153                    return $scheme
154                        . '://'
155                        . $auth
156                        . $host
157                        . (empty($port) ? '' : (':' . $port))
158                        . $url;
159                } else {
160                    $dir = dirname($path);
161                    return $scheme
162                        . '://'
163                        . $auth
164                        . $host
165                        . (empty($port) ? '' : (':' . $port))
166                        . (strlen($dir) > 1 ? $dir : '')
167                        . '/'
168                        . $url;
169                }
170            }
171        }
172        return $url;
173    }
174
175    /**
176     * Converts variable/value pairs into URL encoded query string
177     *
178     * @param array $params variable/value pairs
179     * @return string URL encoded query string
180     */
181    static public function paramsToQuery($params)
182    {
183        foreach($params as $key => $value) {
184            if (isset($query)) {
185                $query .= '&' . $key . '=' . urlencode($value);
186            } else {
187                $query = $key . '=' . urlencode($value);
188            }
189        }
190        return isset($query) ? $query : '';
191    }
192
193    /**
194     * Normalizes URL according to RFC 3986 to use it in comparison operations.
195     * The function gets URL argument by reference and modifies it.
196     * It returns true on success and false of failure.
197     *
198     * @param string &$id url to be normalized
199     * @return bool
200     */
201    static public function normalizeUrl(&$id)
202    {
203        // RFC 3986, 6.2.2.  Syntax-Based Normalization
204
205        // RFC 3986, 6.2.2.2 Percent-Encoding Normalization
206        $i = 0;
207        $n = strlen($id);
208        $res = '';
209        while ($i < $n) {
210            if ($id[$i] == '%') {
211                if ($i + 2 >= $n) {
212                    return false;
213                }
214                ++$i;
215                if ($id[$i] >= '0' && $id[$i] <= '9') {
216                    $c = ord($id[$i]) - ord('0');
217                } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
218                    $c = ord($id[$i]) - ord('A') + 10;
219                } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
220                    $c = ord($id[$i]) - ord('a') + 10;
221                } else {
222                    return false;
223                }
224                ++$i;
225                if ($id[$i] >= '0' && $id[$i] <= '9') {
226                    $c = ($c << 4) | (ord($id[$i]) - ord('0'));
227                } else if ($id[$i] >= 'A' && $id[$i] <= 'F') {
228                    $c = ($c << 4) | (ord($id[$i]) - ord('A') + 10);
229                } else if ($id[$i] >= 'a' && $id[$i] <= 'f') {
230                    $c = ($c << 4) | (ord($id[$i]) - ord('a') + 10);
231                } else {
232                    return false;
233                }
234                ++$i;
235                $ch = chr($c);
236                if (($ch >= 'A' && $ch <= 'Z') ||
237                    ($ch >= 'a' && $ch <= 'z') ||
238                    $ch == '-' ||
239                    $ch == '.' ||
240                    $ch == '_' ||
241                    $ch == '~') {
242                    $res .= $ch;
243                } else {
244                    $res .= '%';
245                    if (($c >> 4) < 10) {
246                        $res .= chr(($c >> 4) + ord('0'));
247                    } else {
248                        $res .= chr(($c >> 4) - 10 + ord('A'));
249                    }
250                    $c = $c & 0xf;
251                    if ($c < 10) {
252                        $res .= chr($c + ord('0'));
253                    } else {
254                        $res .= chr($c - 10 + ord('A'));
255                    }
256                }
257            } else {
258                $res .= $id[$i++];
259            }
260        }
261
262        if (!preg_match('|^([^:]+)://([^:@]*(?:[:][^@]*)?@)?([^/:@?#]*)(?:[:]([^/?#]*))?(/[^?#]*)?((?:[?](?:[^#]*))?)((?:#.*)?)$|', $res, $reg)) {
263            return false;
264        }
265        $scheme = $reg[1];
266        $auth = $reg[2];
267        $host = $reg[3];
268        $port = $reg[4];
269        $path = $reg[5];
270        $query = $reg[6];
271        $fragment = $reg[7]; /* strip it */
272
273        if (empty($scheme) || empty($host)) {
274            return false;
275        }
276
277        // RFC 3986, 6.2.2.1.  Case Normalization
278        $scheme = strtolower($scheme);
279        $host = strtolower($host);
280
281        // RFC 3986, 6.2.2.3.  Path Segment Normalization
282        if (!empty($path)) {
283            $i = 0;
284            $n = strlen($path);
285            $res = "";
286            while ($i < $n) {
287                if ($path[$i] == '/') {
288                    ++$i;
289                    while ($i < $n && $path[$i] == '/') {
290                        ++$i;
291                    }
292                    if ($i < $n && $path[$i] == '.') {
293                        ++$i;
294                        if ($i < $n && $path[$i] == '.') {
295                            ++$i;
296                            if ($i == $n || $path[$i] == '/') {
297                                if (($pos = strrpos($res, '/')) !== false) {
298                                    $res = substr($res, 0, $pos);
299                                }
300                            } else {
301                                    $res .= '/..';
302                            }
303                        } else if ($i != $n && $path[$i] != '/') {
304                            $res .= '/.';
305                        }
306                    } else {
307                        $res .= '/';
308                    }
309                } else {
310                    $res .= $path[$i++];
311                }
312            }
313            $path = $res;
314        }
315
316        // RFC 3986,6.2.3.  Scheme-Based Normalization
317        if ($scheme == 'http') {
318            if ($port == 80) {
319                $port = '';
320            }
321        } else if ($scheme == 'https') {
322            if ($port == 443) {
323                $port = '';
324            }
325        }
326        if (empty($path)) {
327            $path = '/';
328        }
329
330        $id = $scheme
331            . '://'
332            . $auth
333            . $host
334            . (empty($port) ? '' : (':' . $port))
335            . $path
336            . $query;
337        return true;
338    }
339
340    /**
341     * Normalizes OpenID identifier that can be URL or XRI name.
342     * Returns true on success and false of failure.
343     *
344     * Normalization is performed according to the following rules:
345     * 1. If the user's input starts with one of the "xri://", "xri://$ip*",
346     *    or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs
347     *    are used in the canonical form, and URI-authority XRIs are further
348     *    considered URL identifiers.
349     * 2. If the first character of the resulting string is an XRI Global
350     *    Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be
351     *    treated as an XRI.
352     * 3. Otherwise, the input SHOULD be treated as an http URL; if it does
353     *    not include a "http" or "https" scheme, the Identifier MUST be
354     *    prefixed with the string "http://".
355     * 4. URL identifiers MUST then be further normalized by both following
356     *    redirects when retrieving their content and finally applying the
357     *    rules in Section 6 of [RFC3986] to the final destination URL.
358     * @param string &$id identifier to be normalized
359     * @return bool
360     */
361    static public function normalize(&$id)
362    {
363        $id = trim($id);
364        if (strlen($id) === 0) {
365            return true;
366        }
367
368        // 7.2.1
369        if (strpos($id, 'xri://$ip*') === 0) {
370            $id = substr($id, strlen('xri://$ip*'));
371        } else if (strpos($id, 'xri://$dns*') === 0) {
372            $id = substr($id, strlen('xri://$dns*'));
373        } else if (strpos($id, 'xri://') === 0) {
374            $id = substr($id, strlen('xri://'));
375        }
376
377        // 7.2.2
378        if ($id[0] == '=' ||
379            $id[0] == '@' ||
380            $id[0] == '+' ||
381            $id[0] == '$' ||
382            $id[0] == '!') {
383            return true;
384        }
385
386        // 7.2.3
387        if (strpos($id, "://") === false) {
388            $id = 'http://' . $id;
389        }
390
391        // 7.2.4
392        return self::normalizeURL($id);
393    }
394
395    /**
396     * Performs a HTTP redirection to specified URL with additional data.
397     * It may generate redirected request using GET or POST HTTP method.
398     * The function never returns.
399     *
400     * @param string $url URL to redirect to
401     * @param array $params additional variable/value pairs to send
402     * @param Zend_Controller_Response_Abstract $response
403     * @param string $method redirection method ('GET' or 'POST')
404     */
405    static public function redirect($url, $params = null,
406        Zend_Controller_Response_Abstract $response = null, $method = 'GET')
407    {
408        $url = Zend_OpenId::absoluteUrl($url);
409        $body = "";
410        if (null === $response) {
411            require_once "Zend/Controller/Response/Http.php";
412            $response = new Zend_Controller_Response_Http();
413        }
414
415        if ($method == 'POST') {
416            $body = "<html><body onLoad=\"document.forms[0].submit();\">\n";
417            $body .= "<form method=\"POST\" action=\"$url\">\n";
418            if (is_array($params) && count($params) > 0) {
419                foreach($params as $key => $value) {
420                    $body .= '<input type="hidden" name="' . $key . '" value="' . $value . "\">\n";
421                }
422            }
423            $body .= "<input type=\"submit\" value=\"Continue OpenID transaction\">\n";
424            $body .= "</form></body></html>\n";
425        } else if (is_array($params) && count($params) > 0) {
426            if (strpos($url, '?') === false) {
427                $url .= '?' . self::paramsToQuery($params);
428            } else {
429                $url .= '&' . self::paramsToQuery($params);
430            }
431        }
432        if (!empty($body)) {
433            $response->setBody($body);
434        } else if (!$response->canSendHeaders()) {
435            $response->setBody("<script language=\"JavaScript\"" .
436                 " type=\"text/javascript\">window.location='$url';" .
437                 "</script>");
438        } else {
439            $response->setRedirect($url);
440        }
441        $response->sendResponse();
442        if (self::$exitOnRedirect) {
443            exit();
444        }
445    }
446
447    /**
448     * Produces string of random byte of given length.
449     *
450     * @param integer $len length of requested string
451     * @return string RAW random binary string
452     */
453    static public function randomBytes($len)
454    {
455        $key = '';
456        for($i=0; $i < $len; $i++) {
457            $key .= chr(mt_rand(0, 255));
458        }
459        return $key;
460    }
461
462    /**
463     * Generates a hash value (message digest) according to given algorithm.
464     * It returns RAW binary string.
465     *
466     * This is a wrapper function that uses one of available internal function
467     * dependent on given PHP configuration. It may use various functions from
468     *  ext/openssl, ext/hash, ext/mhash or ext/standard.
469     *
470     * @param string $func digest algorithm
471     * @param string $data data to sign
472     * @return string RAW digital signature
473     * @throws Zend_OpenId_Exception
474     */
475    static public function digest($func, $data)
476    {
477        if (function_exists('openssl_digest')) {
478            return openssl_digest($data, $func, true);
479        } else if (function_exists('hash')) {
480            return hash($func, $data, true);
481        } else if ($func === 'sha1') {
482            return sha1($data, true);
483        } else if ($func === 'sha256') {
484            if (function_exists('mhash')) {
485                return mhash(MHASH_SHA256 , $data);
486            }
487        }
488        throw new Zend_OpenId_Exception(
489            'Unsupported digest algorithm "' . $func . '".',
490            Zend_OpenId_Exception::UNSUPPORTED_DIGEST);
491    }
492
493    /**
494     * Generates a keyed hash value using the HMAC method. It uses ext/hash
495     * if available or user-level PHP implementation, that is not significantly
496     * slower.
497     *
498     * @param string $macFunc name of selected hashing algorithm (sha1, sha256)
499     * @param string $data data to sign
500     * @param string $secret shared secret key used for generating the HMAC
501     *  variant of the message digest
502     * @return string RAW HMAC value
503     */
504    static public function hashHmac($macFunc, $data, $secret)
505    {
506//        require_once "Zend/Crypt/Hmac.php";
507//        return Zend_Crypt_Hmac::compute($secret, $macFunc, $data, Zend_Crypt_Hmac::BINARY);
508        if (function_exists('hash_hmac')) {
509            return hash_hmac($macFunc, $data, $secret, 1);
510        } else {
511            if (strlen($secret) > 64) {
512                $secret = self::digest($macFunc, $secret);
513            }
514            $secret = str_pad($secret, 64, chr(0x00));
515            $ipad = str_repeat(chr(0x36), 64);
516            $opad = str_repeat(chr(0x5c), 64);
517            $hash1 = self::digest($macFunc, ($secret ^ $ipad) . $data);
518            return self::digest($macFunc, ($secret ^ $opad) . $hash1);
519        }
520    }
521
522    /**
523     * Converts binary representation into ext/gmp or ext/bcmath big integer
524     * representation.
525     *
526     * @param string $bin binary representation of big number
527     * @return mixed
528     * @throws Zend_OpenId_Exception
529     */
530    static protected function binToBigNum($bin)
531    {
532        if (extension_loaded('gmp')) {
533            return gmp_init(bin2hex($bin), 16);
534        } else if (extension_loaded('bcmath')) {
535            $bn = 0;
536            $len = strlen($bin);
537            for ($i = 0; $i < $len; $i++) {
538                $bn = bcmul($bn, 256);
539                $bn = bcadd($bn, ord($bin[$i]));
540            }
541            return $bn;
542        }
543        throw new Zend_OpenId_Exception(
544            'The system doesn\'t have proper big integer extension',
545            Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
546    }
547
548    /**
549     * Converts internal ext/gmp or ext/bcmath big integer representation into
550     * binary string.
551     *
552     * @param mixed $bn big number
553     * @return string
554     * @throws Zend_OpenId_Exception
555     */
556    static protected function bigNumToBin($bn)
557    {
558        if (extension_loaded('gmp')) {
559            $s = gmp_strval($bn, 16);
560            if (strlen($s) % 2 != 0) {
561                $s = '0' . $s;
562            } else if ($s[0] > '7') {
563                $s = '00' . $s;
564            }
565            return pack("H*", $s);
566        } else if (extension_loaded('bcmath')) {
567            $cmp = bccomp($bn, 0);
568            if ($cmp == 0) {
569                return (chr(0));
570            } else if ($cmp < 0) {
571                throw new Zend_OpenId_Exception(
572                    'Big integer arithmetic error',
573                    Zend_OpenId_Exception::ERROR_LONG_MATH);
574            }
575            $bin = "";
576            while (bccomp($bn, 0) > 0) {
577                $bin = chr(bcmod($bn, 256)) . $bin;
578                $bn = bcdiv($bn, 256);
579            }
580            if (ord($bin[0]) > 127) {
581                $bin = chr(0) . $bin;
582            }
583            return $bin;
584        }
585        throw new Zend_OpenId_Exception(
586            'The system doesn\'t have proper big integer extension',
587            Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
588    }
589
590    /**
591     * Performs the first step of a Diffie-Hellman key exchange by generating
592     * private and public DH values based on given prime number $p and
593     * generator $g. Both sides of key exchange MUST have the same prime number
594     * and generator. In this case they will able to create a random shared
595     * secret that is never send from one to the other.
596     *
597     * @param string $p prime number in binary representation
598     * @param string $g generator in binary representation
599     * @param string $priv_key private key in binary representation
600     * @return mixed
601     */
602    static public function createDhKey($p, $g, $priv_key = null)
603    {
604        if (function_exists('openssl_dh_compute_key')) {
605            $dh_details = array(
606                    'p' => $p,
607                    'g' => $g
608                );
609            if (!is_null($priv_key)) {
610                $dh_details['priv_key'] = $priv_key;
611            }
612            return openssl_pkey_new(array('dh'=>$dh_details));
613        } else {
614            $bn_p        = self::binToBigNum($p);
615            $bn_g        = self::binToBigNum($g);
616            if (is_null($priv_key)) {
617                $priv_key    = self::randomBytes(strlen($p));
618            }
619            $bn_priv_key = self::binToBigNum($priv_key);
620            if (extension_loaded('gmp')) {
621                $bn_pub_key  = gmp_powm($bn_g, $bn_priv_key, $bn_p);
622            } else if (extension_loaded('bcmath')) {
623                $bn_pub_key  = bcpowmod($bn_g, $bn_priv_key, $bn_p);
624            }
625            $pub_key     = self::bigNumToBin($bn_pub_key);
626
627            return array(
628                'p'        => $bn_p,
629                'g'        => $bn_g,
630                'priv_key' => $bn_priv_key,
631                'pub_key'  => $bn_pub_key,
632                'details'  => array(
633                    'p'        => $p,
634                    'g'        => $g,
635                    'priv_key' => $priv_key,
636                    'pub_key'  => $pub_key));
637        }
638    }
639
640    /**
641     * Returns an associative array with Diffie-Hellman key components in
642     * binary representation. The array includes original prime number 'p' and
643     * generator 'g', random private key 'priv_key' and corresponding public
644     * key 'pub_key'.
645     *
646     * @param mixed $dh Diffie-Hellman key
647     * @return array
648     */
649    static public function getDhKeyDetails($dh)
650    {
651        if (function_exists('openssl_dh_compute_key')) {
652            $details = openssl_pkey_get_details($dh);
653            if (isset($details['dh'])) {
654                return $details['dh'];
655            }
656        } else {
657            return $dh['details'];
658        }
659    }
660
661    /**
662     * Computes the shared secret from the private DH value $dh and the other
663     * party's public value in $pub_key
664     *
665     * @param string $pub_key other party's public value
666     * @param mixed $dh Diffie-Hellman key
667     * @return string
668     * @throws Zend_OpenId_Exception
669     */
670    static public function computeDhSecret($pub_key, $dh)
671    {
672        if (function_exists('openssl_dh_compute_key')) {
673            $ret = openssl_dh_compute_key($pub_key, $dh);
674            if (ord($ret[0]) > 127) {
675                $ret = chr(0) . $ret;
676            }
677            return $ret;
678        } else if (extension_loaded('gmp')) {
679            $bn_pub_key = self::binToBigNum($pub_key);
680            $bn_secret  = gmp_powm($bn_pub_key, $dh['priv_key'], $dh['p']);
681            return self::bigNumToBin($bn_secret);
682        } else if (extension_loaded('bcmath')) {
683            $bn_pub_key = self::binToBigNum($pub_key);
684            $bn_secret  = bcpowmod($bn_pub_key, $dh['priv_key'], $dh['p']);
685            return self::bigNumToBin($bn_secret);
686        }
687        throw new Zend_OpenId_Exception(
688            'The system doesn\'t have proper big integer extension',
689            Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
690    }
691
692    /**
693     * Takes an arbitrary precision integer and returns its shortest big-endian
694     * two's complement representation.
695     *
696     * Arbitrary precision integers MUST be encoded as big-endian signed two's
697     * complement binary strings. Henceforth, "btwoc" is a function that takes
698     * an arbitrary precision integer and returns its shortest big-endian two's
699     * complement representation. All integers that are used with
700     * Diffie-Hellman Key Exchange are positive. This means that the left-most
701     * bit of the two's complement representation MUST be zero. If it is not,
702     * implementations MUST add a zero byte at the front of the string.
703     *
704     * @param string $str binary representation of arbitrary precision integer
705     * @return string big-endian signed representation
706     */
707    static public function btwoc($str)
708    {
709        if (ord($str[0]) > 127) {
710            return chr(0) . $str;
711        }
712        return $str;
713    }
714
715}