Passed
Branch development (e0e718)
by Nils
04:45
created

SecureHandler::open()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 15 and the first side effect is on line 197.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Encrypt PHP session data for the internal PHP save handlers
4
 *
5
 * The encryption is built using OpenSSL extension with AES-256-CBC and the
6
 * authentication is provided using HMAC with SHA256.
7
 *
8
 * @author    Enrico Zimuel ([email protected])
9
 * @copyright MIT License
10
 */
11
namespace PHPSecureSession;
12
13
use SessionHandler;
14
15
class SecureHandler extends SessionHandler
16
{
17
    /**
18
     * Encryption and authentication key
19
     * @var string
20
     */
21
    protected $key;
22
23
    /**
24
     * Constructor
25
     */
26
    public function __construct()
27
    {
28
        if (!extension_loaded('openssl')) {
29
            throw new \RuntimeException(sprintf(
30
                "You need the OpenSSL extension to use %s",
31
                __CLASS__
32
            ));
33
        }
34
        if (!extension_loaded('mbstring')) {
35
            throw new \RuntimeException(sprintf(
36
                "You need the Multibytes extension to use %s",
37
                __CLASS__
38
            ));
39
        }
40
    }
41
42
    /**
43
     * Open the session
44
     *
45
     * @param string $save_path
46
     * @param string $session_name
47
     * @return bool
48
     */
49
    public function open($save_path, $session_name)
50
    {
51
        $this->key = $this->getKey('KEY_'.$session_name);
52
        return parent::open($save_path, $session_name);
53
    }
54
55
    /**
56
     * Read from session and decrypt
57
     *
58
     * @param string $session_id
59
     */
60
    public function read($session_id)
61
    {
62
        $data = parent::read($session_id);
63
        return empty($data) ? '' : $this->decrypt($data, $this->key);
64
    }
65
66
    /**
67
     * Encrypt the data and write into the session
68
     *
69
     * @param string $session_id
70
     * @param string $data
71
     */
72
    public function write($session_id, $data)
73
    {
74
        return parent::write($session_id, $this->encrypt($data, $this->key));
75
    }
76
77
    /**
78
     * Encrypt and authenticate
79
     *
80
     * @param string $data
81
     * @param string $key
82
     * @return string
83
     */
84
    protected function encrypt($data, $key)
85
    {
86
        $block_iv = random_bytes(16); // AES block size in CBC mode
87
        // Encryption
88
        $ciphertext = openssl_encrypt(
89
            $data,
90
            'AES-256-CBC',
91
            mb_substr($key, 0, 32, '8bit'),
92
            OPENSSL_RAW_DATA,
93
            $block_iv
94
        );
95
        // Authentication
96
        $hmac = hash_hmac(
97
            'SHA256',
98
            $block_iv.$ciphertext,
99
            mb_substr($key, 32, null, '8bit'),
100
            true
101
        );
102
        return $hmac.$block_iv.$ciphertext;
103
    }
104
105
    /**
106
     * Authenticate and decrypt
107
     *
108
     * @param string $data
109
     * @param string $key
110
     * @return string
111
     */
112
    protected function decrypt($data, $key)
113
    {
114
        $hmac       = mb_substr($data, 0, 32, '8bit');
115
        $block_iv   = mb_substr($data, 32, 16, '8bit');
116
        $ciphertext = mb_substr($data, 48, null, '8bit');
117
        // Authentication
118
        $hmacNew = hash_hmac(
119
            'SHA256',
120
            $block_iv.$ciphertext,
121
            mb_substr($key, 32, null, '8bit'),
122
            true
123
        );
124
        if (!$this->hash_equals($hmac, $hmacNew)) {
125
            throw new \RuntimeException('Authentication failed');
126
        }
127
        // Decrypt
128
        return openssl_decrypt(
129
            $ciphertext,
130
            'AES-256-CBC',
131
            mb_substr($key, 0, 32, '8bit'),
132
            OPENSSL_RAW_DATA,
133
            $block_iv
134
        );
135
    }
136
137
    /**
138
     * Get the encryption and authentication keys from cookie
139
     *
140
     * @param string $name
141
     * @return string
142
     */
143
    protected function getKey($name)
144
    {
145
        if (empty($_COOKIE[$name]) === true) {
146
            // 32 for encryption and 32 for authentication
0 ignored issues
show
Unused Code Comprehensibility introduced by
36% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
147
            $key = random_bytes(64);
148
            $cookieParam = session_get_cookie_params();
149
            setcookie(
150
                $name,
151
                base64_encode($key),
152
                // if session cookie lifetime > 0 then add to current time
153
                // otherwise leave it as zero, honoring zero's special meaning
154
                // expire at browser close.
155
                ($cookieParam['lifetime'] > 0) ? time() + $cookieParam['lifetime'] : 0,
156
                $cookieParam['path'],
157
                $cookieParam['domain'],
158
                $cookieParam['secure'],
159
                $cookieParam['httponly']
160
            );
161
            return $key;
162
        }
163
164
        // If not returned before - cookie exists
165
        return base64_decode($_COOKIE[$name]);
166
    }
167
168
    /**
169
     * Hash equals function for PHP 5.5+
170
     *
171
     * @param string $expected
172
     * @param string $actual
173
     * @return bool
174
     */
175
    protected function hash_equals($expected, $actual)
176
    {
177
        $expected     = filter_var($expected, FILTER_SANITIZE_STRING);
178
        $actual       = filter_var($actual, FILTER_SANITIZE_STRING);
179
        if (function_exists('hash_equals')) {
180
            return hash_equals($expected, $actual);
181
        }
182
        $lenExpected  = mb_strlen($expected, '8bit');
183
        $lenActual    = mb_strlen($actual, '8bit');
184
        $len          = min($lenExpected, $lenActual);
185
        $result = 0;
186
        for ($i = 0; $i < $len; $i++) {
187
            $result |= ord($expected[$i]) ^ ord($actual[$i]);
188
        }
189
        $result |= $lenExpected ^ $lenActual;
190
        return ($result === 0);
191
    }
192
}
193
194
// random_bytes is a PHP7 new function required by SecureHandler.
195
// random_compat has been written to define it in PHP5
196
if (file_exists('./includes/libraries/misc/random_compat/random.php')) {
197
    require_once('./includes/libraries/misc/random_compat/random.php');
198
} else {
199
    if (file_exists('../includes/libraries/misc/random_compat/random.php')) {
200
        require_once('../includes/libraries/misc/random_compat/random.php');
201
    } else {
202
        require_once('../../includes/libraries/misc/random_compat/random.php');
203
    }
204
}
205
206
// load
207
session_set_save_handler(
208
    new SecureHandler(),
209
    true
210
);
211