Completed
Push — master ( 4bde32...7747d8 )
by Antonio Carlos
02:10
created

Google2FA::checkGoogleAuthenticatorCompatibility()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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