AuthenticationCookie.class.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Cookie Authentication plugin for phpMyAdmin
  5. *
  6. * @package PhpMyAdmin-Authentication
  7. * @subpackage Cookie
  8. */
  9. if (! defined('PHPMYADMIN')) {
  10. exit;
  11. }
  12. /* Get the authentication interface */
  13. require_once 'libraries/plugins/AuthenticationPlugin.class.php';
  14. /**
  15. * Remember where to redirect the user
  16. * in case of an expired session.
  17. */
  18. if (! empty($_REQUEST['target'])) {
  19. $GLOBALS['target'] = $_REQUEST['target'];
  20. } else if (PMA_getenv('SCRIPT_NAME')) {
  21. $GLOBALS['target'] = basename(PMA_getenv('SCRIPT_NAME'));
  22. }
  23. /**
  24. * Initialization
  25. * Store the initialization vector because it will be needed for
  26. * further decryption. I don't think necessary to have one iv
  27. * per server so I don't put the server number in the cookie name.
  28. */
  29. if (function_exists('mcrypt_encrypt')) {
  30. if (empty($_COOKIE['pma_mcrypt_iv'])
  31. || ! ($iv = base64_decode($_COOKIE['pma_mcrypt_iv'], true))
  32. ) {
  33. srand((double) microtime() * 1000000);
  34. $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
  35. if ($td === false) {
  36. PMA_fatalError(__('Failed to use Blowfish from mcrypt!'));
  37. }
  38. $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
  39. $GLOBALS['PMA_Config']->setCookie(
  40. 'pma_mcrypt_iv',
  41. base64_encode($iv)
  42. );
  43. }
  44. }
  45. /**
  46. * Handles the cookie authentication method
  47. *
  48. * @package PhpMyAdmin-Authentication
  49. */
  50. class AuthenticationCookie extends AuthenticationPlugin
  51. {
  52. /**
  53. * Displays authentication form
  54. *
  55. * this function MUST exit/quit the application
  56. *
  57. * @global string the last connection error
  58. *
  59. * @return void
  60. */
  61. public function auth()
  62. {
  63. global $conn_error;
  64. $response = PMA_Response::getInstance();
  65. if ($response->isAjax()) {
  66. $response->isSuccess(false);
  67. $login_link = '<br /><br />[ ' .
  68. sprintf(
  69. '<a href="%s" class="ajax login-link">%s</a>',
  70. $GLOBALS['cfg']['PmaAbsoluteUri'],
  71. __('Log in')
  72. )
  73. . ' ]';
  74. if (! empty($conn_error)) {
  75. $conn_error .= $login_link;
  76. $response->addJSON(
  77. 'message',
  78. PMA_Message::error(
  79. $conn_error
  80. )
  81. );
  82. } else {
  83. $response->addJSON(
  84. 'message',
  85. PMA_Message::error(
  86. __('Your session has expired. Please log in again.') .
  87. $login_link
  88. )
  89. );
  90. }
  91. exit;
  92. }
  93. /* Perform logout to custom URL */
  94. if (! empty($_REQUEST['old_usr'])
  95. && ! empty($GLOBALS['cfg']['Server']['LogoutURL'])
  96. ) {
  97. PMA_sendHeaderLocation($GLOBALS['cfg']['Server']['LogoutURL']);
  98. exit;
  99. }
  100. // No recall if blowfish secret is not configured as it would produce
  101. // garbage
  102. if ($GLOBALS['cfg']['LoginCookieRecall']
  103. && ! empty($GLOBALS['cfg']['blowfish_secret'])
  104. ) {
  105. $default_user = $GLOBALS['PHP_AUTH_USER'];
  106. $default_server = $GLOBALS['pma_auth_server'];
  107. $autocomplete = '';
  108. } else {
  109. $default_user = '';
  110. $default_server = '';
  111. // skip the IE autocomplete feature.
  112. $autocomplete = ' autocomplete="off"';
  113. }
  114. $cell_align = ($GLOBALS['text_dir'] == 'ltr') ? 'left' : 'right';
  115. $response->getFooter()->setMinimal();
  116. $header = $response->getHeader();
  117. $header->setBodyId('loginform');
  118. $header->setTitle('phpMyAdmin');
  119. $header->disableMenu();
  120. $header->disableWarnings();
  121. if (file_exists(CUSTOM_HEADER_FILE)) {
  122. include CUSTOM_HEADER_FILE;
  123. }
  124. echo '
  125. <div class="container">
  126. <a href="';
  127. echo PMA_linkURL('https://www.phpmyadmin.net/');
  128. echo '" target="_blank" class="logo">';
  129. $logo_image = $GLOBALS['pmaThemeImage'] . 'logo_right.png';
  130. if (@file_exists($logo_image)) {
  131. echo '<img src="' . $logo_image
  132. . '" id="imLogo" name="imLogo" alt="phpMyAdmin" border="0" />';
  133. } else {
  134. echo '<img name="imLogo" id="imLogo" src="'
  135. . $GLOBALS['pmaThemeImage'] . 'pma_logo.png' . '" '
  136. . 'border="0" width="88" height="31" alt="phpMyAdmin" />';
  137. }
  138. echo '</a>
  139. <h1>';
  140. echo sprintf(
  141. __('Welcome to %s'),
  142. '<bdo dir="ltr" lang="en">phpMyAdmin</bdo>'
  143. );
  144. echo "</h1>";
  145. // Show error message
  146. if (! empty($conn_error)) {
  147. PMA_Message::rawError($conn_error)->display();
  148. }
  149. echo "<noscript>\n";
  150. PMA_message::error(
  151. __("Javascript must be enabled past this point")
  152. )->display();
  153. echo "</noscript>\n";
  154. echo "<div class='hide js-show'>";
  155. // Displays the languages form
  156. if (empty($GLOBALS['cfg']['Lang'])) {
  157. include_once './libraries/display_select_lang.lib.php';
  158. // use fieldset, don't show doc link
  159. echo PMA_getLanguageSelectorHtml(true, false);
  160. }
  161. echo '</div>
  162. <br />
  163. <!-- Login form -->
  164. <form method="post" action="index.php" name="login_form"' . $autocomplete .
  165. ' class="disableAjax login hide js-show">
  166. <fieldset>
  167. <legend>';
  168. echo __('Log in');
  169. echo PMA_Util::showDocu('index');
  170. echo '</legend>';
  171. if ($GLOBALS['cfg']['AllowArbitraryServer']) {
  172. echo '
  173. <div class="item">
  174. <label for="input_servername" title="';
  175. echo __(
  176. 'You can enter hostname/IP address and port separated by space.'
  177. );
  178. echo '">';
  179. echo __('Server:');
  180. echo '</label>
  181. <input type="text" name="pma_servername" id="input_servername"';
  182. echo ' value="';
  183. echo htmlspecialchars($default_server);
  184. echo '" size="24" class="textfield" title="';
  185. echo __(
  186. 'You can enter hostname/IP address and port separated by space.'
  187. ); echo '" />
  188. </div>';
  189. }
  190. echo '<div class="item">
  191. <label for="input_username">' . __('Username:') . '</label>
  192. <input type="text" name="pma_username" id="input_username" '
  193. . 'value="' . htmlspecialchars($default_user) . '" size="24"'
  194. . ' class="textfield"/>
  195. </div>
  196. <div class="item">
  197. <label for="input_password">' . __('Password:') . '</label>
  198. <input type="password" name="pma_password" id="input_password"'
  199. . ' value="" size="24" class="textfield" />
  200. </div>';
  201. if (count($GLOBALS['cfg']['Servers']) > 1) {
  202. echo '<div class="item">
  203. <label for="select_server">' . __('Server Choice') .':</label>
  204. <select name="server" id="select_server"';
  205. if ($GLOBALS['cfg']['AllowArbitraryServer']) {
  206. echo ' onchange="document.forms[\'login_form\'].'
  207. . 'elements[\'pma_servername\'].value = \'\'" ';
  208. }
  209. echo '>';
  210. include_once './libraries/select_server.lib.php';
  211. echo PMA_selectServer(false, false);
  212. echo '</select></div>';
  213. } else {
  214. echo ' <input type="hidden" name="server" value="'
  215. . $GLOBALS['server'] . '" />';
  216. } // end if (server choice)
  217. echo '</fieldset>
  218. <fieldset class="tblFooters">
  219. <input value="' . __('Go') . '" type="submit" id="input_go" />';
  220. $_form_params = array();
  221. if (! empty($GLOBALS['target'])) {
  222. $_form_params['target'] = $GLOBALS['target'];
  223. }
  224. if (! empty($GLOBALS['db'])) {
  225. $_form_params['db'] = $GLOBALS['db'];
  226. }
  227. if (! empty($GLOBALS['table'])) {
  228. $_form_params['table'] = $GLOBALS['table'];
  229. }
  230. // do not generate a "server" hidden field as we want the "server"
  231. // drop-down to have priority
  232. echo PMA_generate_common_hidden_inputs($_form_params, '', 0, 'server');
  233. echo '</fieldset>
  234. </form>';
  235. if ($GLOBALS['error_handler']->hasDisplayErrors()) {
  236. echo '<div>';
  237. $GLOBALS['error_handler']->dispErrors();
  238. echo '</div>';
  239. }
  240. echo '</div>';
  241. if (file_exists(CUSTOM_FOOTER_FILE)) {
  242. include CUSTOM_FOOTER_FILE;
  243. }
  244. exit;
  245. }
  246. /**
  247. * Gets advanced authentication settings
  248. *
  249. * this function DOES NOT check authentication - it just checks/provides
  250. * authentication credentials required to connect to the MySQL server
  251. * usually with PMA_DBI_connect()
  252. *
  253. * it returns false if something is missing - which usually leads to
  254. * auth() which displays login form
  255. *
  256. * it returns true if all seems ok which usually leads to auth_set_user()
  257. *
  258. * it directly switches to authFails() if user inactivity timout is reached
  259. *
  260. * @todo AllowArbitraryServer on does not imply that the user wants an
  261. * arbitrary server, or? so we should also check if this is filled
  262. * and not only if allowed
  263. *
  264. * @return boolean whether we get authentication settings or not
  265. */
  266. public function authCheck()
  267. {
  268. // Initialization
  269. /**
  270. * @global $GLOBALS['pma_auth_server'] the user provided server to
  271. * connect to
  272. */
  273. $GLOBALS['pma_auth_server'] = '';
  274. $GLOBALS['PHP_AUTH_USER'] = $GLOBALS['PHP_AUTH_PW'] = '';
  275. $GLOBALS['from_cookie'] = false;
  276. if (defined('PMA_CLEAR_COOKIES')) {
  277. foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
  278. $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $key);
  279. $GLOBALS['PMA_Config']->removeCookie('pmaUser-' . $key);
  280. }
  281. return false;
  282. }
  283. if (! empty($_REQUEST['old_usr'])) {
  284. // The user wants to be logged out
  285. // -> delete his choices that were stored in session
  286. // according to the PHP manual we should do this before the destroy:
  287. //$_SESSION = array();
  288. session_destroy();
  289. // -> delete password cookie(s)
  290. if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
  291. foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
  292. $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $key);
  293. if (isset($_COOKIE['pmaAuth-' . $key])) {
  294. unset($_COOKIE['pmaAuth-' . $key]);
  295. }
  296. }
  297. } else {
  298. $GLOBALS['PMA_Config']->removeCookie(
  299. 'pmaAuth-' . $GLOBALS['server']
  300. );
  301. if (isset($_COOKIE['pmaAuth-' . $GLOBALS['server']])) {
  302. unset($_COOKIE['pmaAuth-' . $GLOBALS['server']]);
  303. }
  304. }
  305. }
  306. if (! empty($_REQUEST['pma_username'])) {
  307. // The user just logged in
  308. $GLOBALS['PHP_AUTH_USER'] = PMA_sanitizeMySQLUser($_REQUEST['pma_username']);
  309. $GLOBALS['PHP_AUTH_PW'] = empty($_REQUEST['pma_password'])
  310. ? ''
  311. : $_REQUEST['pma_password'];
  312. if ($GLOBALS['cfg']['AllowArbitraryServer']
  313. && isset($_REQUEST['pma_servername'])
  314. ) {
  315. $GLOBALS['pma_auth_server'] = PMA_sanitizeMySQLHost($_REQUEST['pma_servername']);
  316. }
  317. return true;
  318. }
  319. // At the end, try to set the $GLOBALS['PHP_AUTH_USER']
  320. // and $GLOBALS['PHP_AUTH_PW'] variables from cookies
  321. // check cookies
  322. if (empty($_COOKIE['pmaUser-' . $GLOBALS['server']])) {
  323. return false;
  324. }
  325. $GLOBALS['PHP_AUTH_USER'] = $this->cookieDecrypt(
  326. $_COOKIE['pmaUser-' . $GLOBALS['server']],
  327. $this->_getEncryptionSecret()
  328. );
  329. // user was never logged in since session start
  330. if (empty($_SESSION['last_access_time'])) {
  331. return false;
  332. }
  333. // User inactive too long
  334. $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity'];
  335. if ($_SESSION['last_access_time'] < $last_access_time
  336. ) {
  337. PMA_Util::cacheUnset('is_create_db_priv', true);
  338. PMA_Util::cacheUnset('is_process_priv', true);
  339. PMA_Util::cacheUnset('is_reload_priv', true);
  340. PMA_Util::cacheUnset('db_to_create', true);
  341. PMA_Util::cacheUnset('dbs_where_create_table_allowed', true);
  342. $GLOBALS['no_activity'] = true;
  343. $this->authFails();
  344. exit;
  345. }
  346. // check password cookie
  347. if (empty($_COOKIE['pmaAuth-' . $GLOBALS['server']])) {
  348. return false;
  349. }
  350. $auth_data = json_decode(
  351. $this->cookieDecrypt(
  352. $_COOKIE['pmaAuth-' . $GLOBALS['server']],
  353. $this->_getSessionEncryptionSecret()
  354. ),
  355. true
  356. );
  357. if (! is_array($auth_data) || ! isset($auth_data['password'])) {
  358. return false;
  359. }
  360. $GLOBALS['PHP_AUTH_PW'] = $auth_data['password'];
  361. if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($auth_data['server'])) {
  362. $GLOBALS['pma_auth_server'] = $auth_data['server'];
  363. }
  364. $GLOBALS['from_cookie'] = true;
  365. return true;
  366. }
  367. /**
  368. * Set the user and password after last checkings if required
  369. *
  370. * @return boolean always true
  371. */
  372. public function authSetUser()
  373. {
  374. global $cfg;
  375. // Ensures valid authentication mode, 'only_db', bookmark database and
  376. // table names and relation table name are used
  377. if (! hash_equals($cfg['Server']['user'], $GLOBALS['PHP_AUTH_USER'])) {
  378. foreach ($cfg['Servers'] as $idx => $current) {
  379. if ($current['host'] == $cfg['Server']['host']
  380. && $current['port'] == $cfg['Server']['port']
  381. && $current['socket'] == $cfg['Server']['socket']
  382. && $current['ssl'] == $cfg['Server']['ssl']
  383. && $current['connect_type'] == $cfg['Server']['connect_type']
  384. && hash_equals($current['user'], $GLOBALS['PHP_AUTH_USER'])
  385. ) {
  386. $GLOBALS['server'] = $idx;
  387. $cfg['Server'] = $current;
  388. break;
  389. }
  390. } // end foreach
  391. } // end if
  392. if ($GLOBALS['cfg']['AllowArbitraryServer']
  393. && ! empty($GLOBALS['pma_auth_server'])
  394. ) {
  395. /* Allow to specify 'host port' */
  396. $parts = explode(' ', $GLOBALS['pma_auth_server']);
  397. if (count($parts) == 2) {
  398. $tmp_host = $parts[0];
  399. $tmp_port = $parts[1];
  400. } else {
  401. $tmp_host = $GLOBALS['pma_auth_server'];
  402. $tmp_port = '';
  403. }
  404. if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) {
  405. $cfg['Server']['host'] = $tmp_host;
  406. if (! empty($tmp_port)) {
  407. $cfg['Server']['port'] = $tmp_port;
  408. }
  409. }
  410. unset($tmp_host, $tmp_port, $parts);
  411. }
  412. $cfg['Server']['user'] = $GLOBALS['PHP_AUTH_USER'];
  413. $cfg['Server']['password'] = $GLOBALS['PHP_AUTH_PW'];
  414. // Avoid showing the password in phpinfo()'s output
  415. unset($GLOBALS['PHP_AUTH_PW']);
  416. unset($_SERVER['PHP_AUTH_PW']);
  417. $_SESSION['last_access_time'] = time();
  418. }
  419. /**
  420. * Stores user credentials after successful login.
  421. *
  422. * @return void
  423. */
  424. public function storeUserCredentials()
  425. {
  426. global $cfg;
  427. // Name and password cookies need to be refreshed each time
  428. // Duration = one month for username
  429. $this->storeUsernameCookie($cfg['Server']['user']);
  430. // Duration = as configured
  431. $this->storePasswordCookie($cfg['Server']['password']);
  432. // Set server cookies if required (once per session) and, in this case,
  433. // force reload to ensure the client accepts cookies
  434. if (! $GLOBALS['from_cookie']) {
  435. // URL where to go:
  436. $redirect_url = $cfg['PmaAbsoluteUri'] . 'index.php';
  437. // any parameters to pass?
  438. $url_params = array();
  439. if (strlen($GLOBALS['db'])) {
  440. $url_params['db'] = $GLOBALS['db'];
  441. }
  442. if (strlen($GLOBALS['table'])) {
  443. $url_params['table'] = $GLOBALS['table'];
  444. }
  445. // any target to pass?
  446. if (! empty($GLOBALS['target'])
  447. && $GLOBALS['target'] != 'index.php'
  448. ) {
  449. $url_params['target'] = $GLOBALS['target'];
  450. }
  451. /**
  452. * Clear user cache.
  453. */
  454. PMA_Util::clearUserCache();
  455. PMA_Response::getInstance()->disable();
  456. PMA_sendHeaderLocation(
  457. $redirect_url . PMA_generate_common_url($url_params, '&'),
  458. true
  459. );
  460. exit;
  461. } // end if
  462. return true;
  463. }
  464. /**
  465. * Stores username in a cookie.
  466. *
  467. * @param string $username User name
  468. *
  469. * @return void
  470. */
  471. public function storeUsernameCookie($username)
  472. {
  473. // Name and password cookies need to be refreshed each time
  474. // Duration = one month for username
  475. $GLOBALS['PMA_Config']->setCookie(
  476. 'pmaUser-' . $GLOBALS['server'],
  477. $this->cookieEncrypt(
  478. $username,
  479. $this->_getEncryptionSecret()
  480. )
  481. );
  482. }
  483. /**
  484. * Stores password in a cookie.
  485. *
  486. * @param string $password Password
  487. *
  488. * @return void
  489. */
  490. public function storePasswordCookie($password)
  491. {
  492. $payload = array('password' => $password);
  493. if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) {
  494. $payload['server'] = $GLOBALS['pma_auth_server'];
  495. }
  496. // Duration = as configured
  497. $GLOBALS['PMA_Config']->setCookie(
  498. 'pmaAuth-' . $GLOBALS['server'],
  499. $this->cookieEncrypt(
  500. json_encode($payload),
  501. $this->_getSessionEncryptionSecret()
  502. ),
  503. null,
  504. $GLOBALS['cfg']['LoginCookieStore']
  505. );
  506. }
  507. /**
  508. * User is not allowed to login to MySQL -> authentication failed
  509. *
  510. * prepares error message and switches to auth() which display the error
  511. * and the login form
  512. *
  513. * this function MUST exit/quit the application,
  514. * currently doen by call to auth()
  515. *
  516. * @return void
  517. */
  518. public function authFails()
  519. {
  520. global $conn_error;
  521. // Deletes password cookie and displays the login form
  522. $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $GLOBALS['server']);
  523. if (! empty($GLOBALS['login_without_password_is_forbidden'])) {
  524. $conn_error = __(
  525. 'Login without a password is forbidden by configuration'
  526. . ' (see AllowNoPassword)'
  527. );
  528. } elseif (! empty($GLOBALS['allowDeny_forbidden'])) {
  529. $conn_error = __('Access denied');
  530. } elseif (! empty($GLOBALS['no_activity'])) {
  531. $conn_error = sprintf(
  532. __('No activity within %s seconds; please log in again'),
  533. $GLOBALS['cfg']['LoginCookieValidity']
  534. );
  535. } elseif (PMA_DBI_getError()) {
  536. $conn_error = '#' . $GLOBALS['errno'] . ' '
  537. . __('Cannot log in to the MySQL server');
  538. } else {
  539. $conn_error = __('Cannot log in to the MySQL server');
  540. }
  541. // needed for PHP-CGI (not need for FastCGI or mod-php)
  542. header('Cache-Control: no-store, no-cache, must-revalidate');
  543. header('Pragma: no-cache');
  544. $this->auth();
  545. }
  546. /**
  547. * Returns blowfish secret or generates one if needed.
  548. *
  549. * @return string
  550. */
  551. private function _getEncryptionSecret()
  552. {
  553. if (empty($GLOBALS['cfg']['blowfish_secret'])) {
  554. return $this->_getSessionEncryptionSecret();
  555. } else {
  556. return $GLOBALS['cfg']['blowfish_secret'];
  557. }
  558. }
  559. /**
  560. * Returns blowfish secret or generates one if needed.
  561. *
  562. * @return string
  563. */
  564. private function _getSessionEncryptionSecret()
  565. {
  566. if (empty($_SESSION['encryption_key'])) {
  567. if ($this->_useOpenSSL()) {
  568. $_SESSION['encryption_key'] = openssl_random_pseudo_bytes(32);
  569. } else {
  570. include_once "./libraries/phpseclib/Crypt/Random.php";
  571. $_SESSION['encryption_key'] = crypt_random_string(32);
  572. }
  573. }
  574. return $_SESSION['encryption_key'];
  575. }
  576. /**
  577. * Checks whether we should use openssl for encryption.
  578. *
  579. * @return boolean
  580. */
  581. private function _useOpenSSL()
  582. {
  583. return (
  584. function_exists('openssl_encrypt')
  585. && function_exists('openssl_decrypt')
  586. && function_exists('openssl_random_pseudo_bytes')
  587. && PHP_VERSION_ID >= 50304
  588. );
  589. }
  590. /**
  591. * Concatenates secret in order to make it 16 bytes log
  592. *
  593. * This doesn't add any security, just ensures the secret
  594. * is long enough by copying it.
  595. *
  596. * @param string $secret Original secret
  597. *
  598. * @return string
  599. */
  600. public function enlargeSecret($secret)
  601. {
  602. while (strlen($secret) < 16) {
  603. $secret .= $secret;
  604. }
  605. return substr($secret, 0, 16);
  606. }
  607. /**
  608. * Derives MAC secret from encryption secret.
  609. *
  610. * @param string $secret the secret
  611. *
  612. * @return string the MAC secret
  613. */
  614. public function getMACSecret($secret)
  615. {
  616. // Grab first part, up to 16 chars
  617. // The MAC and AES secrets can overlap if original secret is short
  618. $length = strlen($secret);
  619. if ($length > 16) {
  620. return substr($secret, 0, 16);
  621. }
  622. return $this->enlargeSecret(
  623. $length == 1 ? $secret : substr($secret, 0, -1)
  624. );
  625. }
  626. /**
  627. * Derives AES secret from encryption secret.
  628. *
  629. * @param string $secret the secret
  630. *
  631. * @return string the AES secret
  632. */
  633. public function getAESSecret($secret)
  634. {
  635. // Grab second part, up to 16 chars
  636. // The MAC and AES secrets can overlap if original secret is short
  637. $length = strlen($secret);
  638. if ($length > 16) {
  639. return substr($secret, -16);
  640. }
  641. return $this->enlargeSecret(
  642. $length == 1 ? $secret : substr($secret, 1)
  643. );
  644. }
  645. /**
  646. * Encryption using openssl's AES or phpseclib's AES
  647. * (phpseclib uses mcrypt when it is available)
  648. *
  649. * @param string $data original data
  650. * @param string $secret the secret
  651. *
  652. * @return string the encrypted result
  653. */
  654. public function cookieEncrypt($data, $secret)
  655. {
  656. $mac_secret = $this->getMACSecret($secret);
  657. $aes_secret = $this->getAESSecret($secret);
  658. $iv = $this->createIV();
  659. if (! function_exists('mcrypt_encrypt')) {
  660. /**
  661. * This library uses mcrypt when available, so
  662. * we could always call it instead of having an
  663. * if/then/else logic, however the include_once
  664. * call is costly
  665. */
  666. include_once "./libraries/phpseclib/Crypt/AES.php";
  667. $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
  668. $cipher->setIV($iv);
  669. $cipher->setKey($aes_secret);
  670. $result = base64_encode($cipher->encrypt($data));
  671. } else {
  672. $result = base64_encode(
  673. mcrypt_encrypt(
  674. MCRYPT_BLOWFISH,
  675. $secret,
  676. $data,
  677. MCRYPT_MODE_CBC,
  678. $iv
  679. )
  680. );
  681. }
  682. $iv = base64_encode($iv);
  683. return json_encode(
  684. array(
  685. 'iv' => $iv,
  686. 'mac' => hash_hmac('sha1', $iv . $result, $mac_secret),
  687. 'payload' => $result,
  688. )
  689. );
  690. }
  691. /**
  692. * Decryption using blowfish algorithm (mcrypt)
  693. * or phpseclib's AES if mcrypt not available
  694. *
  695. * @param string $encdata encrypted data
  696. * @param string $secret the secret
  697. *
  698. * @return string|bool original data, false on error
  699. */
  700. public function cookieDecrypt($encdata, $secret)
  701. {
  702. $data = json_decode($encdata, true);
  703. if (! is_array($data) || ! isset($data['mac']) || ! isset($data['iv']) || ! isset($data['payload'])
  704. || ! is_string($data['mac']) || ! is_string($data['iv']) || ! is_string($data['payload'])
  705. ) {
  706. return false;
  707. }
  708. $mac_secret = $this->getMACSecret($secret);
  709. $aes_secret = $this->getAESSecret($secret);
  710. $newmac = hash_hmac('sha1', $data['iv'] . $data['payload'], $mac_secret);
  711. if (! hash_equals($data['mac'], $newmac)) {
  712. return false;
  713. }
  714. if (! function_exists('mcrypt_encrypt')) {
  715. include_once "./libraries/phpseclib/Crypt/AES.php";
  716. $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
  717. $cipher->setIV(base64_decode($data['iv']));
  718. $cipher->setKey($aes_secret);
  719. return $cipher->decrypt(base64_decode($data['payload']));
  720. } else {
  721. $decrypted = mcrypt_decrypt(
  722. MCRYPT_BLOWFISH,
  723. $secret,
  724. base64_decode($data['payload']),
  725. MCRYPT_MODE_CBC,
  726. base64_decode($data['iv'])
  727. );
  728. return trim($decrypted);
  729. }
  730. }
  731. /**
  732. * Returns size of IV for encryption.
  733. *
  734. * @return int
  735. */
  736. public function getIVSize()
  737. {
  738. include_once "./libraries/phpseclib/Crypt/AES.php";
  739. $cipher = new Crypt_AES(CRYPT_AES_MODE_ECB);
  740. return $cipher->block_size;
  741. }
  742. /**
  743. * Initialization
  744. * Store the initialization vector because it will be needed for
  745. * further decryption. I don't think necessary to have one iv
  746. * per server so I don't put the server number in the cookie name.
  747. *
  748. * @return void
  749. */
  750. public function createIV()
  751. {
  752. if (function_exists('mcrypt_create_iv')) {
  753. srand((double) microtime() * 1000000);
  754. $td = mcrypt_module_open(MCRYPT_BLOWFISH, '', MCRYPT_MODE_CBC, '');
  755. if ($td === false) {
  756. PMA_fatalError(__('Failed to use Blowfish from mcrypt!'));
  757. }
  758. return mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
  759. } else {
  760. include_once "./libraries/phpseclib/Crypt/Random.php";
  761. return crypt_random_string(
  762. $this->getIVSize()
  763. );
  764. }
  765. }
  766. /**
  767. * This method is called when any PluginManager to which the observer
  768. * is attached calls PluginManager::notify()
  769. *
  770. * @param SplSubject $subject The PluginManager notifying the observer
  771. * of an update.
  772. *
  773. * @return void
  774. */
  775. public function update (SplSubject $subject)
  776. {
  777. }
  778. }