Subversion Repositories oidplus

Rev

Rev 730 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
511 daniel-mar 5
 * Copyright 2019 - 2021 Daniel Marschall, ViaThinkSoft
2 daniel-mar 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
 
511 daniel-mar 20
if (!defined('INSIDE_OIDPLUS')) die();
21
 
730 daniel-mar 22
class OIDplusSessionHandler extends OIDplusBaseClass implements OIDplusGetterSetterInterface {
2 daniel-mar 23
 
263 daniel-mar 24
        private $secret = '';
25
        protected $sessionLifetime = '';
2 daniel-mar 26
 
263 daniel-mar 27
        public function __construct() {
261 daniel-mar 28
                $this->sessionLifetime = OIDplus::baseConfig()->getValue('SESSION_LIFETIME', 30*60);
29
                $this->secret = OIDplus::baseConfig()->getValue('SERVER_SECRET');
30
 
2 daniel-mar 31
                // **PREVENTING SESSION HIJACKING**
32
                // Prevents javascript XSS attacks aimed to steal the session ID
592 daniel-mar 33
                @ini_set('session.cookie_httponly', '1');
2 daniel-mar 34
 
35
                // **PREVENTING SESSION FIXATION**
36
                // Session ID cannot be passed through URLs
592 daniel-mar 37
                @ini_set('session.use_only_cookies', '1');
2 daniel-mar 38
 
592 daniel-mar 39
                @ini_set('session.use_trans_sid', '0');
85 daniel-mar 40
 
2 daniel-mar 41
                // Uses a secure connection (HTTPS) if possible
230 daniel-mar 42
                @ini_set('session.cookie_secure', OIDplus::isSslAvailable());
2 daniel-mar 43
 
801 daniel-mar 44
                $path = OIDplus::webpath(null,OIDplus::PATH_RELATIVE);
555 daniel-mar 45
                if (empty($path)) $path = '/';
46
                @ini_set('session.cookie_path', $path);
2 daniel-mar 47
 
563 daniel-mar 48
                @ini_set('session.cookie_samesite', OIDplus::baseConfig()->getValue('COOKIE_SAMESITE_POLICY', 'Strict'));
2 daniel-mar 49
 
592 daniel-mar 50
                @ini_set('session.use_strict_mode', '1');
2 daniel-mar 51
 
261 daniel-mar 52
                @ini_set('session.gc_maxlifetime', $this->sessionLifetime);
2 daniel-mar 53
        }
54
 
86 daniel-mar 55
        protected function sessionSafeStart() {
179 daniel-mar 56
                if (!isset($_SESSION)) {
57
                        // TODO: session_name() makes some problems. Leave it away for now.
261 daniel-mar 58
                        //session_name('OIDplus_SESHDLR');
179 daniel-mar 59
                        if (!session_start()) {
360 daniel-mar 60
                                throw new OIDplusException(_L('Session could not be started'));
179 daniel-mar 61
                        }
62
                }
86 daniel-mar 63
 
87 daniel-mar 64
                if (!isset($_SESSION['ip'])) {
65
                        if (!isset($_SERVER['REMOTE_ADDR'])) return;
86 daniel-mar 66
 
87 daniel-mar 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!
435 daniel-mar 72
 
73
                                // We don't use $this->destroySession(), because this calls sessionSafeStart() again
74
                                $_SESSION = array();
75
                                session_destroy();
76
                                session_write_close();
557 daniel-mar 77
                                OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
87 daniel-mar 78
                        }
86 daniel-mar 79
                }
80
        }
81
 
2 daniel-mar 82
        function __destruct() {
83
                session_write_close();
84
        }
85
 
424 daniel-mar 86
        private $cacheSetValues = array(); // Important if you do a setValue() followed by an getValue()
87
 
2 daniel-mar 88
        public function setValue($name, $value) {
716 daniel-mar 89
                $enc_data = self::encrypt($value, $this->secret);
426 daniel-mar 90
 
716 daniel-mar 91
                $this->cacheSetValues[$name] = $enc_data;
92
 
86 daniel-mar 93
                $this->sessionSafeStart();
557 daniel-mar 94
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
85 daniel-mar 95
 
716 daniel-mar 96
                $_SESSION[$name] = $enc_data;
2 daniel-mar 97
        }
98
 
