Completed
Push — master ( 3d7e16...e56614 )
by Antonio Carlos
01:22
created

Google2FA::makeStartingTimestamp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 4
nc 2
nop 3
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($binarySeed, $key, $window, $startingTimestamp, $timestamp, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
75
    {
76
        for (; $startingTimestamp <= $timestamp + $this->getWindow($window); $startingTimestamp++) {
77
            if (hash_equals($this->oathHotp($binarySeed, $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 string $initalizationKey
105
     *
106
     * @return string
107
     */
108
    public function getCurrentOtp($initalizationKey)
109
    {
110
        $timestamp = $this->getTimestamp();
111
112
        $secretKey = $this->base32Decode($initalizationKey);
113
114
        return $this->oathHotp($secretKey, $timestamp);
115
    }
116
117
    /**
118
     * Get key regeneration.
119
     *
120
     * @return mixed
121
     */
122
    public function getKeyRegeneration()
123
    {
124
        return $this->keyRegeneration;
125
    }
126
127
    /**
128
     * Get the key regeneration time in seconds.
129
     *
130
     * @return int
131
     */
132
    public function getKeyRegenerationTime()
133
    {
134
        return $this->keyRegeneration;
135
    }
136
137
    /**
138
     * Get OTP length.
139
     *
140
     * @return mixed
141
     */
142
    public function getOneTimePasswordLength()
143
    {
144
        return $this->oneTimePasswordLength;
145
    }
146
147
    /**
148
     * Get secret.
149
     *
150
     * @return mixed
151
     */
152
    public function getSecret($secret = null)
153
    {
154
        return
155
            is_null($secret)
156
            ? $this->secret
157
            : $secret;
158
    }
159
160
    /**
161
     * Returns the current Unix Timestamp divided by the $keyRegeneration
162
     * period.
163
     *
164
     * @return int
165
     **/
166
    public function getTimestamp()
167
    {
168
        return (int) floor(microtime(true) / $this->keyRegeneration);
169
    }
170
171
    /**
172
     * Get the OTP window.
173
     *
174
     * @param null|int $window
175
     *
176
     * @return mixed
177
     */
178
    public function getWindow($window = null)
179
    {
180
        return
181
            is_null($window)
182
                ? $this->window
183
                : $window;
184
    }
185
186
    /**
187
     * Make a window based starting timestamp.
188
     *
189
     * @param $window
190
     * @param $timestamp
191
     * @param $oldTimestamp
192
     * @return mixed
193
     */
194
    private function makeStartingTimestamp($window, $timestamp, $oldTimestamp)
195
    {
196
        return $oldTimestamp === Constants::ARGUMENT_NOT_SET
197
            ? $timestamp - $this->getWindow($window)
198
            : max($timestamp - $this->getWindow($window), $oldTimestamp + 1);
199
    }
200
201
    /**
202
     * Get/use a starting timestamp for key verification.
203
     *
204
     * @param string|int|null $timestamp
205
     *
206
     * @return int
207
     */
208
    protected function makeTimestamp($timestamp = null)
209
    {
210
        if (is_null($timestamp)) {
211
            return $this->getTimestamp();
212
        }
213
214
        return (int) $timestamp;
215
    }
216
217
    /**
218
     * Takes the secret key and the timestamp and returns the one time
219
     * password.
220
     *
221
     * @param string $key     - Secret key in binary form.
222
     * @param int    $counter - Timestamp as returned by getTimestamp.
223
     *
224
     * @throws SecretKeyTooShortException
225
     *
226
     * @return string
227
     */
228
    public function oathHotp($key, $counter)
229
    {
230
        if (strlen($key) < 8) {
231
            throw new SecretKeyTooShortException();
232
        }
233
234
        // Counter must be 64-bit int
235
        $bin_counter = pack('N*', 0, $counter);
236
237
        $hash = hash_hmac('sha1', $bin_counter, $key, true);
238
239
        return str_pad($this->oathTruncate($hash), $this->getOneTimePasswordLength(), '0', STR_PAD_LEFT);
240
    }
241
242
    /**
243
     * Extracts the OTP from the SHA1 hash.
244
     *
245
     * @param string $hash
246
     *
247
     * @return int
248
     **/
249
    public function oathTruncate($hash)
250
    {
251
        $offset = ord($hash[19]) & 0xf;
252
253
        $temp = unpack('N', substr($hash, $offset, 4));
254
255
        return substr($temp[1] & 0x7fffffff, -$this->getOneTimePasswordLength());
256
    }
257
258
    /**
259
     * Remove invalid chars from a base 32 string.
260
     *
261
     * @param $string
262
     *
263
     * @return mixed
264
     */
265
    public function removeInvalidChars($string)
266
    {
267
        return preg_replace('/[^'.Constants::VALID_FOR_B32.']/', '', $string);
268
    }
269
270
    /**
271
     * Setter for the enforce Google Authenticator compatibility property.
272
     *
273
     * @param mixed $enforceGoogleAuthenticatorCompatibility
274
     *
275
     * @return $this
276
     */
277
    public function setEnforceGoogleAuthenticatorCompatibility($enforceGoogleAuthenticatorCompatibility)
278
    {
279
        $this->enforceGoogleAuthenticatorCompatibility = $enforceGoogleAuthenticatorCompatibility;
280
281
        return $this;
282
    }
283
284
    /**
285
     * Set key regeneration.
286
     *
287
     * @param mixed $keyRegeneration
288
     */
289
    public function setKeyRegeneration($keyRegeneration)
290
    {
291
        $this->keyRegeneration = $keyRegeneration;
292
    }
293
294
    /**
295
     * Set OTP length.
296
     *
297
     * @param mixed $oneTimePasswordLength
298
     */
299
    public function setOneTimePasswordLength($oneTimePasswordLength)
300
    {
301
        $this->oneTimePasswordLength = $oneTimePasswordLength;
302
    }
303
304
    /**
305
     * Set secret.
306
     *
307
     * @param mixed $secret
308
     */
309
    public function setSecret($secret)
310
    {
311
        $this->secret = $secret;
312
    }
313
314
    /**
315
     * Set the OTP window.
316
     *
317
     * @param mixed $window
318
     */
319
    public function setWindow($window)
320
    {
321
        $this->window = $window;
322
    }
323
324
    /**
325
     * Verifies a user inputted key against the current timestamp. Checks $window
326
     * keys either side of the timestamp.
327
     *
328
     * @param string          $key          - User specified key
329
     * @param null|string     $secret
330
     * @param null|int        $window
331
     * @param null|int        $timestamp
332
     * @param null|string|int $oldTimestamp
333
     *
334
     * @return bool|int
335
     */
336
    public function verify($key, $secret = null, $window = null, $timestamp = null, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
337
    {
338
        return $this->verifyKey(
339
            $secret,
340
            $key,
341
            $window,
342
            $timestamp,
343
            $oldTimestamp
344
        );
345
    }
346
347
    /**
348
     * Verifies a user inputted key against the current timestamp. Checks $window
349
     * keys either side of the timestamp.
350
     *
351
     * @param string          $secret
352
     * @param string          $key          - User specified key
353
     * @param null|int        $window
354
     * @param null|int        $timestamp
355
     * @param null|string|int $oldTimestamp
356
     *
357
     * @return bool|int
358
     */
359
    public function verifyKey($secret, $key, $window = null, $timestamp = null, $oldTimestamp = Constants::ARGUMENT_NOT_SET)
360
    {
361
        $timestamp = $this->makeTimestamp($timestamp);
362
363
        return $this->findValidOTP(
364
           $this->base32Decode($this->getSecret($secret)),
365
           $key,
366
           $window,
367
           $this->makeStartingTimestamp($window, $timestamp, $oldTimestamp),
368
           $timestamp,
369
           $oldTimestamp
370
       );
371
    }
372
373
    /**
374
     * Verifies a user inputted key against the current timestamp. Checks $window
375
     * keys either side of the timestamp, but ensures that the given key is newer than
376
     * the given oldTimestamp. Useful if you need to ensure that a single key cannot
377
     * be used twice.
378
     *
379
     * @param string   $secret
380
     * @param string   $key          - User specified key
381
     * @param int      $oldTimestamp - The timestamp from the last verified key
382
     * @param int|null $window
383
     * @param int|null $timestamp
384
     *
385
     * @return bool|int - false (not verified) or the timestamp of the verified key
386
     **/
387
    public function verifyKeyNewer($secret, $key, $oldTimestamp, $window = null, $timestamp = null)
388
    {
389
        return $this->verifyKey($secret, $key, $window, $timestamp, $oldTimestamp);
390
    }
391
}
392