mirror of
https://github.com/RobThree/TwoFactorAuth.git
synced 2026-08-30 12:06:59 +00:00
* Dropped ConvertUnixTimeDotComTimeProvider
* Added NTPTimeProvider * Added out argument on verifyCode() method that returns the matched timeslice (if any) when a valid code was entered
This commit is contained in:
@@ -180,11 +180,11 @@ You can easily implement your own `RNGProvider` by simply implementing the `IRNG
|
||||
|
||||
### Time providers
|
||||
|
||||
Another set of providers in this library are the Time Providers; this library provides three 'built-in' ones. The default Time Provider used is the [`LocalMachineTimeProvider`](lib/Providers/Time/LocalMachineTimeProvider.php); this provider simply returns the output of `Time()` and is *highly recommended* as default provider. The [`HttpTimeProvider`](lib/Providers/Time/HttpTimeProvider.php) executes a `HEAD` request against a given webserver (default: google.com) and tries to extract the `Date:`-HTTP header and returns it's date. Other url's/domains can be used by specifying the url in the constructor. The final Time Provider is the [`ConvertUnixTimeDotComTimeProvider`](lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php) which does a HTTP request to `convert-unix-time.com/api` and decodes the `JSON` result to retrieve the time.
|
||||
Another set of providers in this library are the Time Providers; this library provides three 'built-in' ones. The default Time Provider used is the [`LocalMachineTimeProvider`](lib/Providers/Time/LocalMachineTimeProvider.php); this provider simply returns the output of `Time()` and is *highly recommended* as default provider. The [`HttpTimeProvider`](lib/Providers/Time/HttpTimeProvider.php) executes a `HEAD` request against a given webserver (default: google.com) and tries to extract the `Date:`-HTTP header and returns it's date. Other url's/domains can be used by specifying the url in the constructor. The final Time Provider is the [`NTPTimeProvider`](lib/Providers/Time/NTPTimeProvider.php) which does an NTP request to a specified NTP server.
|
||||
|
||||
You can easily implement your own `TimeProvider` by simply implementing the `ITimeProvider` interface.
|
||||
|
||||
As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to Google's and convert-unix-time.com's current time. You can pass an array of `ITimeProvider`s and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `ConvertUnixTimeDotComTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly.
|
||||
As to *why* these Time Providers are implemented: it allows the TwoFactorAuth library to ensure the hosts time is correct (or rather: within a margin). You can use the `ensureCorrectTime()` method to ensure the hosts time is correct. By default this method will compare the hosts time (returned by calling `time()` on the `LocalMachineTimeProvider`) to Google's and convert-unix-time.com's current time. You can pass an array of `ITimeProvider`s and specify the `leniency` (second argument) allowed (default: 5 seconds). The method will throw when the TwoFactorAuth's timeprovider (which can be any `ITimeProvider`, see constructor) differs more than the given amount of seconds from any of the given `ITimeProviders`. We advise to call this method sparingly when relying on 3rd parties (which both the `HttpTimeProvider` and `NTPTimeProvider` do) or, if you need to ensure time is correct on a (very) regular basis to implement an `ITimeProvider` that is more efficient than the 'built-in' ones (like use a GPS signal). The `ensureCorrectTime()` method is mostly to be used to make sure the server is configured correctly.
|
||||
|
||||
## Integrations
|
||||
|
||||
|
||||
@@ -38,10 +38,10 @@
|
||||
<Compile Include="lib\Providers\Rng\OpenSSLRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\HashRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\RNGException.php" />
|
||||
<Compile Include="lib\Providers\Time\ConvertUnixTimeDotComTimeProvider.php" />
|
||||
<Compile Include="lib\Providers\Time\HttpTimeProvider.php" />
|
||||
<Compile Include="lib\Providers\Time\ITimeProvider.php" />
|
||||
<Compile Include="lib\Providers\Time\LocalMachineTimeProvider.php" />
|
||||
<Compile Include="lib\Providers\Time\NTPTimeProvider.php" />
|
||||
<Compile Include="lib\Providers\Time\TimeException.php" />
|
||||
<Compile Include="lib\TwoFactorAuth.php" />
|
||||
<Compile Include=".gitignore" />
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
class ConvertUnixTimeDotComTimeProvider implements ITimeProvider
|
||||
{
|
||||
public function getTime() {
|
||||
$json = @json_decode(
|
||||
@file_get_contents('http://www.convert-unix-time.com/api?timestamp=now&r=' . uniqid(null, true))
|
||||
);
|
||||
if ($json === null || !is_int($json->timestamp))
|
||||
throw new \TimeException('Unable to retrieve time from convert-unix-time.com');
|
||||
return $json->timestamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace RobThree\Auth\Providers\Time;
|
||||
|
||||
/**
|
||||
* Takes the time from any NTP server
|
||||
*/
|
||||
class NTPTimeProvider implements ITimeProvider
|
||||
{
|
||||
public $host;
|
||||
public $port;
|
||||
public $timeout;
|
||||
|
||||
function __construct($host = 'pool.ntp.org', $port = 123, $timeout = 1)
|
||||
{
|
||||
$this->host = $host;
|
||||
|
||||
if (!is_int($port) || $port <= 0 || $port > 65535)
|
||||
throw new \TimeException('Port must be 0 < port < 65535');
|
||||
$this->port = $port;
|
||||
|
||||
if (!is_int($timeout) || $timeout < 0)
|
||||
throw new \TimeException('Timeout must be >= 0');
|
||||
$this->timeout = $timeout;
|
||||
}
|
||||
|
||||
public function getTime() {
|
||||
try {
|
||||
/* Create a socket and connect to NTP server */
|
||||
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
||||
socket_connect($sock, $this->host, $this->port);
|
||||
|
||||
/* Send request */
|
||||
$msg = "\010" . str_repeat("\0", 47);
|
||||
socket_send($sock, $msg, strlen($msg), 0);
|
||||
|
||||
/* Receive response and close socket */
|
||||
socket_recv($sock, $recv, 48, MSG_WAITALL);
|
||||
socket_close($sock);
|
||||
|
||||
/* Interpret response */
|
||||
$data = unpack('N12', $recv);
|
||||
$timestamp = sprintf('%u', $data[9]);
|
||||
|
||||
/* NTP is number of seconds since 0000 UT on 1 January 1900 Unix time is seconds since 0000 UT on 1 January 1970 */
|
||||
return $timestamp - 2208988800;
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
throw new \TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->host, $ex->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,16 +79,17 @@ class TwoFactorAuth
|
||||
/**
|
||||
* Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
|
||||
*/
|
||||
public function verifyCode($secret, $code, $discrepancy = 1, $time = null)
|
||||
public function verifyCode($secret, $code, $discrepancy = 1, $time = null, &$timeslice = 0)
|
||||
{
|
||||
$result = false;
|
||||
$timetamp = $this->getTime($time);
|
||||
|
||||
// To keep safe from timing-attachs we iterate *all* possible codes even though we already may have verified a code is correct
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; $i++)
|
||||
$result |= $this->codeEquals($this->getCode($secret, $timetamp + ($i * $this->period)), $code);
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
|
||||
$ts = $timetamp + ($i * $this->period);
|
||||
$timeslice += $this->codeEquals($this->getCode($secret, $ts), $code) ? $ts : 0;
|
||||
}
|
||||
|
||||
return (bool)$result;
|
||||
return $timeslice > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,7 +135,7 @@ class TwoFactorAuth
|
||||
|
||||
if ($timeproviders == null)
|
||||
$timeproviders = array(
|
||||
new Providers\Time\ConvertUnixTimeDotComTimeProvider(),
|
||||
new Providers\Time\NTPTimeProvider(),
|
||||
new Providers\Time\HttpTimeProvider()
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ require_once 'lib/Providers/Rng/RNGException.php';
|
||||
require_once 'lib/Providers/Time/ITimeProvider.php';
|
||||
require_once 'lib/Providers/Time/LocalMachineTimeProvider.php';
|
||||
require_once 'lib/Providers/Time/HttpTimeProvider.php';
|
||||
require_once 'lib/Providers/Time/ConvertUnixTimeDotComTimeProvider.php';
|
||||
require_once 'lib/Providers/Time/NTPTimeProvider.php';
|
||||
require_once 'lib/Providers/Time/TimeException.php';
|
||||
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
@@ -124,7 +124,8 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
public function testEnsureAllTimeProvidersReturnCorrectTime() {
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
|
||||
$tfa->ensureCorrectTime(array(
|
||||
new RobThree\Auth\Providers\Time\ConvertUnixTimeDotComTimeProvider(),
|
||||
new RobThree\Auth\Providers\Time\NTPTimeProvider(), // Uses pool.ntp.org by default
|
||||
new RobThree\Auth\Providers\Time\NTPTimeProvider('time.windows.com'),
|
||||
new RobThree\Auth\Providers\Time\HttpTimeProvider(), // Uses google.com by default
|
||||
new RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'),
|
||||
new RobThree\Auth\Providers\Time\HttpTimeProvider('https://yahoo.com'),
|
||||
@@ -150,6 +151,27 @@ class TwoFactorAuthTest extends PHPUnit_Framework_TestCase
|
||||
$this->assertEquals(true , $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
|
||||
}
|
||||
|
||||
public function testVerifyCorrectTimeSliceIsReturned() {
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30);
|
||||
|
||||
// We test with discrepancy 3 (so total of 7 codes: c-3, c-2, c-1, c, c+1, c+2, c+3
|
||||
// Ensure each corresponding timeslice is returned correctly
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
|
||||
$this->assertEquals(1426847100, $timeslice1);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
|
||||
$this->assertEquals(1426847130, $timeslice2);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
|
||||
$this->assertEquals(1426847160, $timeslice3);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
|
||||
$this->assertEquals(1426847190, $timeslice4);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
|
||||
$this->assertEquals(1426847220, $timeslice5);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
|
||||
$this->assertEquals(1426847250, $timeslice6);
|
||||
$this->assertEquals(true, $tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
|
||||
$this->assertEquals(1426847280, $timeslice7);
|
||||
}
|
||||
|
||||
public function testTotpUriIsCorrect() {
|
||||
$qr = new TestQrProvider();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user