569 daniel-mar 99
        public function getValue($name, $default = NULL) {
424 daniel-mar 100
                if (isset($this->cacheSetValues[$name])) return self::decrypt($this->cacheSetValues[$name], $this->secret);
101
 
585 daniel-mar 102
                if (!$this->isActive()) return $default; // GDPR: Only start a session when we really need one
86 daniel-mar 103
                $this->sessionSafeStart();
557 daniel-mar 104
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
85 daniel-mar 105
 
569 daniel-mar 106
                if (!isset($_SESSION[$name])) return $default;
2 daniel-mar 107
                return self::decrypt($_SESSION[$name], $this->secret);
108
        }
109
 
569 daniel-mar 110
        public function exists($name) {
111
                if (isset($this->cacheSetValues[$name])) return true;
112
 
585 daniel-mar 113
                if (!$this->isActive()) return false; // GDPR: Only start a session when we really need one
569 daniel-mar 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
 
585 daniel-mar 123
                if (!$this->isActive()) return; // GDPR: Only start a session when we really need one
569 daniel-mar 124
                $this->sessionSafeStart();
125
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
126
 
127
                unset($_SESSION[$name]);
128
        }
129
 
85 daniel-mar 130
        public function destroySession() {
585 daniel-mar 131
                if (!$this->isActive()) return;
85 daniel-mar 132
 
86 daniel-mar 133
                $this->sessionSafeStart();
557 daniel-mar 134
                OIDplus::cookieUtils()->setcookie(session_name(),session_id(),time()+$this->sessionLifetime);
85 daniel-mar 135
 
136
                $_SESSION = array();
137
                session_destroy();
138
                session_write_close();
557 daniel-mar 139
                OIDplus::cookieUtils()->unsetcookie(session_name()); // remove cookie, so GDPR people are happy
85 daniel-mar 140
        }
141
 
585 daniel-mar 142
        public function isActive() {
143
                return isset($_COOKIE[session_name()]);
144
        }
145
 
2 daniel-mar 146
        protected static function encrypt($data, $key) {
465 daniel-mar 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',
711 daniel-mar 153
                                hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, true),
465 daniel-mar 154
                                OPENSSL_RAW_DATA,
155
                                $iv
156
                        );
157
                        // Authentication
711 daniel-mar 158
                        $hmac = sha3_512_hmac($iv . $ciphertext, $key, true);
465 daniel-mar 159
                        return $hmac . $iv . $ciphertext;
160
                } else {
161
                        // When OpenSSL is not available, then we just do a HMAC
711 daniel-mar 162
                        $hmac = sha3_512_hmac($data, $key, true);
465 daniel-mar 163
                        return $hmac . $data;
164
                }
6 daniel-mar 165
        }
2 daniel-mar 166
 
6 daniel-mar 167
        protected static function decrypt($data, $key) {
465 daniel-mar 168
                if (function_exists('openssl_decrypt')) {
711 daniel-mar 169
                        $hmac       = mb_substr($data, 0, 64, '8bit');
170
                        $iv         = mb_substr($data, 64, 16, '8bit');
171
                        $ciphertext = mb_substr($data, 80, null, '8bit');
465 daniel-mar 172
                        // Authentication
711 daniel-mar 173
                        $hmacNew = sha3_512_hmac($iv . $ciphertext, $key, true);
465 daniel-mar 174
                        if (!hash_equals($hmac, $hmacNew)) {
175
                                throw new OIDplusException(_L('Authentication failed'));
176
                        }
177
                        // Decryption
711 daniel-mar 178
                        $cleartext = openssl_decrypt(
465 daniel-mar 179
                                $ciphertext,
180
                                'AES-256-CBC',
711 daniel-mar 181
                                hash_pbkdf2('sha512', $key, '', 10000, 64/*256bit*/, true),
465 daniel-mar 182
                                OPENSSL_RAW_DATA,
183
                                $iv
184
                        );
711 daniel-mar 185
                        if ($cleartext === false) {
186
                                throw new OIDplusException(_L('Decryption failed'));
187
                        }
188
                        return $cleartext;
465 daniel-mar 189
                } else {
190
                        // When OpenSSL is not available, then we just do a HMAC
711 daniel-mar 191
                        $hmac       = mb_substr($data, 0, 64, '8bit');
192
                        $cleartext  = mb_substr($data, 64, null, '8bit');
193
                        $hmacNew    = sha3_512_hmac($cleartext, $key, true);
465 daniel-mar 194
                        if (!hash_equals($hmac, $hmacNew)) {
195
                                throw new OIDplusException(_L('Authentication failed'));
196
                        }
197
                        return $cleartext;
6 daniel-mar 198
                }
199
        }
424 daniel-mar 200
}