PageRenderTime 60ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/OpenId.php

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