Subversion Repositories oidplus

Rev

Rev 847 | Go to most recent revision | Blame | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * OIDplus 2.0
  5.  * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
  6.  *
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *     http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  */
  19.  
  20. namespace ViaThinkSoft\OIDplus;
  21.  
  22. class OIDplusSessionHandler extends OIDplusBaseClass implements OIDplusGetterSetterInterface {
  23.  
  24.         private $secret = '';
  25.         protected $sessionLifetime = 0;
  26.  
  27.         public function __construct() {
  28.                 $this->sessionLifetime = OIDplus::baseConfig()->getValue('SESSION_LIFETIME', 30*60);
  29.                 $this->secret = OIDplus::baseConfig()->getValue('SERVER_SECRET');
  30.  
  31.                 // **PREVENTING SESSION HIJACKING**
  32.                 // Prevents javascript XSS attacks aimed to steal the session ID
  33.                 @ini_set('session.cookie_httponly', '1');
  34.  
  35.                 // **PREVENTING SESSION FIXATION**
  36.                 // Session ID cannot be passed through URLs
  37.                 @ini_set('session.use_only_cookies', '1');
  38.  
  39.                 @ini_set('session.use_trans_sid', '0');
  40.  
  41.                 // Uses a secure connection (HTTPS) if possible
  42.                 @ini_set('session.cookie_secure', OIDplus::isSslAvailable());
  43.  
  44.                 $path = OIDplus::webpath(null,OIDplus::PATH_RELATIVE);
  45.                 if (empty($path)) $path = '/';
  46.                 @ini_set('session.cookie_path', $path);
  47.  
  48.                 @ini_set('session.cookie_samesite', OIDplus::baseConfig()->getValue('COOKIE_SAMESITE_POLICY', 'Strict'));
  49.  
  50.                 @ini_set('session.use_strict_mode', '1');
  51.  
  52.                 @ini_set('session.gc_maxlifetime', $this->sessionLifetime);
  53.         }
  54.  
  55.         protected function sessionSafeStart() {
  56.                 if (!isset($_SESSION)) {
  57.                         // TODO: session_name() makes some problems. Leave it away for now.
  58.                         //session_name('OIDplus_SESHDLR');
  59.                         if (!session_start()) {
  60.                                 throw new OIDplusException(_L('Session could not be started'));
  61.                         }
  62.                 }
  63.  
  64.                 if (!isset($_SESSION['ip'])) {
  65.                         if (!isset($_SERVER['REMOTE_ADDR'])) return;
  66.  
  67.                         // Remember the IP address of the user
  68.                         $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
  69.                 } else {
  70.                         if ($_SERVER['REMOTE_ADDR'] != $_SESSION['ip']) {
  71.                                 // Was the session hijacked?! Get out of here!
  72.  
  73.                                 // We don't use $this->destroySession(), because this calls sessionSafeStart() again
  74.                                 $_SESSION = array();
  75.                                 session_destroy();
  76.                                 session_write_close();
  77.                                 OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
  78.                         }
  79.                 }
  80.         }
  81.  
  82.         function __destruct() {
  83.                 session_write_close();
  84.         }
  85.  
  86.         private $cacheSetValues = array(); // Important if you do a setValue() followed by an getValue()
  87.  
  88.         public function setValue($name, $value) {
  89.                 $enc_data = self::encrypt($value, $this->secret);
  90.  
  91.                 $this->cacheSetValues[$name] = $enc_data;
  92.  
  93.                 $this->sessionSafeStart();
  94.                 OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
  95.  
  96.                 $_SESSION[$name] = $enc_data;
  97.         }
  98.  
  99.         public function getValue($name, $default = NULL) {
  100.                 if (isset($this->cacheSetValues[$name])) return self::decrypt($this->cacheSetValues[$name], $this->secret);
  101.  
  102.                 if (!$this->isActive()) return $default; // GDPR: Only start a session when we really need one
  103.                 $this->sessionSafeStart();
  104.                 OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
  105.  
  106.                 if (!isset($_SESSION[$name])) return $default;
  107.                 return self::decrypt($_SESSION[$name], $this->secret);
  108.         }
  109.  
  110.         public function exists($name) {
  111.                 if (isset($this->cacheSetValues[$name])) return true;
  112.  
  113.                 if (!$this->isActive()) return false; // GDPR: Only start a session when we really need one
  114.                 $this->sessionSafeStart();
  115.                 OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
  116.  
  117.                 if (!isset($_SESSION[$name])) return false;
  118.         }
  119.  
  120.         public function delete($name) {
  121.                 if (isset($this->cacheSetValues[$name])) unset($this->cacheSetValues[$name]);
  122.  
  123.                 if (!$this->isActive()) return; // GDPR: Only start a session when we really need one
  124.                 $this->sessionSafeStart();
  125.                 OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
  126.  
  127.                 unset($_SESSION[$name]);
  128.         }
  129.  
  130.         public function destroySession() {
  131.                 if (!$this->isActive()) return;
  132.  
  133.                 $this->sessionSafeStart();
  134.                 OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
  135.  
  136.                 $_SESSION = array();
  137.                 session_destroy();
  138.                 session_write_close();
  139.                 OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
  140.         }
  141.  
  142.         public function isActive() {
  143.                 return isset($_COOKIE[session_name()]);
  144.         }
  145.  
  146.         protected static function encrypt($data, $key) {
  147.                 if (function_exists('openssl_encrypt')) {
  148.                         $iv = random_bytes(16); // AES block size in CBC mode
  149.                         // Encryption
  150.                         $ciphertext = openssl_encrypt(
  151.                                 $data,
  152.                                 'AES-256-CBC',
  153.                                 hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
  154.                                 OPENSSL_RAW_DATA,
  155.                                 $iv
  156.                         );
  157.                         // Authentication
  158.                         $hmac = sha3_512_hmac($iv . $ciphertext, $key, true);
  159.                         return $hmac . $iv . $ciphertext;
  160.                 } else {
  161.                         // When OpenSSL is not available, then we just do a HMAC
  162.                         $hmac = sha3_512_hmac($data, $key, true);
  163.                         return $hmac . $data;
  164.                 }
  165.         }
  166.  
  167.         protected static function decrypt($data, $key) {
  168.                 if (function_exists('openssl_decrypt')) {
  169.                         $hmac       = mb_substr($data, 0, 64, '8bit');
  170.                         $iv         = mb_substr($data, 64, 16, '8bit');
  171.                         $ciphertext = mb_substr($data, 80, null, '8bit');
  172.                         // Authentication
  173.                         $hmacNew = sha3_512_hmac($iv . $ciphertext, $key, true);
  174.                         if (!hash_equals($hmac, $hmacNew)) {
  175.                                 throw new OIDplusException(_L('Authentication failed'));
  176.                         }
  177.                         // Decryption
  178.                         $cleartext = openssl_decrypt(
  179.                                 $ciphertext,
  180.                                 'AES-256-CBC',
  181.                                 hash_pbkdf2('sha512', $key, '', 10000, 32/*256bit*/, true),
  182.                                 OPENSSL_RAW_DATA,
  183.                                 $iv
  184.                         );
  185.                         if ($cleartext === false) {
  186.                                 throw new OIDplusException(_L('Decryption failed'));
  187.                         }
  188.                         return $cleartext;
  189.                 } else {
  190.                         // When OpenSSL is not available, then we just do a HMAC
  191.                         $hmac       = mb_substr($data, 0, 64, '8bit');
  192.                         $cleartext  = mb_substr($data, 64, null, '8bit');
  193.                         $hmacNew    = sha3_512_hmac($cleartext, $key, true);
  194.                         if (!hash_equals($hmac, $hmacNew)) {
  195.                                 throw new OIDplusException(_L('Authentication failed'));
  196.                         }
  197.                         return $cleartext;
  198.                 }
  199.         }
  200. }
  201.