OpenId.php 26 KB

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