Subversion Repositories oidplus

Rev

Rev 179 | 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
5
 * Copyright 2019 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
 
112 daniel-mar 20
if (!defined('IN_OIDPLUS')) die();
21
 
179 daniel-mar 22
define('SESSION_LIFETIME', 30*60); // TODO: Configure. Current default: 30 minutes
85 daniel-mar 23
 
2 daniel-mar 24
class OIDplusSessionHandler {
25
 
26
        protected $secret = '';
27
 
28
        function __construct($secret) {
29
                // **PREVENTING SESSION HIJACKING**
30
                // Prevents javascript XSS attacks aimed to steal the session ID
87 daniel-mar 31
                @ini_set('session.cookie_httponly', 1);
2 daniel-mar 32
 
33
                // **PREVENTING SESSION FIXATION**
34
                // Session ID cannot be passed through URLs
87 daniel-mar 35
                @ini_set('session.use_only_cookies', 1);
2 daniel-mar 36
 
87 daniel-mar 37
                @ini_set('session.use_trans_sid', 0);
85 daniel-mar 38
 
2 daniel-mar 39
                // Uses a secure connection (HTTPS) if possible
87 daniel-mar 40
                @ini_set('session.cookie_secure', OIDPLUS_SSL_AVAILABLE);
2 daniel-mar 41
 
227 daniel-mar 42
                $path = OIDplus::getSystemUrl(true);
83 daniel-mar 43
                if (!empty($path)) {
87 daniel-mar 44
                        @ini_set('session.cookie_path', $path);
2 daniel-mar 45
                }
46
 
87 daniel-mar 47
                @ini_set('session.cookie_samesite', 'Lax');
2 daniel-mar 48
 
87 daniel-mar 49
                @ini_set('session.use_strict_mode', 1);
2 daniel-mar 50
 
87 daniel-mar 51
                @ini_set('session.gc_maxlifetime', SESSION_LIFETIME);
85 daniel-mar 52
 
2 daniel-mar 53
                $this->secret = $secret;
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.
59
                        //session_name('OIDPLUS_SESHDLR');
60
                        if (!session_start()) {
61
                                throw new Exception("Session could not be started");
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!
73
                                $this->destroySession();
74
                        }
86 daniel-mar 75
                }
76
        }
77
 
2 daniel-mar 78
        function __destruct() {
79
                session_write_close();
80
        }
81
 
82
        public function setValue($name, $value) {
86 daniel-mar 83
                $this->sessionSafeStart();
85 daniel-mar 84
                setcookie(session_name(),session_id(),time()+SESSION_LIFETIME, ini_get('session.cookie_path'));
85
 
2 daniel-mar 86
                $_SESSION[$name] = self::encrypt($value, $this->secret);
87
        }
88
 
89
        public function getValue($name) {
85 daniel-mar 90
                if (!isset($_COOKIE[session_name()])) return null; // GDPR: Only start a session when we really need one
91
 
86 daniel-mar 92
                $this->sessionSafeStart();
85 daniel-mar 93
                setcookie(session_name(),session_id(),time()+SESSION_LIFETIME, ini_get('session.cookie_path'));
94
 
2 daniel-mar 95
                if (!isset($_SESSION[$name])) return null;
96
                return self::decrypt($_SESSION[$name], $this->secret);
97
        }
98
 
85 daniel-mar 99
        public function destroySession() {
100
                if (!isset($_COOKIE[session_name()])) return;
101
 
86 daniel-mar 102
                $this->sessionSafeStart();
85 daniel-mar 103
                setcookie(session_name(),session_id(),time()+SESSION_LIFETIME, ini_get('session.cookie_path'));
104
 
105
                $_SESSION = array();
106
                session_destroy();
107
                session_write_close();
108
                setcookie(session_name(), "", time()-3600, ini_get('session.cookie_path')); // remove cookie, so GDPR people are happy
109
        }
110
 
83 daniel-mar 111
        public function exists($name) {
112
                return isset($_SESSION[$name]);
113
        }
114
 
2 daniel-mar 115
        protected static function encrypt($data, $key) {
6 daniel-mar 116
                $iv = random_bytes(16); // AES block size in CBC mode
117
                // Encryption
118
                $ciphertext = openssl_encrypt(
119
                        $data,
120
                        'AES-256-CBC',
121
                        mb_substr($key, 0, 32, '8bit'),
122
                        OPENSSL_RAW_DATA,
123
                        $iv
124
                );
125
                // Authentication
126
                $hmac = hash_hmac(
127
                        'SHA256',
128
                        $iv . $ciphertext,
129
                        mb_substr($key, 32, null, '8bit'),
130
                        true
131
                );
132
                return $hmac . $iv . $ciphertext;
133
        }
2 daniel-mar 134
 
6 daniel-mar 135
        protected static function decrypt($data, $key) {
136
                $hmac       = mb_substr($data, 0, 32, '8bit');
137
                $iv         = mb_substr($data, 32, 16, '8bit');
138
                $ciphertext = mb_substr($data, 48, null, '8bit');
139
                // Authentication
140
                $hmacNew = hash_hmac(
141
                        'SHA256',
142
                        $iv . $ciphertext,
143
                        mb_substr($key, 32, null, '8bit'),
144
                        true
145
                );
146
                if (!hash_equals($hmac, $hmacNew)) {
147
                        throw new Exception('Authentication failed');
148
                }
149
                // Decryption
150
                return openssl_decrypt(
151
                        $ciphertext,
152
                        'AES-256-CBC',
153
                        mb_substr($key, 0, 32, '8bit'),
154
                        OPENSSL_RAW_DATA,
155
                        $iv
156
                );
157
        }
2 daniel-mar 158
}