"Fossies" - the Fresh Open Source Software Archive 
Member "phpMyAdmin-5.1.0-all-languages/libraries/classes/Plugins/Auth/AuthenticationCookie.php" (24 Feb 2021, 29369 Bytes) of package /linux/www/phpMyAdmin-5.1.0-all-languages.zip:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the last
Fossies "Diffs" side-by-side code changes report for "AuthenticationCookie.php":
5.0.4-english_vs_5.1.0-english.
1 <?php
2 /**
3 * Cookie Authentication plugin for phpMyAdmin
4 */
5
6 declare(strict_types=1);
7
8 namespace PhpMyAdmin\Plugins\Auth;
9
10 use PhpMyAdmin\Config;
11 use PhpMyAdmin\Core;
12 use PhpMyAdmin\LanguageManager;
13 use PhpMyAdmin\Message;
14 use PhpMyAdmin\Plugins\AuthenticationPlugin;
15 use PhpMyAdmin\Response;
16 use PhpMyAdmin\Server\Select;
17 use PhpMyAdmin\Session;
18 use PhpMyAdmin\Template;
19 use PhpMyAdmin\Url;
20 use PhpMyAdmin\Util;
21 use PhpMyAdmin\Utils\SessionCache;
22 use phpseclib\Crypt;
23 use phpseclib\Crypt\Random;
24 use ReCaptcha;
25 use function base64_decode;
26 use function base64_encode;
27 use function class_exists;
28 use function count;
29 use function defined;
30 use function explode;
31 use function function_exists;
32 use function hash_equals;
33 use function hash_hmac;
34 use function in_array;
35 use function ini_get;
36 use function intval;
37 use function is_array;
38 use function is_string;
39 use function json_decode;
40 use function json_encode;
41 use function openssl_cipher_iv_length;
42 use function openssl_decrypt;
43 use function openssl_encrypt;
44 use function openssl_error_string;
45 use function openssl_random_pseudo_bytes;
46 use function preg_match;
47 use function session_id;
48 use function strlen;
49 use function substr;
50 use function time;
51
52 /**
53 * Handles the cookie authentication method
54 */
55 class AuthenticationCookie extends AuthenticationPlugin
56 {
57 /**
58 * IV for encryption
59 *
60 * @var string|null
61 */
62 private $cookieIv = null;
63
64 /**
65 * Whether to use OpenSSL directly
66 *
67 * @var bool
68 */
69 private $useOpenSsl;
70
71 public function __construct()
72 {
73 parent::__construct();
74 $this->useOpenSsl = ! class_exists(Random::class);
75 }
76
77 /**
78 * Forces (not)using of openSSL
79 *
80 * @param bool $use The flag
81 *
82 * @return void
83 */
84 public function setUseOpenSSL($use)
85 {
86 $this->useOpenSsl = $use;
87 }
88
89 /**
90 * Displays authentication form
91 *
92 * this function MUST exit/quit the application
93 *
94 * @return bool|void
95 *
96 * @global string $conn_error the last connection error
97 */
98 public function showLoginForm()
99 {
100 global $conn_error, $route;
101
102 $response = Response::getInstance();
103
104 /**
105 * When sending login modal after session has expired, send the
106 * new token explicitly with the response to update the token
107 * in all the forms having a hidden token.
108 */
109 $session_expired = isset($_REQUEST['check_timeout']) || isset($_REQUEST['session_timedout']);
110 if (! $session_expired && $response->loginPage()) {
111 if (defined('TESTSUITE')) {
112 return true;
113 }
114
115 exit;
116 }
117
118 /**
119 * When sending login modal after session has expired, send the
120 * new token explicitly with the response to update the token
121 * in all the forms having a hidden token.
122 */
123 if ($session_expired) {
124 $response->setRequestStatus(false);
125 $response->addJSON(
126 'new_token',
127 $_SESSION[' PMA_token ']
128 );
129 }
130
131 /**
132 * logged_in response parameter is used to check if the login,
133 * using the modal was successful after session expiration.
134 */
135 if (isset($_REQUEST['session_timedout'])) {
136 $response->addJSON(
137 'logged_in',
138 0
139 );
140 }
141
142 // No recall if blowfish secret is not configured as it would produce
143 // garbage
144 if ($GLOBALS['cfg']['LoginCookieRecall']
145 && ! empty($GLOBALS['cfg']['blowfish_secret'])
146 ) {
147 $default_user = $this->user;
148 $default_server = $GLOBALS['pma_auth_server'];
149 $hasAutocomplete = true;
150 } else {
151 $default_user = '';
152 $default_server = '';
153 $hasAutocomplete = false;
154 }
155
156 // wrap the login form in a div which overlays the whole page.
157 if ($session_expired) {
158 $loginHeader = $this->template->render('login/header', [
159 'theme' => $GLOBALS['PMA_Theme'],
160 'add_class' => ' modal_form',
161 'session_expired' => 1,
162 ]);
163 } else {
164 $loginHeader = $this->template->render('login/header', [
165 'theme' => $GLOBALS['PMA_Theme'],
166 'add_class' => '',
167 'session_expired' => 0,
168 ]);
169 }
170
171 $errorMessages = '';
172 // Show error message
173 if (! empty($conn_error)) {
174 $errorMessages = Message::rawError((string) $conn_error)->getDisplay();
175 } elseif (isset($_GET['session_expired'])
176 && intval($_GET['session_expired']) == 1
177 ) {
178 $errorMessages = Message::rawError(
179 __('Your session has expired. Please log in again.')
180 )->getDisplay();
181 }
182
183 $language_manager = LanguageManager::getInstance();
184 $languageSelector = '';
185 $hasLanguages = empty($GLOBALS['cfg']['Lang']) && $language_manager->hasChoice();
186 if ($hasLanguages) {
187 $languageSelector = $language_manager->getSelectorDisplay(new Template(), true, false);
188 }
189
190 $serversOptions = '';
191 $hasServers = count($GLOBALS['cfg']['Servers']) > 1;
192 if ($hasServers) {
193 $serversOptions = Select::render(false, false);
194 }
195
196 $_form_params = [];
197 if (isset($route)) {
198 $_form_params['route'] = $route;
199 }
200 if (strlen($GLOBALS['db'])) {
201 $_form_params['db'] = $GLOBALS['db'];
202 }
203 if (strlen($GLOBALS['table'])) {
204 $_form_params['table'] = $GLOBALS['table'];
205 }
206
207 $errors = '';
208 if ($GLOBALS['error_handler']->hasDisplayErrors()) {
209 $errors = $GLOBALS['error_handler']->getDispErrors();
210 }
211
212 // close the wrapping div tag, if the request is after session timeout
213 if ($session_expired) {
214 $loginFooter = $this->template->render('login/footer', ['session_expired' => 1]);
215 } else {
216 $loginFooter = $this->template->render('login/footer', ['session_expired' => 0]);
217 }
218
219 $configFooter = Config::renderFooter();
220
221 echo $this->template->render('login/form', [
222 'login_header' => $loginHeader,
223 'is_demo' => $GLOBALS['cfg']['DBG']['demo'],
224 'error_messages' => $errorMessages,
225 'has_languages' => $hasLanguages,
226 'language_selector' => $languageSelector,
227 'is_session_expired' => $session_expired,
228 'has_autocomplete' => $hasAutocomplete,
229 'session_id' => session_id(),
230 'is_arbitrary_server_allowed' => $GLOBALS['cfg']['AllowArbitraryServer'],
231 'default_server' => $default_server,
232 'default_user' => $default_user,
233 'has_servers' => $hasServers,
234 'server_options' => $serversOptions,
235 'server' => $GLOBALS['server'],
236 'lang' => $GLOBALS['lang'],
237 'has_captcha' => ! empty($GLOBALS['cfg']['CaptchaApi'])
238 && ! empty($GLOBALS['cfg']['CaptchaRequestParam'])
239 && ! empty($GLOBALS['cfg']['CaptchaResponseParam'])
240 && ! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
241 && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey']),
242 'use_captcha_checkbox' => ($GLOBALS['cfg']['CaptchaMethod'] ?? '') === 'checkbox',
243 'captcha_api' => $GLOBALS['cfg']['CaptchaApi'],
244 'captcha_req' => $GLOBALS['cfg']['CaptchaRequestParam'],
245 'captcha_resp' => $GLOBALS['cfg']['CaptchaResponseParam'],
246 'captcha_key' => $GLOBALS['cfg']['CaptchaLoginPublicKey'],
247 'form_params' => $_form_params,
248 'errors' => $errors,
249 'login_footer' => $loginFooter,
250 'config_footer' => $configFooter,
251 ]);
252
253 if (! defined('TESTSUITE')) {
254 exit;
255 }
256
257 return true;
258 }
259
260 /**
261 * Gets authentication credentials
262 *
263 * this function DOES NOT check authentication - it just checks/provides
264 * authentication credentials required to connect to the MySQL server
265 * usually with $dbi->connect()
266 *
267 * it returns false if something is missing - which usually leads to
268 * showLoginForm() which displays login form
269 *
270 * it returns true if all seems ok which usually leads to auth_set_user()
271 *
272 * it directly switches to showFailure() if user inactivity timeout is reached
273 *
274 * @return bool whether we get authentication settings or not
275 */
276 public function readCredentials()
277 {
278 global $conn_error;
279
280 // Initialization
281 /**
282 * @global $GLOBALS['pma_auth_server'] the user provided server to
283 * connect to
284 */
285 $GLOBALS['pma_auth_server'] = '';
286
287 $this->user = $this->password = '';
288 $GLOBALS['from_cookie'] = false;
289
290 if (isset($_POST['pma_username']) && strlen($_POST['pma_username']) > 0) {
291 // Verify Captcha if it is required.
292 if (! empty($GLOBALS['cfg']['CaptchaApi'])
293 && ! empty($GLOBALS['cfg']['CaptchaRequestParam'])
294 && ! empty($GLOBALS['cfg']['CaptchaResponseParam'])
295 && ! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
296 && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
297 ) {
298 if (empty($_POST[$GLOBALS['cfg']['CaptchaResponseParam']])) {
299 $conn_error = __('Missing reCAPTCHA verification, maybe it has been blocked by adblock?');
300
301 return false;
302 }
303
304 $captchaSiteVerifyURL = $GLOBALS['cfg']['CaptchaSiteVerifyURL'] ?? '';
305 $captchaSiteVerifyURL = empty($captchaSiteVerifyURL) ? null : $captchaSiteVerifyURL;
306 if (function_exists('curl_init')) {
307 $reCaptcha = new ReCaptcha\ReCaptcha(
308 $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
309 new ReCaptcha\RequestMethod\CurlPost(null, $captchaSiteVerifyURL)
310 );
311 } elseif (ini_get('allow_url_fopen')) {
312 $reCaptcha = new ReCaptcha\ReCaptcha(
313 $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
314 new ReCaptcha\RequestMethod\Post($captchaSiteVerifyURL)
315 );
316 } else {
317 $reCaptcha = new ReCaptcha\ReCaptcha(
318 $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
319 new ReCaptcha\RequestMethod\SocketPost(null, $captchaSiteVerifyURL)
320 );
321 }
322
323 // verify captcha status.
324 $resp = $reCaptcha->verify(
325 $_POST[$GLOBALS['cfg']['CaptchaResponseParam']],
326 Core::getIp()
327 );
328
329 // Check if the captcha entered is valid, if not stop the login.
330 if ($resp == null || ! $resp->isSuccess()) {
331 $codes = $resp->getErrorCodes();
332
333 if (in_array('invalid-json', $codes)) {
334 $conn_error = __('Failed to connect to the reCAPTCHA service!');
335 } else {
336 $conn_error = __('Entered captcha is wrong, try again!');
337 }
338
339 return false;
340 }
341 }
342
343 // The user just logged in
344 $this->user = Core::sanitizeMySQLUser($_POST['pma_username']);
345
346 $password = $_POST['pma_password'] ?? '';
347 if (strlen($password) >= 1000) {
348 $conn_error = __('Your password is too long. To prevent denial-of-service attacks, ' .
349 'phpMyAdmin restricts passwords to less than 1000 characters.');
350
351 return false;
352 }
353 $this->password = $password;
354
355 if ($GLOBALS['cfg']['AllowArbitraryServer']
356 && isset($_REQUEST['pma_servername'])
357 ) {
358 if ($GLOBALS['cfg']['ArbitraryServerRegexp']) {
359 $parts = explode(' ', $_REQUEST['pma_servername']);
360 if (count($parts) === 2) {
361 $tmp_host = $parts[0];
362 } else {
363 $tmp_host = $_REQUEST['pma_servername'];
364 }
365
366 $match = preg_match(
367 $GLOBALS['cfg']['ArbitraryServerRegexp'],
368 $tmp_host
369 );
370 if (! $match) {
371 $conn_error = __(
372 'You are not allowed to log in to this MySQL server!'
373 );
374
375 return false;
376 }
377 }
378 $GLOBALS['pma_auth_server'] = Core::sanitizeMySQLHost($_REQUEST['pma_servername']);
379 }
380 /* Secure current session on login to avoid session fixation */
381 Session::secure();
382
383 return true;
384 }
385
386 // At the end, try to set the $this->user
387 // and $this->password variables from cookies
388
389 // check cookies
390 $serverCookie = $GLOBALS['PMA_Config']->getCookie('pmaUser-' . $GLOBALS['server']);
391 if (empty($serverCookie)) {
392 return false;
393 }
394
395 $value = $this->cookieDecrypt(
396 $serverCookie,
397 $this->getEncryptionSecret()
398 );
399
400 if ($value === false) {
401 return false;
402 }
403
404 $this->user = $value;
405 // user was never logged in since session start
406 if (empty($_SESSION['browser_access_time'])) {
407 return false;
408 }
409
410 // User inactive too long
411 $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity'];
412 foreach ($_SESSION['browser_access_time'] as $key => $value) {
413 if ($value >= $last_access_time) {
414 continue;
415 }
416
417 unset($_SESSION['browser_access_time'][$key]);
418 }
419 // All sessions expired
420 if (empty($_SESSION['browser_access_time'])) {
421 SessionCache::remove('is_create_db_priv');
422 SessionCache::remove('is_reload_priv');
423 SessionCache::remove('db_to_create');
424 SessionCache::remove('dbs_where_create_table_allowed');
425 SessionCache::remove('dbs_to_test');
426 SessionCache::remove('db_priv');
427 SessionCache::remove('col_priv');
428 SessionCache::remove('table_priv');
429 SessionCache::remove('proc_priv');
430
431 $this->showFailure('no-activity');
432 if (! defined('TESTSUITE')) {
433 exit;
434 }
435
436 return false;
437 }
438
439 // check password cookie
440 $serverCookie = $GLOBALS['PMA_Config']->getCookie('pmaAuth-' . $GLOBALS['server']);
441
442 if (empty($serverCookie)) {
443 return false;
444 }
445 $value = $this->cookieDecrypt(
446 $serverCookie,
447 $this->getSessionEncryptionSecret()
448 );
449 if ($value === false) {
450 return false;
451 }
452
453 $auth_data = json_decode($value, true);
454
455 if (! is_array($auth_data) || ! isset($auth_data['password'])) {
456 return false;
457 }
458 $this->password = $auth_data['password'];
459 if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($auth_data['server'])) {
460 $GLOBALS['pma_auth_server'] = $auth_data['server'];
461 }
462
463 $GLOBALS['from_cookie'] = true;
464
465 return true;
466 }
467
468 /**
469 * Set the user and password after last checkings if required
470 *
471 * @return bool always true
472 */
473 public function storeCredentials()
474 {
475 global $cfg;
476
477 if ($GLOBALS['cfg']['AllowArbitraryServer']
478 && ! empty($GLOBALS['pma_auth_server'])
479 ) {
480 /* Allow to specify 'host port' */
481 $parts = explode(' ', $GLOBALS['pma_auth_server']);
482 if (count($parts) === 2) {
483 $tmp_host = $parts[0];
484 $tmp_port = $parts[1];
485 } else {
486 $tmp_host = $GLOBALS['pma_auth_server'];
487 $tmp_port = '';
488 }
489 if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) {
490 $cfg['Server']['host'] = $tmp_host;
491 if (! empty($tmp_port)) {
492 $cfg['Server']['port'] = $tmp_port;
493 }
494 }
495 unset($tmp_host, $tmp_port, $parts);
496 }
497
498 return parent::storeCredentials();
499 }
500
501 /**
502 * Stores user credentials after successful login.
503 *
504 * @return void|bool
505 */
506 public function rememberCredentials()
507 {
508 global $route;
509
510 // Name and password cookies need to be refreshed each time
511 // Duration = one month for username
512 $this->storeUsernameCookie($this->user);
513
514 // Duration = as configured
515 // Do not store password cookie on password change as we will
516 // set the cookie again after password has been changed
517 if (! isset($_POST['change_pw'])) {
518 $this->storePasswordCookie($this->password);
519 }
520
521 // any parameters to pass?
522 $url_params = [];
523 if (isset($route)) {
524 $url_params['route'] = $route;
525 }
526 if (strlen($GLOBALS['db']) > 0) {
527 $url_params['db'] = $GLOBALS['db'];
528 }
529 if (strlen($GLOBALS['table']) > 0) {
530 $url_params['table'] = $GLOBALS['table'];
531 }
532
533 // user logged in successfully after session expiration
534 if (isset($_REQUEST['session_timedout'])) {
535 $response = Response::getInstance();
536 $response->addJSON(
537 'logged_in',
538 1
539 );
540 $response->addJSON(
541 'success',
542 1
543 );
544 $response->addJSON(
545 'new_token',
546 $_SESSION[' PMA_token ']
547 );
548
549 if (! defined('TESTSUITE')) {
550 exit;
551 }
552
553 return false;
554 }
555 // Set server cookies if required (once per session) and, in this case,
556 // force reload to ensure the client accepts cookies
557 if (! $GLOBALS['from_cookie']) {
558
559 /**
560 * Clear user cache.
561 */
562 Util::clearUserCache();
563
564 Response::getInstance()
565 ->disable();
566
567 Core::sendHeaderLocation(
568 './index.php?route=/' . Url::getCommonRaw($url_params, '&'),
569 true
570 );
571 if (! defined('TESTSUITE')) {
572 exit;
573 }
574
575 return false;
576 }
577
578 return true;
579 }
580
581 /**
582 * Stores username in a cookie.
583 *
584 * @param string $username User name
585 *
586 * @return void
587 */
588 public function storeUsernameCookie($username)
589 {
590 // Name and password cookies need to be refreshed each time
591 // Duration = one month for username
592 $GLOBALS['PMA_Config']->setCookie(
593 'pmaUser-' . $GLOBALS['server'],
594 $this->cookieEncrypt(
595 $username,
596 $this->getEncryptionSecret()
597 )
598 );
599 }
600
601 /**
602 * Stores password in a cookie.
603 *
604 * @param string $password Password
605 *
606 * @return void
607 */
608 public function storePasswordCookie($password)
609 {
610 $payload = ['password' => $password];
611 if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) {
612 $payload['server'] = $GLOBALS['pma_auth_server'];
613 }
614 // Duration = as configured
615 $GLOBALS['PMA_Config']->setCookie(
616 'pmaAuth-' . $GLOBALS['server'],
617 $this->cookieEncrypt(
618 json_encode($payload),
619 $this->getSessionEncryptionSecret()
620 ),
621 null,
622 (int) $GLOBALS['cfg']['LoginCookieStore']
623 );
624 }
625
626 /**
627 * User is not allowed to login to MySQL -> authentication failed
628 *
629 * prepares error message and switches to showLoginForm() which display the error
630 * and the login form
631 *
632 * this function MUST exit/quit the application,
633 * currently done by call to showLoginForm()
634 *
635 * @param string $failure String describing why authentication has failed
636 *
637 * @return void
638 */
639 public function showFailure($failure)
640 {
641 global $conn_error;
642
643 parent::showFailure($failure);
644
645 // Deletes password cookie and displays the login form
646 $GLOBALS['PMA_Config']->removeCookie('pmaAuth-' . $GLOBALS['server']);
647
648 $conn_error = $this->getErrorMessage($failure);
649
650 $response = Response::getInstance();
651
652 // needed for PHP-CGI (not need for FastCGI or mod-php)
653 $response->header('Cache-Control: no-store, no-cache, must-revalidate');
654 $response->header('Pragma: no-cache');
655
656 $this->showLoginForm();
657 }
658
659 /**
660 * Returns blowfish secret or generates one if needed.
661 *
662 * @return string
663 */
664 private function getEncryptionSecret()
665 {
666 if (empty($GLOBALS['cfg']['blowfish_secret'])) {
667 return $this->getSessionEncryptionSecret();
668 }
669
670 return $GLOBALS['cfg']['blowfish_secret'];
671 }
672
673 /**
674 * Returns blowfish secret or generates one if needed.
675 *
676 * @return string
677 */
678 private function getSessionEncryptionSecret()
679 {
680 if (empty($_SESSION['encryption_key'])) {
681 if ($this->useOpenSsl) {
682 $_SESSION['encryption_key'] = openssl_random_pseudo_bytes(32);
683 } else {
684 $_SESSION['encryption_key'] = Crypt\Random::string(32);
685 }
686 }
687
688 return $_SESSION['encryption_key'];
689 }
690
691 /**
692 * Concatenates secret in order to make it 16 bytes log
693 *
694 * This doesn't add any security, just ensures the secret
695 * is long enough by copying it.
696 *
697 * @param string $secret Original secret
698 *
699 * @return string
700 */
701 public function enlargeSecret($secret)
702 {
703 while (strlen($secret) < 16) {
704 $secret .= $secret;
705 }
706
707 return substr($secret, 0, 16);
708 }
709
710 /**
711 * Derives MAC secret from encryption secret.
712 *
713 * @param string $secret the secret
714 *
715 * @return string the MAC secret
716 */
717 public function getMACSecret($secret)
718 {
719 // Grab first part, up to 16 chars
720 // The MAC and AES secrets can overlap if original secret is short
721 $length = strlen($secret);
722 if ($length > 16) {
723 return substr($secret, 0, 16);
724 }
725
726 return $this->enlargeSecret(
727 $length == 1 ? $secret : substr($secret, 0, -1)
728 );
729 }
730
731 /**
732 * Derives AES secret from encryption secret.
733 *
734 * @param string $secret the secret
735 *
736 * @return string the AES secret
737 */
738 public function getAESSecret($secret)
739 {
740 // Grab second part, up to 16 chars
741 // The MAC and AES secrets can overlap if original secret is short
742 $length = strlen($secret);
743 if ($length > 16) {
744 return substr($secret, -16);
745 }
746
747 return $this->enlargeSecret(
748 $length == 1 ? $secret : substr($secret, 1)
749 );
750 }
751
752 /**
753 * Cleans any SSL errors
754 *
755 * This can happen from corrupted cookies, by invalid encryption
756 * parameters used in older phpMyAdmin versions or by wrong openSSL
757 * configuration.
758 *
759 * In neither case the error is useful to user, but we need to clear
760 * the error buffer as otherwise the errors would pop up later, for
761 * example during MySQL SSL setup.
762 *
763 * @return void
764 */
765 public function cleanSSLErrors()
766 {
767 if (! function_exists('openssl_error_string')) {
768 return;
769 }
770
771 do {
772 $hasSslErrors = openssl_error_string();
773 } while ($hasSslErrors !== false);
774 }
775
776 /**
777 * Encryption using openssl's AES or phpseclib's AES
778 * (phpseclib uses another extension when it is available)
779 *
780 * @param string $data original data
781 * @param string $secret the secret
782 *
783 * @return string the encrypted result
784 */
785 public function cookieEncrypt($data, $secret)
786 {
787 $mac_secret = $this->getMACSecret($secret);
788 $aes_secret = $this->getAESSecret($secret);
789 $iv = $this->createIV();
790 if ($this->useOpenSsl) {
791 $result = openssl_encrypt(
792 $data,
793 'AES-128-CBC',
794 $aes_secret,
795 0,
796 $iv
797 );
798 } else {
799 $cipher = new Crypt\AES(Crypt\Base::MODE_CBC);
800 $cipher->setIV($iv);
801 $cipher->setKey($aes_secret);
802 $result = base64_encode($cipher->encrypt($data));
803 }
804 $this->cleanSSLErrors();
805 $iv = base64_encode($iv);
806
807 return json_encode(
808 [
809 'iv' => $iv,
810 'mac' => hash_hmac('sha1', $iv . $result, $mac_secret),
811 'payload' => $result,
812 ]
813 );
814 }
815
816 /**
817 * Decryption using openssl's AES or phpseclib's AES
818 * (phpseclib uses another extension when it is available)
819 *
820 * @param string $encdata encrypted data
821 * @param string $secret the secret
822 *
823 * @return string|false original data, false on error
824 */
825 public function cookieDecrypt($encdata, $secret)
826 {
827 $data = json_decode($encdata, true);
828
829 if (! isset($data['mac'], $data['iv'], $data['payload'])
830 || ! is_array($data)
831 || ! is_string($data['mac'])
832 || ! is_string($data['iv'])
833 || ! is_string($data['payload'])
834 ) {
835 return false;
836 }
837
838 $mac_secret = $this->getMACSecret($secret);
839 $aes_secret = $this->getAESSecret($secret);
840 $newmac = hash_hmac('sha1', $data['iv'] . $data['payload'], $mac_secret);
841
842 if (! hash_equals($data['mac'], $newmac)) {
843 return false;
844 }
845
846 if ($this->useOpenSsl) {
847 $result = openssl_decrypt(
848 $data['payload'],
849 'AES-128-CBC',
850 $aes_secret,
851 0,
852 base64_decode($data['iv'])
853 );
854 } else {
855 $cipher = new Crypt\AES(Crypt\Base::MODE_CBC);
856 $cipher->setIV(base64_decode($data['iv']));
857 $cipher->setKey($aes_secret);
858 $result = $cipher->decrypt(base64_decode($data['payload']));
859 }
860 $this->cleanSSLErrors();
861
862 return $result;
863 }
864
865 /**
866 * Returns size of IV for encryption.
867 *
868 * @return int
869 */
870 public function getIVSize()
871 {
872 if ($this->useOpenSsl) {
873 return openssl_cipher_iv_length('AES-128-CBC');
874 }
875
876 return (new Crypt\AES(Crypt\Base::MODE_CBC))->block_size;
877 }
878
879 /**
880 * Initialization
881 * Store the initialization vector because it will be needed for
882 * further decryption. I don't think necessary to have one iv
883 * per server so I don't put the server number in the cookie name.
884 *
885 * @return string
886 */
887 public function createIV()
888 {
889 /* Testsuite shortcut only to allow predictable IV */
890 if ($this->cookieIv !== null) {
891 return $this->cookieIv;
892 }
893 if ($this->useOpenSsl) {
894 return openssl_random_pseudo_bytes(
895 $this->getIVSize()
896 );
897 }
898
899 return Crypt\Random::string(
900 $this->getIVSize()
901 );
902 }
903
904 /**
905 * Sets encryption IV to use
906 *
907 * This is for testing only!
908 *
909 * @param string $vector The IV
910 *
911 * @return void
912 */
913 public function setIV($vector)
914 {
915 $this->cookieIv = $vector;
916 }
917
918 /**
919 * Callback when user changes password.
920 *
921 * @param string $password New password to set
922 *
923 * @return void
924 */
925 public function handlePasswordChange($password)
926 {
927 $this->storePasswordCookie($password);
928 }
929
930 /**
931 * Perform logout
932 *
933 * @return void
934 */
935 public function logOut()
936 {
937 global $PMA_Config;
938
939 // -> delete password cookie(s)
940 if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
941 foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
942 $PMA_Config->removeCookie('pmaAuth-' . $key);
943 if (! $PMA_Config->issetCookie('pmaAuth-' . $key)) {
944 continue;
945 }
946
947 $PMA_Config->removeCookie('pmaAuth-' . $key);
948 }
949 } else {
950 $cookieName = 'pmaAuth-' . $GLOBALS['server'];
951 $PMA_Config->removeCookie($cookieName);
952 if ($PMA_Config->issetCookie($cookieName)) {
953 $PMA_Config->removeCookie($cookieName);
954 }
955 }
956 parent::logOut();
957 }
958 }