Test Failed
Pull Request — master (#83)
by Antonio Carlos
05:22 queued 14s
created

Google2FA::removeInvalidChars()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace PragmaRX\Google2FA;
4
5
/*
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 * PHP Google two-factor authentication module.
20
 *
21
 * See http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
22
 * for more details
23
 *
24
 * @author Phil (Orginal author of this class)
25
 *
26
 * Changes have been made in the original class to remove all static methods and, also,
27
 * provide some other methods.
28
 *
29
 * @package    Google2FA
30
 * @author     Antonio Carlos Ribeiro @ PragmaRX
31
 **/
32
33
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
34
use PragmaRX\Google2FA\Support\Base32;
35
use PragmaRX\Google2FA\Support\Constants;
36
use PragmaRX\Google2FA\Support\QRCode;
37
38
class Google2FA
39
{
40
    use QRCode, Base32;
41
42
    /**
43
     * Length of the Token generated.
44
     */
45
    protected $oneTimePasswordLength = 6;
46
47
    /**
48
     * Interval between key regeneration.
49
     */
50
    protected $keyRegeneration = 30;
51
52
    /**
53
     * Secret.
54
     */
55
    protected $secret;
56
57
    /**
58
     * Window.
59
     */
60
    protected $window = 1; // Keys will be valid for 60 seconds
61
62
    /**
63
     * Find a valid One Time Password.
64
     *
65
     * @param $binarySeed
66
     * @param $key
67
     * @param $window
68
     * @param $startingTimestamp
69
     * @param $timestamp
70
     * @param $oldTimestamp
71
     *
72
     * @return bool
73
     */
74
    public function findValidOTP($secret, $key, $window, $startingTimestamp, $timestamp, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
75
    {
76
        for (; $startingTimestamp <= $timestamp + $this->getWindow($window); $startingTimestamp++) {
77
            if (hash_equals($this->oathHotp($secret, $startingTimestamp), $key)) {
78
                return
79
                    $oldTimestamp === Constants::ARGUMENT_NOT_SET
80
                        ? true
81
                        : $startingTimestamp;
82
            }
83
        }
84
85
        return false;
86
    }
87
88
    /**
89
     * Generate a digit secret key in base32 format.
90
     *
91
     * @param int    $length
92
     * @param string $prefix
93
     *
94
     * @return string
95
     */
96
    public function generateSecretKey($length = 16, $prefix = '')
97
    {
98
        return $this->generateBase32RandomKey($length, $prefix);
99
    }
100
101
    /**
102
     * Get the current one time password for a key.
103
     *
104
     * @param $secret
105
     *
106
     * @return string
107
     */
108
    public function getCurrentOtp($secret)
109
    {
110
        return $this->oathHotp($secret, $this->getTimestamp());
111
    }
112
113
    /**
114
     * Get key regeneration.
115
     *
116
     * @return mixed
117
     */
118
    public function getKeyRegeneration()
119
    {
120
        return $this->keyRegeneration;
121
    }
122
123
    /**
124
     * Get OTP length.
125
     *
126
     * @return mixed
127
     */
128
    public function getOneTimePasswordLength()
129
    {
130
        return $this->oneTimePasswordLength;
131
    }
132
133
    /**
134
     * Get secret.
135
     *
136
     * @return mixed
137
     */
138
    public function getSecret($secret = null)
139
    {
140
        return
141
            is_null($secret)
142
            ? $this->secret
143
            : $secret;
144
    }
145
146
    /**
147
     * Returns the current Unix Timestamp divided by the $keyRegeneration
148
     * period.
149
     *
150
     * @return int
151
     **/
152
    public function getTimestamp()
153
    {
154
        return (int) floor(microtime(true) / $this->keyRegeneration);
155
    }
156
157
    /**
158
     * Get the OTP window.
159
     *
160
     * @param null|int $window
161
     *
162
     * @return mixed
163
     */
164
    public function getWindow($window = null)
165
    {
166
        return
167
            is_null($window)
168
                ? $this->window
169
                : $window;
170
    }
171
172
    /**
173
     * Make a window based starting timestamp.
174
     *
175
     * @param $window
176
     * @param $timestamp
177
     * @param $oldTimestamp
178
     *
179
     * @return mixed
180
     */
181
    private function makeStartingTimestamp($window, $timestamp, $oldTimestamp)
182
    {
183
        return $oldTimestamp === Constants::ARGUMENT_NOT_SET
184
            ? $timestamp - $this->getWindow($window)
185
            : max($timestamp - $this->getWindow($window), $oldTimestamp + 1);
186
    }
187
188
    /**
189
     * Get/use a starting timestamp for key verification.
190
     *
191
     * @param string|int|null $timestamp
192
     *
193
     * @return int
194
     */
195
    protected function makeTimestamp($timestamp = null)
196
    {
197
        if (is_null($timestamp)) {
198
            return $this->getTimestamp();
199
        }
200
201
        return (int) $timestamp;
202
    }
203
204
    /**
205
     * Takes the secret key and the timestamp and returns the one time
206
     * password.
207
     *
208
     * @param string $secret  - Secret key in binary form.
209
     * @param int    $counter - Timestamp as returned by getTimestamp.
210
     *
211
     * @throws SecretKeyTooShortException
212
     *
213
     * @return string
214
     */
215
    public function oathHotp($secret, $counter)
216
    {
217
        $secret = $this->base32Decode($this->getSecret($secret));
218
219
        if (strlen($secret) < 8) {
220
            throw new SecretKeyTooShortException();
221
        }
222
223
        // Counter must be 64-bit int
224
        $bin_counter = pack('N*', 0, $counter);
225
226
        $hash = hash_hmac('sha1', $bin_counter, $secret, true);
227
228
        return str_pad($this->oathTruncate($hash), $this->getOneTimePasswordLength(), '0', STR_PAD_LEFT);
229
    }
230
231
    /**
232
     * Extracts the OTP from the SHA1 hash.
233
     *
234
     * @param string $hash
235
     *
236
     * @return int
237
     **/
238
    public function oathTruncate($hash)
239
    {
240
        $offset = ord($hash[19]) & 0xf;
241
242
        $temp = unpack('N', substr($hash, $offset, 4));
243
244
        return substr($temp[1] & 0x7fffffff, -$this->getOneTimePasswordLength());
245
    }
246
247
    /**
248
     * Remove invalid chars from a base 32 string.
249
     *
250
     * @param $string
251
     *
252
     * @return mixed
253
     */
254
    public function removeInvalidChars($string)
255
    {
256
        return preg_replace('/[^'.Constants::VALID_FOR_B32.']/', '', $string);
257
    }
258
259
    /**
260
     * Setter for the enforce Google Authenticator compatibility property.
261
     *
262
     * @param mixed $enforceGoogleAuthenticatorCompatibility
263
     *
264
     * @return $this
265
     */
266
    public function setEnforceGoogleAuthenticatorCompatibility($enforceGoogleAuthenticatorCompatibility)
267
    {
268
        $this->enforceGoogleAuthenticatorCompatibility = $enforceGoogleAuthenticatorCompatibility;
269
270
        return $this;
271
    }
272
273
    /**
274
     * Set key regeneration.
275
     *
276
     * @param mixed $keyRegeneration
277
     */
278
    public function setKeyRegeneration($keyRegeneration)
279
    {
280
        $this->keyRegeneration = $keyRegeneration;
281
    }
282
283
    /**
284
     * Set OTP length.
285
     *
286
     * @param mixed $oneTimePasswordLength
287
     */
288
    public function setOneTimePasswordLength($oneTimePasswordLength)
289
    {
290
        $this->oneTimePasswordLength = $oneTimePasswordLength;
291
    }
292
293
    /**
294
     * Set secret.
295
     *
296
     * @param mixed $secret
297
     */
298
    public function setSecret($secret)
299
    {
300
        $this->secret = $secret;
301
    }
302
303
    /**
304
     * Set the OTP window.
305
     *
306
     * @param mixed $window
307
     */
308
    public function setWindow($window)
309
    {
310
        $this->window = $window;
311
    }
312
313
    /**
314
     * Verifies a user inputted key against the current timestamp. Checks $window
315
     * keys either side of the timestamp.
316
     *
317
     * @param string          $key          - User specified key
318
     * @param null|string     $secret
319
     * @param null|int        $window
320
     * @param null|int        $timestamp
321
     * @param null|string|int $oldTimestamp
322
     *
323
     * @return bool|int
324
     */
325
    public function verify($key, $secret = null, $window = null, $timestamp = null, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
326
    {
327
        return $this->verifyKey(
328
            $secret,
329
            $key,
330
            $window,
331
            $timestamp,
332
            $oldTimestamp
333
        );
334
    }
335
336
    /**
337
     * Verifies a user inputted key against the current timestamp. Checks $window
338
     * keys either side of the timestamp.
339
     *
340
     * @param string          $secret
341
     * @param string          $key          - User specified key
342
     * @param null|int        $window
343
     * @param null|int        $timestamp
344
     * @param null|string|int $oldTimestamp
345
     *
346
     * @return bool|int
347
     */
348
    public function verifyKey($secret, $key, $window = null, $timestamp = null, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
349
    {
350
        $timestamp = $this->makeTimestamp($timestamp);
351
352
        return $this->findValidOTP(
353
           $secret,
354
           $key,
355
           $window,
356
           $this->makeStartingTimestamp($window, $timestamp, $oldTimestamp),
357
           $timestamp,
358
           $oldTimestamp
359
       );
360
    }
361
362
    /**
363
     * Verifies a user inputted key against the current timestamp. Checks $window
364
     * keys either side of the timestamp, but ensures that the given key is newer than
365
     * the given oldTimestamp. Useful if you need to ensure that a single key cannot
366
     * be used twice.
367
     *
368
     * @param string   $secret
369
     * @param string   $key          - User specified key
370
     * @param int      $oldTimestamp - The timestamp from the last verified key
371
     * @param int|null $window
372
     * @param int|null $timestamp
373
     *
374
     * @return bool|int - false (not verified) or the timestamp of the verified key
375
     **/
376
    public function verifyKeyNewer($secret, $key, $oldTimestamp, $window = null, $timestamp = null)
377
    {
378
        return $this->verifyKey($secret, $key, $window, $timestamp, $oldTimestamp);
379
    }
380
}
381