Rev 5 | Rev 7 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed
Rev 5 | Rev 6 | ||
---|---|---|---|
Line 1... | Line 1... | ||
1 | <?php |
1 | <?php |
2 | 2 | ||
3 | /* |
3 | /* |
4 | * php_clientchallenge |
4 | * php_clientchallenge |
5 | * Copyright 2021 Daniel Marschall, ViaThinkSoft |
5 | * Copyright 2021-2022 Daniel Marschall, ViaThinkSoft |
6 | * |
6 | * |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
7 | * Licensed under the Apache License, Version 2.0 (the "License"); |
8 | * you may not use this file except in compliance with 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 |
9 | * You may obtain a copy of the License at |
10 | * |
10 | * |
Line 19... | Line 19... | ||
19 | 19 | ||
20 | namespace ViaThinkSoft\RateLimitingChallenge; |
20 | namespace ViaThinkSoft\RateLimitingChallenge; |
21 | 21 | ||
22 | class ClientChallenge { |
22 | class ClientChallenge { |
23 | 23 | ||
- | 24 | const OPEN_TRANS_DIR = __DIR__.'/cache'; |
|
- | 25 | ||
24 | private static function sha3_512($message, $raw_output=false) { |
26 | private static function sha3_512($message, $raw_output=false) { |
25 | if (version_compare(PHP_VERSION, '7.1.0') >= 0) { |
27 | if (version_compare(PHP_VERSION, '7.1.0') >= 0) { |
26 | return hash('sha3-512', $message, $raw_output); |
28 | return hash('sha3-512', $message, $raw_output); |
27 | } else { |
29 | } else { |
- | 30 | // Download file if required (usually composer should do it) |
|
- | 31 | if (file_exists(__DIR__.'/Sha3.php')) include_once __DIR__.'/Sha3.php'; |
|
- | 32 | if (!class_exists('\bb\Sha3\Sha3')) { |
|
- | 33 | $sha3_lib = file_get_contents('https://raw.githubusercontent.com/danielmarschall/php-sha3/master/src/Sha3.php'); |
|
- | 34 | if (file_put_contents(__DIR__.'/Sha3.php', $sha3_lib)) { |
|
- | 35 | include_once __DIR__.'/Sha3.php'; |
|
- | 36 | } else { |
|
- | 37 | eval('?>'.$sha3_lib); |
|
- | 38 | } |
|
- | 39 | } |
|
28 | return \bb\Sha3\Sha3::hash($message, 512, $raw_output); /** @phpstan-ignore-line */ |
40 | return \bb\Sha3\Sha3::hash($message, 512, $raw_output); |
29 | } |
41 | } |
30 | } |
42 | } |
31 | 43 | ||
32 | private static function sha3_512_hmac($message, $key, $raw_output=false) { |
44 | private static function sha3_512_hmac($message, $key, $raw_output=false) { |
33 | // RFC 2104 HMAC |
45 | // RFC 2104 HMAC |
Line 51... | Line 63... | ||
51 | 63 | ||
52 | return self::sha3_512($k_opad . self::sha3_512($k_ipad . $message, true)); |
64 | return self::sha3_512($k_opad . self::sha3_512($k_ipad . $message, true)); |
53 | } |
65 | } |
54 | } |
66 | } |
55 | 67 | ||
56 | public static function checkValidation($max_time=10, $server_secret) { |
68 | private static function getOpenTransFileName($ip_target, $random) { |
- | 69 | // Delete challenges which were never completed |
|
- | 70 | $files = glob(self::OPEN_TRANS_DIR.'/*.tmp'); |
|
- | 71 | $expire = strtotime('-3 DAYS'); |
|
- | 72 | foreach ($files as $file) { |
|
- | 73 | if (!is_file($file)) continue; |
|
- | 74 | if (filemtime($file) > $expire) continue; |
|
- | 75 | @unlink($file); |
|
- | 76 | } |
|
57 | 77 | ||
58 | if (!isset($_REQUEST['vts_validation_result'])) throw new \Exception('No challenge response found'); |
78 | return self::OPEN_TRANS_DIR.'/'.self::sha3_512($ip_target.'/'.$random).'.tmp'; |
- | 79 | } |
|
59 | 80 | ||
- | 81 | public static function checkValidation($client_response, $max_time=10, $server_secret) { |
|
60 | list($starttime, $ip_target, $challenge, $answer, $challenge_integrity) = @json_decode($_REQUEST['vts_validation_result'], true); |
82 | list($starttime, $ip_target, $challenge, $answer, $challenge_integrity) = $client_response; |
- | 83 | $open_trans_file = self::getOpenTransFileName($ip_target, $answer); |
|
61 | 84 | ||
62 | if ($ip_target != $_SERVER['REMOTE_ADDR']) { |
85 | if ($ip_target != $_SERVER['REMOTE_ADDR']) { |
63 | throw new \Exception('Wrong IP'); |
86 | throw new \Exception('Wrong IP'); |
64 | } else if (time()-$starttime > $max_time) { |
87 | } else if (time()-$starttime > $max_time) { |
65 | throw new \Exception('Challenge expired'); |
88 | throw new \Exception('Challenge expired'); |
66 | } else if ($challenge_integrity != self::sha3_512_hmac($challenge,$server_secret)) { |
89 | } else if ($challenge_integrity != self::sha3_512_hmac($challenge,$server_secret)) { |
67 | throw new \Exception('Challenge integrity failed'); |
90 | throw new \Exception('Challenge integrity failed'); |
68 | } else if ($challenge !== self::sha3_512($starttime.'/'.$ip_target.'/'.$answer)) { |
91 | } else if ($challenge !== self::sha3_512($starttime.'/'.$ip_target.'/'.$answer)) { |
69 | throw new \Exception('Wrong answer'); |
92 | throw new \Exception('Wrong answer'); |
- | 93 | } else if (!file_exists($open_trans_file)) { |
|
- | 94 | throw new \Exception('Challenge submitted twice or transaction missing'); |
|
70 | } else { |
95 | } else { |
- | 96 | unlink($open_trans_file); |
|
71 | return true; |
97 | return true; |
72 | } |
98 | } |
73 | } |
99 | } |
74 | 100 | ||
75 | // This is only called by ajax_get_challenge.php |
- | |
76 | public static function createChallenge($complexity=500000, $server_secret) { |
101 | public static function createChallenge($complexity=50000, $server_secret) { |
77 | - | ||
- | 102 | $offset = 0; // doesn't matter |
|
78 | $min = 0; |
103 | $min = $offset; |
79 | $max = $complexity; |
104 | $max = $offset + $complexity; |
80 | 105 | ||
81 | $starttime = time(); |
106 | $starttime = time(); |
82 | 107 | ||
83 | $random = rand($min,$max); // TODO: cryptographic rand |
108 | $random = rand($min,$max); // TODO: cryptographic rand |
84 | 109 | ||
Line 88... | Line 113... | ||
88 | 113 | ||
89 | $challenge_integrity = self::sha3_512_hmac($challenge,$server_secret); |
114 | $challenge_integrity = self::sha3_512_hmac($challenge,$server_secret); |
90 | 115 | ||
91 | $send_to_client = array($starttime, $ip_target, $challenge, $min, $max, $challenge_integrity); |
116 | $send_to_client = array($starttime, $ip_target, $challenge, $min, $max, $challenge_integrity); |
92 | 117 | ||
- | 118 | $open_trans_file = self::getOpenTransFileName($ip_target, $random); |
|
93 | header('Content-Type:application/json'); |
119 | if (@file_put_contents($open_trans_file, '') === false) { |
94 | die(json_encode($send_to_client)); |
120 | throw new \Exception("Cannot write $open_trans_file"); |
- | 121 | } |
|
95 | 122 | ||
- | 123 | return $send_to_client; |
|
96 | } |
124 | } |
97 | 125 | ||
98 | } |
126 | } |