Passed
Push — master ( 99a67f...d5b2ba )
by Antonio Carlos
12:27 queued 03:54
created

Google2FA::getAlgorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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