Passed
Push — master ( 9fc4f7...c9bd42 )
by Antonio Carlos
01:43
created

Google2FA::getValidAlgorithms()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

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