1 <?php
2
3 namespace Alo;
4
5 use Alo\Session\AbstractSession;
6
7 if (!defined('GEN_START')) {
8 http_response_code(404);
9 } else {
10
11 /**
12 * Handles hashing, tokens, randomising and other security operations
13 *
14 * @author Art <a.molcanovas@gmail.com>
15 */
16 abstract class Security {
17
18 /**
19 * Default hash algorithm to use
20 * @var string
21 */
22 const DEFAULT_HASH = 'sha256';
23
24 /**
25 * Defines the ascii charset subset as "the entire set"
26 *
27 * @var int
28 */
29 const ASCII_ALL = 0;
30
31 /**
32 * Defines the ascii charset subset as "only alphanumeric"
33 *
34 * @var int
35 */
36 const ASCII_ALPHANUM = 1;
37
38 /**
39 * Defines the ascii charset subset as "only non-alphanumeric"
40 *
41 * @var int
42 */
43 const ASCII_NONALPHANUM = 2;
44
45 /**
46 * Array of ASCII alphanumeric characters
47 *
48 * @var array
49 */
50 protected static $asciiAlphanum = ['a',
51 'b',
52 'c',
53 'd',
54 'e',
55 'f',
56 'g',
57 'h',
58 'i',
59 'j',
60 'k',
61 'l',
62 'm',
63 'n',
64 'o',
65 'p',
66 'q',
67 'r',
68 's',
69 't',
70 'u',
71 'v',
72 'w',
73 'x',
74 'y',
75 'z',
76 'A',
77 'B',
78 'C',
79 'D',
80 'E',
81 'F',
82 'G',
83 'H',
84 'I',
85 'J',
86 'K',
87 'L',
88 'M',
89 'N',
90 'O',
91 'P',
92 'Q',
93 'R',
94 'S',
95 'T',
96 'U',
97 'V',
98 'W',
99 'X',
100 'Y',
101 'Z',
102 0,
103 1,
104 2,
105 3,
106 4,
107 5,
108 6,
109 7,
110 8,
111 9];
112 /**
113 * The rest of the ASCII charset
114 *
115 * @var array
116 */
117 protected static $asciiRest = [' ',
118 '!',
119 '"',
120 '#',
121 '$',
122 '%',
123 '\'',
124 '(',
125 ')',
126 '*',
127 '+',
128 ',',
129 '.',
130 '/',
131 ':',
132 ';',
133 '<',
134 '=',
135 '>',
136 '?',
137 '@',
138 '[',
139 '\\',
140 ']',
141 '^',
142 '_',
143 '`',
144 '-',
145 '{',
146 '|',
147 '}',
148 '~'];
149
150 /**
151 * Escapes a string or array (recursively) from XSS attacks
152 *
153 * @author Art <a.molcanovas@gmail.com>
154 *
155 * @param string|array $item The item to be escaped
156 *
157 * @return string|array
158 */
159 static function unXss($item) {
160 if (is_array($item)) {
161 foreach ($item as &$v) {
162 $v = self::unXss($item);
163 }
164
165 return $item;
166 } else {
167 return is_scalar($item) ? htmlspecialchars($item, ENT_QUOTES | ENT_HTML5, 'UTF-8', false) : null;
168 }
169 }
170
171 /**
172 * Generates a token and sets it in session
173 *
174 * @author Art <a.molcanovas@gmail.com>
175 *
176 * @param string $tokenName The token name
177 * @param string $hash Which hash algorithm to use
178 *
179 * @return string The generated token
180 */
181 static function tokenGet($tokenName, $hash = self::DEFAULT_HASH) {
182 $token = self::getUniqid($hash, 'token_' . $tokenName);
183
184 if (!AbstractSession::isActive()) {
185 phpWarning('A session is not currently active - tokens are unusable.');
186 } else {
187 $_SESSION[$tokenName] = $token;
188 }
189
190 return $token;
191 }
192
193 /**
194 * Generates a unique identifier
195 *
196 * @author Art <a.molcanovas@gmail.com>
197 *
198 * @param string $hash Hash algorithm
199 * @param string|int $prefix Prefix for the identifier
200 * @param int $entropy Number of pseudo bytes used in entropy
201 *
202 * @return string
203 */
204 static function getUniqid($hash = self::DEFAULT_HASH, $prefix = null, $entropy = 200) {
205 $str = mt_rand(PHP_INT_MIN, PHP_INT_MAX) .
206 json_encode([$_COOKIE,
207 $_REQUEST,
208 $_FILES,
209 $_ENV,
210 $_GET,
211 $_POST,
212 $_SERVER],
213 true) .
214 uniqid() .
215 $prefix .
216 self::asciiRand($entropy);
217
218 if (function_exists('\openssl_random_pseudo_bytes')) {
219 $str .= \openssl_random_pseudo_bytes($entropy);
220 } else {
221 phpWarning('The openssl extension is not enabled, therefore the unique ID is not cryptographically secure.');
222 }
223
224 return hash($hash, $str);
225 }
226
227 /**
228 * Generates a string of random ASCII characters
229 *
230 * @author Art <a.molcanovas@gmail.com>
231 *
232 * @param int $length The length of the string
233 * @param int $subset Which subset to use - see class' ASCII_* constants
234 *
235 * @return string
236 */
237 static function asciiRand($length, $subset = self::ASCII_ALL) {
238 switch ($subset) {
239 case self::ASCII_ALPHANUM:
240 $subset = self::$asciiAlphanum;
241 break;
242 case self::ASCII_NONALPHANUM:
243 $subset = self::$asciiRest;
244 break;
245 default:
246 $subset = array_merge(self::$asciiAlphanum, self::$asciiRest);
247 }
248
249 $count = count($subset) - 1;
250
251 $r = '';
252
253 for ($i = 0; $i < $length; $i++) {
254 $r .= $subset[mt_rand(0, $count)];
255 }
256
257 return $r;
258 }
259
260 /**
261 * Checks if a token is valid
262 *
263 * @author Art <a.molcanovas@gmail.com>
264 *
265 * @param string $tokenName The token name
266 * @param array $dataArray Which data array to check. Defaults to $_POST
267 *
268 * @return bool TRUE if the token is valid, false if not
269 */
270 static function tokenValid($tokenName, array $dataArray = null) {
271 if (!AbstractSession::isActive()) {
272 phpWarning('Session isn\'t initialised - tokens unavailable.');
273
274 return false;
275 } else {
276 if ($dataArray === null) {
277 $dataArray = $_POST;
278 }
279
280 return $_SESSION[$tokenName] &&
281 \get($dataArray[$tokenName]) &&
282 $_SESSION[$tokenName] == $dataArray[$tokenName];
283 }
284 }
285
286 /**
287 * Removes a token from session data
288 *
289 * @author Art <a.molcanovas@gmail.com>
290 *
291 * @param string $tokenName The token's name
292 *
293 * @return bool TRUE if the session handler was loaded, false if not
294 */
295 static function tokenRemove($tokenName) {
296 if (AbstractSession::isActive()) {
297 unset($_SESSION[$tokenName]);
298
299 return true;
300 } else {
301 phpWarning('Session not initialised - tokens unavailable.');
302
303 return false;
304 }
305 }
306
307 /**
308 * Returns an unhashed browser/IP fingerprint
309 *
310 * @author Art <a.molcanovas@gmail.com>
311 * @return string
312 */
313 static function getFingerprint() {
314 return '$%c0hYlc$kn!rZF' .
315 \get($_SERVER['HTTP_USER_AGENT']) .
316 \get($_SERVER['HTTP_DNT']) .
317 '^#J!kCRh&H4CKav' .
318 \get($_SERVER['HTTP_ACCEPT_LANGUAGE']) .
319 'h0&ThYYxk4YOD!g' .
320 \get($_SERVER['REMOTE_ADDR']);
321 }
322
323 }
324 }
325