Subversion Repositories oidplus

Rev

Rev 496 | 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. if (!defined('INSIDE_OIDPLUS')) die();
  21.  
  22. class OIDplusSessionHandler {
  23.  
  24.         private $secret = '';
  25.         protected $sessionLifetime = '';
  26.         public $simulate = false;
  27.  
  28.         public function __construct() {
  29.                 $this->sessionLifetime = OIDplus::baseConfig()->getValue('SESSION_LIFETIME', 30*60);
  30.                 $this->secret = OIDplus::baseConfig()->getValue('SERVER_SECRET');
  31.  
  32.                 // **PREVENTING SESSION HIJACKING**
  33.                 // Prevents javascript XSS attacks aimed to steal the session ID
  34.                 @ini_set('session.cookie_httponly', 1);
  35.  
  36.                 // **PREVENTING SESSION FIXATION**
  37.                 // Session ID cannot be passed through URLs
  38.                 @ini_set('session.use_only_cookies', 1);
  39.  
  40.                 @ini_set('session.use_trans_sid', 0);
  41.  
  42.                 // Uses a secure connection (HTTPS) if possible
  43.                 @ini_set('session.cookie_secure', OIDplus::isSslAvailable());
  44.  
  45.                 $path = OIDplus::webpath(null,true);
  46.                 if (!empty($path)) {
  47.                         @ini_set('session.cookie_path', $path);
  48.                 }
  49.  
  50.                 @ini_set('session.cookie_samesite', 'Lax');
  51.  
  52.                 @ini_set('session.use_strict_mode', 1);
  53.  
  54.                 @ini_set('session.gc_maxlifetime', $this->sessionLifetime);
  55.         }
  56.  
  57.         protected function sessionSafeStart() {
  58.                 if (!isset($_SESSION)) {
  59.                         // TODO: session_name() makes some problems. Leave it away for now.
  60.                         //session_name('OIDplus_SESHDLR');
  61.                         if (!session_start()) {
  62.                                 throw new OIDplusException(_L('Session could not be started'));
  63.                         }
  64.                 }
  65.  
  66.                 if (!isset($_SESSION['ip'])) {
  67.                         if (!isset($_SERVER['REMOTE_ADDR'])) return;
  68.  
  69.                         // Remember the IP address of the user
  70.                         $_SESSION['ip'] = $_SERVER['REMOTE_ADDR'];
  71.                 } else {
  72.                         if ($_SERVER['REMOTE_ADDR'] != $_SESSION['ip']) {
  73.                                 // Was the session hijacked?! Get out of here!
  74.  
  75.                                 // We don't use $this->destroySession(), because this calls sessionSafeStart() again
  76.                                 $_SESSION = array();
  77.                                 session_destroy();
  78.                                 session_write_close();
  79.                                 setcookie(session_name(), "", time()-3600, ini_get('session.cookie_path')); // remove cookie, so GDPR people are happy
  80.                         }
  81.                 }
  82.         }
  83.  
  84.         function __destruct() {
  85.                 session_write_close();
  86.         }
  87.  
  88.         private $cacheSetValues = array(); // Important if you do a setValue() followed by an getValue()
  89.  
  90.         public function setValue($name, $value) {
  91.                 $this->cacheSetValues[$name] = self::encrypt($value, $this->secret);
  92.                 if ($this->simulate) return;
  93.  
  94.                 $this->sessionSafeStart();
  95.                 setcookie(session_name(),session_id(),time()+$this->sessionLifetime, ini_get('session.cookie_path'));
  96.  
  97.                 $_SESSION[$name] = self::encrypt($value, $this->secret);
  98.         }
  99.  
  100.         public function getValue($name) {
  101.                 if (isset($this->cacheSetValues[$name])) return self::decrypt($this->cacheSetValues[$name], $this->secret);
  102.                 if ($this->simulate) return null;
  103.  
  104.                 if (!isset($_COOKIE[session_name()])) return null; // GDPR: Only start a session when we really need one
  105.                 $this->sessionSafeStart();
  106.                 setcookie(session_name(),session_id(),time()+$this->sessionLifetime, ini_get('session.cookie_path'));
  107.  
  108.                 if (!isset($_SESSION[$name])) return null;
  109.                 return self::decrypt($_SESSION[$name], $this->secret);
  110.         }
  111.  
  112.         public function destroySession() {
  113.                 if (!isset($_COOKIE[session_name()])) return;
  114.  
  115.                 $this->sessionSafeStart();
  116.                 setcookie(session_name(),session_id(),time()+$this->sessionLifetime, ini_get('session.cookie_path'));
  117.  
  118.                 $_SESSION = array();
  119.                 session_destroy();
  120.                 session_write_close();
  121.                 setcookie(session_name(), "", time()-3600, ini_get('session.cookie_path')); // remove cookie, so GDPR people are happy
  122.         }
  123.  
  124.         public function exists($name) {
  125.                 return isset($_SESSION[$name]);
  126.         }
  127.  
  128.         protected static function encrypt($data, $key) {
  129.                 if (function_exists('openssl_encrypt')) {
  130.                         $iv = random_bytes(16); // AES block size in CBC mode
  131.                         // Encryption
  132.                         $ciphertext = openssl_encrypt(
  133.                                 $data,
  134.                                 'AES-256-CBC',
  135.                                 mb_substr($key, 0, 32, '8bit'),
  136.                                 OPENSSL_RAW_DATA,
  137.                                 $iv
  138.                         );
  139.                         // Authentication
  140.                         $hmac = hash_hmac(
  141.                                 'SHA256',
  142.                                 $iv . $ciphertext,
  143.                                 mb_substr($key, 32, null, '8bit'),
  144.                                 true
  145.                         );
  146.                         return $hmac . $iv . $ciphertext;
  147.                 } else {
  148.                         // When OpenSSL is not available, then we just do a HMAC
  149.                         $hmac = hash_hmac(
  150.                                 'SHA256',
  151.                                 $data,
  152.                                 mb_substr($key, 32, null, '8bit'),
  153.                                 true
  154.                         );
  155.                         return $hmac . $data;
  156.                 }
  157.         }
  158.  
  159.         protected static function decrypt($data, $key) {
  160.                 if (function_exists('openssl_decrypt')) {
  161.                         $hmac       = mb_substr($data, 0, 32, '8bit');
  162.                         $iv         = mb_substr($data, 32, 16, '8bit');
  163.                         $ciphertext = mb_substr($data, 48, null, '8bit');
  164.                         // Authentication
  165.                         $hmacNew = hash_hmac(
  166.                                 'SHA256',
  167.                                 $iv . $ciphertext,
  168.                                 mb_substr($key, 32, null, '8bit'),
  169.                                 true
  170.                         );
  171.                         if (!hash_equals($hmac, $hmacNew)) {
  172.                                 throw new OIDplusException(_L('Authentication failed'));
  173.                         }
  174.                         // Decryption
  175.                         return openssl_decrypt(
  176.                                 $ciphertext,
  177.                                 'AES-256-CBC',
  178.                                 mb_substr($key, 0, 32, '8bit'),
  179.                                 OPENSSL_RAW_DATA,
  180.                                 $iv
  181.                         );
  182.                 } else {
  183.                         // When OpenSSL is not available, then we just do a HMAC
  184.                         $hmac       = mb_substr($data, 0, 32, '8bit');
  185.                         $cleartext  = mb_substr($data, 32, null, '8bit');
  186.                         $hmacNew = hash_hmac(
  187.                                 'SHA256',
  188.                                 $cleartext,
  189.                                 mb_substr($key, 32, null, '8bit'),
  190.                                 true
  191.                         );
  192.                         if (!hash_equals($hmac, $hmacNew)) {
  193.                                 throw new OIDplusException(_L('Authentication failed'));
  194.                         }
  195.                         return $cleartext;
  196.                 }
  197.         }
  198. }
  199.