Subversion Repositories oidplus

Rev

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