Subversion Repositories php_clientchallenge

Rev

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
}