Completed
Pull Request — master (#126)
by
unknown
01:55
created

Google2FA::setAlgorithm()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 16
ccs 8
cts 8
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
namespace PragmaRX\Google2FA;
4
5
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
6
use PragmaRX\Google2FA\Support\Base32;
7
use PragmaRX\Google2FA\Support\Constants;
8
use PragmaRX\Google2FA\Support\QRCode;
9
10
class Google2FA
11
{
12
    use QRCode, Base32;
13
14
    /**
15
     * Algorithm.
16
     */
17
    protected $algorithm = Constants::SHA1;
18
19
    /**
20
     * Length of the Token generated.
21
     */
22
    protected $oneTimePasswordLength = 6;
23
24
    /**
25
     * Interval between key regeneration.
26
     */
27
    protected $keyRegeneration = 30;
28
29
    /**
30
     * Secret.
31
     */
32
    protected $secret;
33
34
    /**
35
     * Window.
36
     */
37
    protected $window = 1; // Keys will be valid for 60 seconds
38
39
    /**
40
     * Find a valid One Time Password.
41
     *
42
     * @param $secret
43
     * @param $key
44
     * @param $window
45
     * @param $startingTimestamp
46
     * @param $timestamp
47
     * @param string $oldTimestamp
48
     *
49
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
50
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
51
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
52
     *
53
     * @return bool
54
     */
55 14
    public function findValidOTP(
56
        $secret,
57
        $key,
58
        $window,
59
        $startingTimestamp,
60
        $timestamp,
61
        $oldTimestamp = Constants::ARGUMENT_NOT_SET
62
    ) {
63
        for (
64
            ;
65 14
            $startingTimestamp <= $timestamp + $this->getWindow($window);
66
            $startingTimestamp++
67
        ) {
68
            if (
69 14
                hash_equals($this->oathTotp($secret, $startingTimestamp), $key)
70
            ) {
71 9
                return $oldTimestamp === Constants::ARGUMENT_NOT_SET
72 6
                    ? true
73 9
                    : $startingTimestamp;
74
            }
75
        }
76
77 8
        return false;
78
    }
79
80
    /**
81
     * Generate a digit secret key in base32 format.
82
     *
83
     * @param int    $length
84
     * @param string $prefix
85
     *
86
     * @throws Exceptions\InvalidCharactersException
87
     * @throws Exceptions\IncompatibleWithGoogleAuthenticatorException
88
     *
89
     * @return string
90
     */
91 5
    public function generateSecretKey($length = 16, $prefix = '')
92
    {
93 5
        return $this->generateBase32RandomKey($length, $prefix);
94
    }
95
96
    /**
97
     * Get the current one time password for a key.
98
     *
99
     * @param $secret
100
     *
101
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
102
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
103
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
104
     *
105
     * @return string
106
     */
107 2
    public function getCurrentOtp($secret)
108
    {
109 2
        return $this->oathTotp($secret, $this->getTimestamp());
110
    }
111
112
    /**
113
     * Get the HMAC algorithm.
114
     *
115
     * @param null|int $algorithm
116
     *
117
     * @return mixed
118
     */
119 12
    public function getAlgorithm()
120
    {
121 12
        return $this->algorithm;
122
    }
123
124
    /**
125
     * Get key regeneration.
126
     *
127
     * @return mixed
128
     */
129 1
    public function getKeyRegeneration()
130
    {
131 1
        return $this->keyRegeneration;
132
    }
133
134
    /**
135
     * Get OTP length.
136
     *
137
     * @return mixed
138
     */
139 12
    public function getOneTimePasswordLength()
140
    {
141 12
        return $this->oneTimePasswordLength;
142
    }
143
144
    /**
145
     * Get secret.
146
     *
147
     * @param string|null $secret
148
     *
149
     * @return mixed
150
     */
151 16
    public function getSecret($secret = null)
152
    {
153 16
        return is_null($secret) ? $this->secret : $secret;
154
    }
155
156
    /**
157
     * Returns the current Unix Timestamp divided by the $keyRegeneration
158
     * period.
159
     *
160
     * @return int
161
     **/
162 4
    public function getTimestamp()
163
    {
164 4
        return (int) floor(microtime(true) / $this->keyRegeneration);
165
    }
166
167
    /**
168
     * Get the OTP window.
169
     *
170
     * @param null|int $window
171
     *
172
     * @return mixed
173
     */
174 14
    public function getWindow($window = null)
175
    {
176 14
        return is_null($window) ? $this->window : $window;
177
    }
178
179
    /**
180
     * Make a window based starting timestamp.
181
     *
182
     * @param $window
183
     * @param $timestamp
184
     * @param $oldTimestamp
185
     *
186
     * @return mixed
187
     */
188 14
    private function makeStartingTimestamp($window, $timestamp, $oldTimestamp)
189
    {
190 14
        return $oldTimestamp === Constants::ARGUMENT_NOT_SET
191 11
            ? $timestamp - $this->getWindow($window)
192 14
            : max($timestamp - $this->getWindow($window), $oldTimestamp + 1);
193
    }
194
195
    /**
196
     * Get/use a starting timestamp for key verification.
197
     *
198
     * @param string|int|null $timestamp
199
     *
200
     * @return int
201
     */
202 14
    protected function makeTimestamp($timestamp = null)
203
    {
204 14
        if (is_null($timestamp)) {
205 1
            return $this->getTimestamp();
206
        }
207
208 14
        return (int) $timestamp;
209
    }
210
211
    /**
212
     * Takes the secret key and the timestamp and returns the one time
213
     * password.
214
     *
215
     * @param string $secret  - Secret key in binary form.
216
     * @param int    $counter - Timestamp as returned by getTimestamp.
217
     *
218
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
219
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
220
     * @throws Exceptions\IncompatibleWithGoogleAuthenticatorException
221
     *
222
     * @return string
223
     */
224 16
    public function oathTotp($secret, $counter)
225
    {
226 16
        $secret = $this->base32Decode($this->getSecret($secret));
227
228 11
        if (strlen($secret) < 8) {
229
            throw new SecretKeyTooShortException();
230
        }
231
232
        // Counter must be 64-bit int
233 11
        $bin_counter = pack('N*', 0, $counter);
234
235 11
        $hash = hash_hmac($this->getAlgorithm(), $bin_counter, $secret, true);
236
237 11
        return str_pad(
238 11
            $this->oathTruncate($hash),
239 11
            $this->getOneTimePasswordLength(),
240 11
            '0',
241 11
            STR_PAD_LEFT
242
        );
243
    }
244
245
    /**
246
     * Extracts the OTP from the SHA1 hash.
247
     *
248
     * @param string $hash
249
     *
250
     * @return int
251
     **/
252 11
    public function oathTruncate($hash)
253
    {
254 11
        $offset = ord($hash[strlen($hash) - 1]) & 0xf;
255
256 11
        $temp = unpack('N', substr($hash, $offset, 4));
257
258 11
        return substr(
259 11
            $temp[1] & 0x7fffffff,
260 11
            -$this->getOneTimePasswordLength()
261
        );
262
    }
263
264
    /**
265
     * Remove invalid chars from a base 32 string.
266
     *
267
     * @param $string
268
     *
269
     * @return mixed
270
     */
271 1
    public function removeInvalidChars($string)
272
    {
273 1
        return preg_replace(
274 1
            '/[^' . Constants::VALID_FOR_B32 . ']/',
275 1
            '',
276 1
            $string
277
        );
278
    }
279
280
    /**
281
     * Setter for the enforce Google Authenticator compatibility property.
282
     *
283
     * @param mixed $enforceGoogleAuthenticatorCompatibility
284
     *
285
     * @return $this
286
     */
287 9
    public function setEnforceGoogleAuthenticatorCompatibility(
288
        $enforceGoogleAuthenticatorCompatibility
289
    ) {
290 9
        $this->enforceGoogleAuthenticatorCompatibility = $enforceGoogleAuthenticatorCompatibility;
291
292 9
        return $this;
293
    }
294
295
    /**
296
     * Set the HMAC hashing algorithm.
297
     *
298
     * @param mixed $algorithm
299
     */
300 5
    public function setAlgorithm($algorithm)
301
    {
302
        $validAlgorithms = [
303 5
            Constants::SHA1,
304 5
            Constants::SHA256,
305 5
            Constants::SHA512,
306
        ];
307
308
        // Default to SHA1 HMAC algorithm
309 5
        if (! in_array($algorithm, $validAlgorithms)) {
310 1
            $this->algorithm = Constants::SHA1;
311
312 1
            return;
313
        }
314
315 5
        $this->algorithm = $algorithm;
316 5
    }
317
318
    /**
319
     * Set key regeneration.
320
     *
321
     * @param mixed $keyRegeneration
322
     */
323 1
    public function setKeyRegeneration($keyRegeneration)
324
    {
325 1
        $this->keyRegeneration = $keyRegeneration;
326 1
    }
327
328
    /**
329
     * Set OTP length.
330
     *
331
     * @param mixed $oneTimePasswordLength
332
     */
333 2
    public function setOneTimePasswordLength($oneTimePasswordLength)
334
    {
335 2
        $this->oneTimePasswordLength = $oneTimePasswordLength;
336 2
    }
337
338
    /**
339
     * Set secret.
340
     *
341
     * @param mixed $secret
342
     */
343 1
    public function setSecret($secret)
344
    {
345 1
        $this->secret = $secret;
346 1
    }
347
348
    /**
349
     * Set the OTP window.
350
     *
351
     * @param mixed $window
352
     */
353 3
    public function setWindow($window)
354
    {
355 3
        $this->window = $window;
356 3
    }
357
358
    /**
359
     * Verifies a user inputted key against the current timestamp. Checks $window
360
     * keys either side of the timestamp.
361
     *
362
     * @param string          $key          - User specified key
363
     * @param null|string     $secret
364
     * @param null|int        $window
365
     * @param null|int        $timestamp
366
     * @param null|string|int $oldTimestamp
367
     *
368
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
369
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
370
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
371
     *
372
     * @return bool|int
373
     */
374 1
    public function verify(
375
        $key,
376
        $secret = null,
377
        $window = null,
378
        $timestamp = null,
379
        $oldTimestamp = Constants::ARGUMENT_NOT_SET
380
    ) {
381 1
        return $this->verifyKey(
382 1
            $secret,
383 1
            $key,
384 1
            $window,
385 1
            $timestamp,
386 1
            $oldTimestamp
387
        );
388
    }
389
390
    /**
391
     * Verifies a user inputted key against the current timestamp. Checks $window
392
     * keys either side of the timestamp.
393
     *
394
     * @param string          $secret
395
     * @param string          $key          - User specified key
396
     * @param null|int        $window
397
     * @param null|int        $timestamp
398
     * @param null|string|int $oldTimestamp
399
     *
400
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
401
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
402
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
403
     *
404
     * @return bool|int
405
     */
406 14
    public function verifyKey(
407
        $secret,
408
        $key,
409
        $window = null,
410
        $timestamp = null,
411
        $oldTimestamp = Constants::ARGUMENT_NOT_SET
412
    ) {
413 14
        $timestamp = $this->makeTimestamp($timestamp);
414
415 14
        return $this->findValidOTP(
416 14
            $secret,
417 14
            $key,
418 14
            $window,
419 14
            $this->makeStartingTimestamp($window, $timestamp, $oldTimestamp),
420 14
            $timestamp,
421 14
            $oldTimestamp
422
        );
423
    }
424
425
    /**
426
     * Verifies a user inputted key against the current timestamp. Checks $window
427
     * keys either side of the timestamp, but ensures that the given key is newer than
428
     * the given oldTimestamp. Useful if you need to ensure that a single key cannot
429
     * be used twice.
430
     *
431
     * @param string   $secret
432
     * @param string   $key          - User specified key
433
     * @param int      $oldTimestamp - The timestamp from the last verified key
434
     * @param int|null $window
435
     * @param int|null $timestamp
436
     *
437
     * @throws \PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException
438
     * @throws \PragmaRX\Google2FA\Exceptions\InvalidCharactersException
439
     * @throws \PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException
440
     *
441
     * @return bool|int - false (not verified) or the timestamp of the verified key
442
     */
443 3
    public function verifyKeyNewer(
444
        $secret,
445
        $key,
446
        $oldTimestamp,
447
        $window = null,
448
        $timestamp = null
449
    ) {
450 3
        return $this->verifyKey(
451 3
            $secret,
452 3
            $key,
453 3
            $window,
454 3
            $timestamp,
455 3
            $oldTimestamp
456
        );
457
    }
458
}
459