web/Zend/OpenId.php
changeset 0 4eba9c11703f
equal deleted inserted replaced
-1:000000000000 0:4eba9c11703f
       
     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-2010 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 22653 2010-07-22 18:41:39Z mabe $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @see Zend_Controller_Response_Abstract
       
    25  */
       
    26 require_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-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    39  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    40  */
       
    41 class 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 */
       
   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         return true;
       
   355     }
       
   356 
       
   357     /**
       
   358      * Normalizes OpenID identifier that can be URL or XRI name.
       
   359      * Returns true on success and false of failure.
       
   360      *
       
   361      * Normalization is performed according to the following rules:
       
   362      * 1. If the user's input starts with one of the "xri://", "xri://$ip*",
       
   363      *    or "xri://$dns*" prefixes, they MUST be stripped off, so that XRIs
       
   364      *    are used in the canonical form, and URI-authority XRIs are further
       
   365      *    considered URL identifiers.
       
   366      * 2. If the first character of the resulting string is an XRI Global
       
   367      *    Context Symbol ("=", "@", "+", "$", "!"), then the input SHOULD be
       
   368      *    treated as an XRI.
       
   369      * 3. Otherwise, the input SHOULD be treated as an http URL; if it does
       
   370      *    not include a "http" or "https" scheme, the Identifier MUST be
       
   371      *    prefixed with the string "http://".
       
   372      * 4. URL identifiers MUST then be further normalized by both following
       
   373      *    redirects when retrieving their content and finally applying the
       
   374      *    rules in Section 6 of [RFC3986] to the final destination URL.
       
   375      * @param string &$id identifier to be normalized
       
   376      * @return bool
       
   377      */
       
   378     static public function normalize(&$id)
       
   379     {
       
   380         $id = trim($id);
       
   381         if (strlen($id) === 0) {
       
   382             return true;
       
   383         }
       
   384 
       
   385         // 7.2.1
       
   386         if (strpos($id, 'xri://$ip*') === 0) {
       
   387             $id = substr($id, strlen('xri://$ip*'));
       
   388         } else if (strpos($id, 'xri://$dns*') === 0) {
       
   389             $id = substr($id, strlen('xri://$dns*'));
       
   390         } else if (strpos($id, 'xri://') === 0) {
       
   391             $id = substr($id, strlen('xri://'));
       
   392         }
       
   393 
       
   394         // 7.2.2
       
   395         if ($id[0] == '=' ||
       
   396             $id[0] == '@' ||
       
   397             $id[0] == '+' ||
       
   398             $id[0] == '$' ||
       
   399             $id[0] == '!') {
       
   400             return true;
       
   401         }
       
   402 
       
   403         // 7.2.3
       
   404         if (strpos($id, "://") === false) {
       
   405             $id = 'http://' . $id;
       
   406         }
       
   407 
       
   408         // 7.2.4
       
   409         return self::normalizeURL($id);
       
   410     }
       
   411 
       
   412     /**
       
   413      * Performs a HTTP redirection to specified URL with additional data.
       
   414      * It may generate redirected request using GET or POST HTTP method.
       
   415      * The function never returns.
       
   416      *
       
   417      * @param string $url URL to redirect to
       
   418      * @param array $params additional variable/value pairs to send
       
   419      * @param Zend_Controller_Response_Abstract $response
       
   420      * @param string $method redirection method ('GET' or 'POST')
       
   421      */
       
   422     static public function redirect($url, $params = null,
       
   423         Zend_Controller_Response_Abstract $response = null, $method = 'GET')
       
   424     {
       
   425         $url = Zend_OpenId::absoluteUrl($url);
       
   426         $body = "";
       
   427         if (null === $response) {
       
   428             require_once "Zend/Controller/Response/Http.php";
       
   429             $response = new Zend_Controller_Response_Http();
       
   430         }
       
   431 
       
   432         if ($method == 'POST') {
       
   433             $body = "<html><body onLoad=\"document.forms[0].submit();\">\n";
       
   434             $body .= "<form method=\"POST\" action=\"$url\">\n";
       
   435             if (is_array($params) && count($params) > 0) {
       
   436                 foreach($params as $key => $value) {
       
   437                     $body .= '<input type="hidden" name="' . $key . '" value="' . $value . "\">\n";
       
   438                 }
       
   439             }
       
   440             $body .= "<input type=\"submit\" value=\"Continue OpenID transaction\">\n";
       
   441             $body .= "</form></body></html>\n";
       
   442         } else if (is_array($params) && count($params) > 0) {
       
   443             if (strpos($url, '?') === false) {
       
   444                 $url .= '?' . self::paramsToQuery($params);
       
   445             } else {
       
   446                 $url .= '&' . self::paramsToQuery($params);
       
   447             }
       
   448         }
       
   449         if (!empty($body)) {
       
   450             $response->setBody($body);
       
   451         } else if (!$response->canSendHeaders()) {
       
   452             $response->setBody("<script language=\"JavaScript\"" .
       
   453                  " type=\"text/javascript\">window.location='$url';" .
       
   454                  "</script>");
       
   455         } else {
       
   456             $response->setRedirect($url);
       
   457         }
       
   458         $response->sendResponse();
       
   459         if (self::$exitOnRedirect) {
       
   460             exit();
       
   461         }
       
   462     }
       
   463 
       
   464     /**
       
   465      * Produces string of random byte of given length.
       
   466      *
       
   467      * @param integer $len length of requested string
       
   468      * @return string RAW random binary string
       
   469      */
       
   470     static public function randomBytes($len)
       
   471     {
       
   472         $key = '';
       
   473         for($i=0; $i < $len; $i++) {
       
   474             $key .= chr(mt_rand(0, 255));
       
   475         }
       
   476         return $key;
       
   477     }
       
   478 
       
   479     /**
       
   480      * Generates a hash value (message digest) according to given algorithm.
       
   481      * It returns RAW binary string.
       
   482      *
       
   483      * This is a wrapper function that uses one of available internal function
       
   484      * dependent on given PHP configuration. It may use various functions from
       
   485      *  ext/openssl, ext/hash, ext/mhash or ext/standard.
       
   486      *
       
   487      * @param string $func digest algorithm
       
   488      * @param string $data data to sign
       
   489      * @return string RAW digital signature
       
   490      * @throws Zend_OpenId_Exception
       
   491      */
       
   492     static public function digest($func, $data)
       
   493     {
       
   494         if (function_exists('openssl_digest')) {
       
   495             return openssl_digest($data, $func, true);
       
   496         } else if (function_exists('hash')) {
       
   497             return hash($func, $data, true);
       
   498         } else if ($func === 'sha1') {
       
   499             return sha1($data, true);
       
   500         } else if ($func === 'sha256') {
       
   501             if (function_exists('mhash')) {
       
   502                 return mhash(MHASH_SHA256 , $data);
       
   503             }
       
   504         }
       
   505         require_once "Zend/OpenId/Exception.php";
       
   506         throw new Zend_OpenId_Exception(
       
   507             'Unsupported digest algorithm "' . $func . '".',
       
   508             Zend_OpenId_Exception::UNSUPPORTED_DIGEST);
       
   509     }
       
   510 
       
   511     /**
       
   512      * Generates a keyed hash value using the HMAC method. It uses ext/hash
       
   513      * if available or user-level PHP implementation, that is not significantly
       
   514      * slower.
       
   515      *
       
   516      * @param string $macFunc name of selected hashing algorithm (sha1, sha256)
       
   517      * @param string $data data to sign
       
   518      * @param string $secret shared secret key used for generating the HMAC
       
   519      *  variant of the message digest
       
   520      * @return string RAW HMAC value
       
   521      */
       
   522     static public function hashHmac($macFunc, $data, $secret)
       
   523     {
       
   524 //        require_once "Zend/Crypt/Hmac.php";
       
   525 //        return Zend_Crypt_Hmac::compute($secret, $macFunc, $data, Zend_Crypt_Hmac::BINARY);
       
   526         if (function_exists('hash_hmac')) {
       
   527             return hash_hmac($macFunc, $data, $secret, 1);
       
   528         } else {
       
   529             if (Zend_OpenId::strlen($secret) > 64) {
       
   530                 $secret = self::digest($macFunc, $secret);
       
   531             }
       
   532             $secret = str_pad($secret, 64, chr(0x00));
       
   533             $ipad = str_repeat(chr(0x36), 64);
       
   534             $opad = str_repeat(chr(0x5c), 64);
       
   535             $hash1 = self::digest($macFunc, ($secret ^ $ipad) . $data);
       
   536             return self::digest($macFunc, ($secret ^ $opad) . $hash1);
       
   537         }
       
   538     }
       
   539 
       
   540     /**
       
   541      * Converts binary representation into ext/gmp or ext/bcmath big integer
       
   542      * representation.
       
   543      *
       
   544      * @param string $bin binary representation of big number
       
   545      * @return mixed
       
   546      * @throws Zend_OpenId_Exception
       
   547      */
       
   548     static protected function binToBigNum($bin)
       
   549     {
       
   550         if (extension_loaded('gmp')) {
       
   551             return gmp_init(bin2hex($bin), 16);
       
   552         } else if (extension_loaded('bcmath')) {
       
   553             $bn = 0;
       
   554             $len = Zend_OpenId::strlen($bin);
       
   555             for ($i = 0; $i < $len; $i++) {
       
   556                 $bn = bcmul($bn, 256);
       
   557                 $bn = bcadd($bn, ord($bin[$i]));
       
   558             }
       
   559             return $bn;
       
   560         }
       
   561         require_once "Zend/OpenId/Exception.php";
       
   562         throw new Zend_OpenId_Exception(
       
   563             'The system doesn\'t have proper big integer extension',
       
   564             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
       
   565     }
       
   566 
       
   567     /**
       
   568      * Converts internal ext/gmp or ext/bcmath big integer representation into
       
   569      * binary string.
       
   570      *
       
   571      * @param mixed $bn big number
       
   572      * @return string
       
   573      * @throws Zend_OpenId_Exception
       
   574      */
       
   575     static protected function bigNumToBin($bn)
       
   576     {
       
   577         if (extension_loaded('gmp')) {
       
   578             $s = gmp_strval($bn, 16);
       
   579             if (strlen($s) % 2 != 0) {
       
   580                 $s = '0' . $s;
       
   581             } else if ($s[0] > '7') {
       
   582                 $s = '00' . $s;
       
   583             }
       
   584             return pack("H*", $s);
       
   585         } else if (extension_loaded('bcmath')) {
       
   586             $cmp = bccomp($bn, 0);
       
   587             if ($cmp == 0) {
       
   588                 return "\0";
       
   589             } else if ($cmp < 0) {
       
   590                 require_once "Zend/OpenId/Exception.php";
       
   591                 throw new Zend_OpenId_Exception(
       
   592                     'Big integer arithmetic error',
       
   593                     Zend_OpenId_Exception::ERROR_LONG_MATH);
       
   594             }
       
   595             $bin = "";
       
   596             while (bccomp($bn, 0) > 0) {
       
   597                 $bin = chr(bcmod($bn, 256)) . $bin;
       
   598                 $bn = bcdiv($bn, 256);
       
   599             }
       
   600             if (ord($bin[0]) > 127) {
       
   601                 $bin = "\0" . $bin;
       
   602             }
       
   603             return $bin;
       
   604         }
       
   605         require_once "Zend/OpenId/Exception.php";
       
   606         throw new Zend_OpenId_Exception(
       
   607             'The system doesn\'t have proper big integer extension',
       
   608             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
       
   609     }
       
   610 
       
   611     /**
       
   612      * Performs the first step of a Diffie-Hellman key exchange by generating
       
   613      * private and public DH values based on given prime number $p and
       
   614      * generator $g. Both sides of key exchange MUST have the same prime number
       
   615      * and generator. In this case they will able to create a random shared
       
   616      * secret that is never send from one to the other.
       
   617      *
       
   618      * @param string $p prime number in binary representation
       
   619      * @param string $g generator in binary representation
       
   620      * @param string $priv_key private key in binary representation
       
   621      * @return mixed
       
   622      */
       
   623     static public function createDhKey($p, $g, $priv_key = null)
       
   624     {
       
   625         if (function_exists('openssl_dh_compute_key')) {
       
   626             $dh_details = array(
       
   627                     'p' => $p,
       
   628                     'g' => $g
       
   629                 );
       
   630             if ($priv_key !== null) {
       
   631                 $dh_details['priv_key'] = $priv_key;
       
   632             }
       
   633             return openssl_pkey_new(array('dh'=>$dh_details));
       
   634         } else {
       
   635             $bn_p        = self::binToBigNum($p);
       
   636             $bn_g        = self::binToBigNum($g);
       
   637             if ($priv_key === null) {
       
   638                 $priv_key    = self::randomBytes(Zend_OpenId::strlen($p));
       
   639             }
       
   640             $bn_priv_key = self::binToBigNum($priv_key);
       
   641             if (extension_loaded('gmp')) {
       
   642                 $bn_pub_key  = gmp_powm($bn_g, $bn_priv_key, $bn_p);
       
   643             } else if (extension_loaded('bcmath')) {
       
   644                 $bn_pub_key  = bcpowmod($bn_g, $bn_priv_key, $bn_p);
       
   645             }
       
   646             $pub_key     = self::bigNumToBin($bn_pub_key);
       
   647 
       
   648             return array(
       
   649                 'p'        => $bn_p,
       
   650                 'g'        => $bn_g,
       
   651                 'priv_key' => $bn_priv_key,
       
   652                 'pub_key'  => $bn_pub_key,
       
   653                 'details'  => array(
       
   654                     'p'        => $p,
       
   655                     'g'        => $g,
       
   656                     'priv_key' => $priv_key,
       
   657                     'pub_key'  => $pub_key));
       
   658         }
       
   659     }
       
   660 
       
   661     /**
       
   662      * Returns an associative array with Diffie-Hellman key components in
       
   663      * binary representation. The array includes original prime number 'p' and
       
   664      * generator 'g', random private key 'priv_key' and corresponding public
       
   665      * key 'pub_key'.
       
   666      *
       
   667      * @param mixed $dh Diffie-Hellman key
       
   668      * @return array
       
   669      */
       
   670     static public function getDhKeyDetails($dh)
       
   671     {
       
   672         if (function_exists('openssl_dh_compute_key')) {
       
   673             $details = openssl_pkey_get_details($dh);
       
   674             if (isset($details['dh'])) {
       
   675                 return $details['dh'];
       
   676             }
       
   677         } else {
       
   678             return $dh['details'];
       
   679         }
       
   680     }
       
   681 
       
   682     /**
       
   683      * Computes the shared secret from the private DH value $dh and the other
       
   684      * party's public value in $pub_key
       
   685      *
       
   686      * @param string $pub_key other party's public value
       
   687      * @param mixed $dh Diffie-Hellman key
       
   688      * @return string
       
   689      * @throws Zend_OpenId_Exception
       
   690      */
       
   691     static public function computeDhSecret($pub_key, $dh)
       
   692     {
       
   693         if (function_exists('openssl_dh_compute_key')) {
       
   694             $ret = openssl_dh_compute_key($pub_key, $dh);
       
   695             if (ord($ret[0]) > 127) {
       
   696                 $ret = "\0" . $ret;
       
   697             }
       
   698             return $ret;
       
   699         } else if (extension_loaded('gmp')) {
       
   700             $bn_pub_key = self::binToBigNum($pub_key);
       
   701             $bn_secret  = gmp_powm($bn_pub_key, $dh['priv_key'], $dh['p']);
       
   702             return self::bigNumToBin($bn_secret);
       
   703         } else if (extension_loaded('bcmath')) {
       
   704             $bn_pub_key = self::binToBigNum($pub_key);
       
   705             $bn_secret  = bcpowmod($bn_pub_key, $dh['priv_key'], $dh['p']);
       
   706             return self::bigNumToBin($bn_secret);
       
   707         }
       
   708         require_once "Zend/OpenId/Exception.php";
       
   709         throw new Zend_OpenId_Exception(
       
   710             'The system doesn\'t have proper big integer extension',
       
   711             Zend_OpenId_Exception::UNSUPPORTED_LONG_MATH);
       
   712     }
       
   713 
       
   714     /**
       
   715      * Takes an arbitrary precision integer and returns its shortest big-endian
       
   716      * two's complement representation.
       
   717      *
       
   718      * Arbitrary precision integers MUST be encoded as big-endian signed two's
       
   719      * complement binary strings. Henceforth, "btwoc" is a function that takes
       
   720      * an arbitrary precision integer and returns its shortest big-endian two's
       
   721      * complement representation. All integers that are used with
       
   722      * Diffie-Hellman Key Exchange are positive. This means that the left-most
       
   723      * bit of the two's complement representation MUST be zero. If it is not,
       
   724      * implementations MUST add a zero byte at the front of the string.
       
   725      *
       
   726      * @param string $str binary representation of arbitrary precision integer
       
   727      * @return string big-endian signed representation
       
   728      */
       
   729     static public function btwoc($str)
       
   730     {
       
   731         if (ord($str[0]) > 127) {
       
   732             return "\0" . $str;
       
   733         }
       
   734         return $str;
       
   735     }
       
   736 
       
   737     /**
       
   738      * Returns lenght of binary string in bytes
       
   739      *
       
   740      * @param string $str
       
   741      * @return int the string lenght
       
   742      */
       
   743     static public function strlen($str)
       
   744     {
       
   745         if (extension_loaded('mbstring') &&
       
   746             (((int)ini_get('mbstring.func_overload')) & 2)) {
       
   747             return mb_strlen($str, 'latin1');
       
   748         } else {
       
   749             return strlen($str);
       
   750         }
       
   751     }
       
   752 
       
   753 }