PageRenderTime 290ms CodeModel.GetById 160ms app.highlight 47ms RepoModel.GetById 74ms app.codeStats 0ms

/library/Zend/OpenId.php

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