first batch

This commit is contained in:
Nicolas CARPi
2022-12-07 22:28:52 +01:00
parent ba4e8c55ed
commit aeb4b00c60
17 changed files with 106 additions and 401 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
* Tested on PHP 5.6 up to 8.0
* [cURL](http://php.net/manual/en/book.curl.php) when using the provided `QRServerProvider` (default), `ImageChartsQRCodeProvider` or `QRicketProvider` but you can also provide your own QR-code provider.
* [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [MCrypt](http://php.net/manual/en/book.mcrypt.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
* [random_bytes()](http://php.net/manual/en/function.random-bytes.php), [OpenSSL](http://php.net/manual/en/book.openssl.php) or [Hash](http://php.net/manual/en/book.hash.php) depending on which built-in RNG you use (TwoFactorAuth will try to 'autodetect' and use the best available); however: feel free to provide your own (CS)RNG.
Optionally, you may need:
+2 -3
View File
@@ -15,7 +15,7 @@
<PHPDevHostName>localhost</PHPDevHostName>
<IISProjectUrl>http://localhost:41315/</IISProjectUrl>
<Runtime>PHP</Runtime>
<RuntimeVersion>7.0</RuntimeVersion>
<RuntimeVersion>8.1</RuntimeVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<IncludeDebugInformation>true</IncludeDebugInformation>
@@ -34,7 +34,6 @@
<Compile Include="lib\Providers\Qr\QRServerProvider.php" />
<Compile Include="lib\Providers\Rng\CSRNGProvider.php" />
<Compile Include="lib\Providers\Rng\IRNGProvider.php" />
<Compile Include="lib\Providers\Rng\MCryptRNGProvider.php" />
<Compile Include="lib\Providers\Rng\OpenSSLRNGProvider.php" />
<Compile Include="lib\Providers\Rng\HashRNGProvider.php" />
<Compile Include="lib\Providers\Rng\RNGException.php" />
@@ -67,4 +66,4 @@
<Content Include="LICENSE" />
<Content Include="phpunit.xml" />
</ItemGroup>
</Project>
</Project>
+1 -1
View File
@@ -17,7 +17,7 @@
"source": "https://github.com/RobThree/TwoFactorAuth"
},
"require": {
"php": ">=5.6.0"
"php": ">=8.1.0"
},
"require-dev": {
"phpunit/phpunit": "@stable",
+2 -3
View File
@@ -24,9 +24,8 @@ Argument | Default value | Use
This library also comes with some [Random Number Generator (RNG)](https://en.wikipedia.org/wiki/Random_number_generation) providers. The RNG provider generates a number of random bytes and returns these bytes as a string. These values are then used to create the secret. By default (no RNG provider specified) TwoFactorAuth will try to determine the best available RNG provider to use in this order.
1. [CSRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/CSRNGProvider.php) for PHP7+
2. [MCryptRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/MCryptRNGProvider.php) where mcrypt is available
3. [OpenSSLRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/OpenSSLRNGProvider.php) where openssl is available
4. [HashRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/HashRNGProvider.php) **non-cryptographically secure** fallback
2. [OpenSSLRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/OpenSSLRNGProvider.php) where openssl is available
3. [HashRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/HashRNGProvider.php) **non-cryptographically secure** fallback
Each of these RNG providers have some constructor arguments that allow you to tweak some of the settings to use when creating the random bytes.
+2 -2
View File
@@ -7,7 +7,7 @@ class CSRNGProvider implements IRNGProvider
/**
* {@inheritdoc}
*/
public function getRandomBytes($bytecount)
public function getRandomBytes(int $bytecount): string
{
return random_bytes($bytecount); // PHP7+
}
@@ -15,7 +15,7 @@ class CSRNGProvider implements IRNGProvider
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure()
public function isCryptographicallySecure(): bool
{
return true;
}
+6 -11
View File
@@ -2,27 +2,22 @@
namespace RobThree\Auth\Providers\Rng;
use function in_array;
class HashRNGProvider implements IRNGProvider
{
/** @var string */
private $algorithm;
/**
* @param string $algorithm
*/
public function __construct($algorithm = 'sha256')
public function __construct(private string $algorithm = 'sha256')
{
$algos = array_values(hash_algos());
if (!in_array($algorithm, $algos, true)) {
if (!in_array($this->algorithm, $algos, true)) {
throw new RNGException('Unsupported algorithm specified');
}
$this->algorithm = $algorithm;
}
/**
* {@inheritdoc}
*/
public function getRandomBytes($bytecount)
public function getRandomBytes(int $bytecount): string
{
$result = '';
$hash = mt_rand();
@@ -36,7 +31,7 @@ class HashRNGProvider implements IRNGProvider
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure()
public function isCryptographicallySecure(): bool
{
return false;
}
+2 -10
View File
@@ -4,15 +4,7 @@ namespace RobThree\Auth\Providers\Rng;
interface IRNGProvider
{
/**
* @param int $bytecount the number of bytes of randomness to return
*
* @return string the random bytes
*/
public function getRandomBytes($bytecount);
public function getRandomBytes(int $bytecount): string;
/**
* @return bool whether this provider is cryptographically secure
*/
public function isCryptographicallySecure();
public function isCryptographicallySecure(): bool;
}
-37
View File
@@ -1,37 +0,0 @@
<?php
namespace RobThree\Auth\Providers\Rng;
class MCryptRNGProvider implements IRNGProvider
{
/** @var int */
private $source;
/**
* @param int $source
*/
public function __construct($source = MCRYPT_DEV_URANDOM)
{
$this->source = $source;
}
/**
* {@inheritdoc}
*/
public function getRandomBytes($bytecount)
{
$result = @mcrypt_create_iv($bytecount, $this->source);
if ($result === false) {
throw new RNGException('mcrypt_create_iv returned an invalid value');
}
return $result;
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure()
{
return true;
}
}
+5 -18
View File
@@ -4,36 +4,23 @@ namespace RobThree\Auth\Providers\Rng;
class OpenSSLRNGProvider implements IRNGProvider
{
/** @var bool */
private $requirestrong;
/**
* @param bool $requirestrong
*/
public function __construct($requirestrong = true)
public function __construct(private bool $requirestrong = true)
{
$this->requirestrong = $requirestrong;
}
/**
* {@inheritdoc}
*/
public function getRandomBytes($bytecount)
public function getRandomBytes(int $bytecount): string
{
$result = openssl_random_pseudo_bytes($bytecount, $crypto_strong);
if ($this->requirestrong && ($crypto_strong === false)) {
throw new RNGException('openssl_random_pseudo_bytes returned non-cryptographically strong value');
}
if ($result === false) {
throw new RNGException('openssl_random_pseudo_bytes returned an invalid value');
}
return $result;
// will throw an Exception on failure
return openssl_random_pseudo_bytes($bytecount, $crypto_strong);
}
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure()
public function isCryptographicallySecure(): bool
{
return $this->requirestrong;
}
+7 -22
View File
@@ -2,38 +2,23 @@
namespace RobThree\Auth\Providers\Time;
use function socket_create;
use Exception;
/**
* Takes the time from any NTP server
*/
class NTPTimeProvider implements ITimeProvider
{
/** @var string */
public $host;
/** @var int */
public $port;
/** @var int */
public $timeout;
/**
* @param string $host
* @param int $port
* @param int $timeout
*/
public function __construct($host = 'time.google.com', $port = 123, $timeout = 1)
public function __construct(public string $host = 'time.google.com', public int $port = 123, public int $timeout = 1)
{
$this->host = $host;
if (!is_int($port) || $port <= 0 || $port > 65535) {
if ($this->port <= 0 || $this->port > 65535) {
throw new TimeException('Port must be 0 < port < 65535');
}
$this->port = $port;
if (!is_int($timeout) || $timeout < 0) {
if ($this->timeout < 0) {
throw new TimeException('Timeout must be >= 0');
}
$this->timeout = $timeout;
}
/**
@@ -63,7 +48,7 @@ class NTPTimeProvider implements ITimeProvider
/* 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) {
} catch (Exception $ex) {
throw new TimeException(sprintf('Unable to retrieve time from %s (%s)', $this->host, $ex->getMessage()));
}
}
+34 -135
View File
@@ -7,7 +7,6 @@ use RobThree\Auth\Providers\Qr\QRServerProvider;
use RobThree\Auth\Providers\Rng\CSRNGProvider;
use RobThree\Auth\Providers\Rng\HashRNGProvider;
use RobThree\Auth\Providers\Rng\IRNGProvider;
use RobThree\Auth\Providers\Rng\MCryptRNGProvider;
use RobThree\Auth\Providers\Rng\OpenSSLRNGProvider;
use RobThree\Auth\Providers\Time\HttpTimeProvider;
use RobThree\Auth\Providers\Time\ITimeProvider;
@@ -18,69 +17,28 @@ use RobThree\Auth\Providers\Time\NTPTimeProvider;
// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
class TwoFactorAuth
{
/** @var string */
private $algorithm;
private static string $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
/** @var int */
private $period;
private static array $_base32;
/** @var int */
private $digits;
private static array $_base32lookup = array();
/** @var string */
private $issuer;
/** @var ?IQRCodeProvider */
private $qrcodeprovider = null;
/** @var ?IRNGProvider */
private $rngprovider = null;
/** @var ?ITimeProvider */
private $timeprovider = null;
/** @var string */
private static $_base32dict = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
/** @var array */
private static $_base32;
/** @var array */
private static $_base32lookup = array();
/** @var array */
private static $_supportedalgos = array('sha1', 'sha256', 'sha512', 'md5');
/**
* @param ?string $issuer
* @param int $digits
* @param int $period
* @param string $algorithm
* @param ?IQRCodeProvider $qrcodeprovider
* @param ?IRNGProvider $rngprovider
* @param ?ITimeProvider $timeprovider
*/
public function __construct($issuer = null, $digits = 6, $period = 30, $algorithm = 'sha1', IQRCodeProvider $qrcodeprovider = null, IRNGProvider $rngprovider = null, ITimeProvider $timeprovider = null)
{
$this->issuer = $issuer;
if (!is_int($digits) || $digits <= 0) {
throw new TwoFactorAuthException('Digits must be int > 0');
public function __construct(
private ?string $issuer = null,
private int $digits = 6,
private int $period = 30,
private Algorithm $algorithm = Algorithm::Sha1,
private ?IQRCodeProvider $qrcodeprovider = null,
private ?IRNGProvider $rngprovider = null,
private ?ITimeProvider $timeprovider = null
) {
if ($this->digits <= 0) {
throw new TwoFactorAuthException('Digits must be > 0');
}
$this->digits = $digits;
if (!is_int($period) || $period <= 0) {
if ($this->period <= 0) {
throw new TwoFactorAuthException('Period must be int > 0');
}
$this->period = $period;
$algorithm = strtolower(trim($algorithm));
if (!in_array($algorithm, self::$_supportedalgos)) {
throw new TwoFactorAuthException('Unsupported algorithm: ' . $algorithm);
}
$this->algorithm = $algorithm;
$this->qrcodeprovider = $qrcodeprovider;
$this->rngprovider = $rngprovider;
$this->timeprovider = $timeprovider;
self::$_base32 = str_split(self::$_base32dict);
self::$_base32lookup = array_flip(self::$_base32);
@@ -88,16 +46,11 @@ class TwoFactorAuth
/**
* Create a new secret
*
* @param int $bits
* @param bool $requirecryptosecure
*
* @return string
*/
public function createSecret($bits = 80, $requirecryptosecure = true)
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
{
$secret = '';
$bytes = (int) ceil($bits / 5); //We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
$bytes = (int) ceil($bits / 5); // We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
$rngprovider = $this->getRngProvider();
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure()) {
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
@@ -111,18 +64,13 @@ class TwoFactorAuth
/**
* Calculate the code with given secret and point in time
*
* @param string $secret
* @param ?int $time
*
* @return string
*/
public function getCode($secret, $time = null)
public function getCode(string $secret, ?int $time = null): string
{
$secretkey = $this->base32Decode($secret);
$timestamp = "\0\0\0\0" . pack('N*', $this->getTimeSlice($this->getTime($time))); // Pack time into binary string
$hashhmac = hash_hmac($this->algorithm, $timestamp, $secretkey, true); // Hash it with users secret key
$hashhmac = hash_hmac($this->algorithm->value, $timestamp, $secretkey, true); // Hash it with users secret key
$hashpart = substr($hashhmac, ord(substr($hashhmac, -1)) & 0x0F, 4); // Use last nibble of result as index/offset and grab 4 bytes of the result
$value = unpack('N', $hashpart); // Unpack binary value
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
@@ -132,16 +80,8 @@ class TwoFactorAuth
/**
* Check if the code is correct. This will accept codes starting from ($discrepancy * $period) sec ago to ($discrepancy * period) sec from now
*
* @param string $secret
* @param string $code
* @param int $discrepancy
* @param ?int $time
* @param int $timeslice
*
* @return bool
*/
public function verifyCode($secret, $code, $discrepancy = 1, $time = null, &$timeslice = 0)
public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?int $time = null, ?int &$timeslice = 0): bool
{
$timestamp = $this->getTime($time);
@@ -162,13 +102,8 @@ class TwoFactorAuth
/**
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
*
* @param string $safe
* @param string $user
*
* @return bool
*/
private function codeEquals($safe, $user)
private function codeEquals(string $safe, string $user): bool
{
if (function_exists('hash_equals')) {
return hash_equals($safe, $user);
@@ -187,17 +122,11 @@ class TwoFactorAuth
/**
* Get data-uri of QRCode
*
* @param string $label
* @param string $secret
* @param mixed $size
*
* @return string
*/
public function getQRCodeImageAsDataUri($label, $secret, $size = 200)
public function getQRCodeImageAsDataUri(string $label, string $secret, int $size = 200): string
{
if (!is_int($size) || $size <= 0) {
throw new TwoFactorAuthException('Size must be int > 0');
if ($size <= 0) {
throw new TwoFactorAuthException('Size must be > 0');
}
$qrcodeprovider = $this->getQrCodeProvider();
@@ -209,12 +138,8 @@ class TwoFactorAuth
/**
* Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
* @param ?array $timeproviders
* @param int $leniency
*
* @return void
*/
public function ensureCorrectTime(array $timeproviders = null, $leniency = 5)
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
{
if ($timeproviders === null) {
$timeproviders = array(
@@ -239,50 +164,30 @@ class TwoFactorAuth
}
}
/**
* @param ?int $time
*
* @return int
*/
private function getTime($time = null)
private function getTime(?int $time = null): int
{
return ($time === null) ? $this->getTimeProvider()->getTime() : $time;
}
/**
* @param int $time
* @param int $offset
*
* @return int
*/
private function getTimeSlice($time = null, $offset = 0)
private function getTimeSlice(?int $time = null, int $offset = 0): int
{
return (int)floor($time / $this->period) + ($offset * $this->period);
return (int) floor($time / $this->period) + ($offset * $this->period);
}
/**
* Builds a string to be encoded in a QR code
*
* @param string $label
* @param string $secret
*
* @return string
*/
public function getQRText($label, $secret)
public function getQRText(string $label, string $secret): string
{
return 'otpauth://totp/' . rawurlencode($label)
. '?secret=' . rawurlencode($secret)
. '&issuer=' . rawurlencode((string)$this->issuer)
. '&period=' . intval($this->period)
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm))
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
. '&digits=' . intval($this->digits);
}
/**
* @param string $value
* @return string
*/
private function base32Decode($value)
private function base32Decode(string $value): string
{
if (strlen($value) == 0) {
return '';
@@ -309,10 +214,9 @@ class TwoFactorAuth
}
/**
* @return IQRCodeProvider
* @throws TwoFactorAuthException
*/
public function getQrCodeProvider()
public function getQrCodeProvider(): IQRCodeProvider
{
// Set default QR Code provider if none was specified
if (null === $this->qrcodeprovider) {
@@ -322,10 +226,9 @@ class TwoFactorAuth
}
/**
* @return IRNGProvider
* @throws TwoFactorAuthException
*/
public function getRngProvider()
public function getRngProvider(): IRNGProvider
{
if (null !== $this->rngprovider) {
return $this->rngprovider;
@@ -333,9 +236,6 @@ class TwoFactorAuth
if (function_exists('random_bytes')) {
return $this->rngprovider = new CSRNGProvider();
}
if (function_exists('mcrypt_create_iv')) {
return $this->rngprovider = new MCryptRNGProvider();
}
if (function_exists('openssl_random_pseudo_bytes')) {
return $this->rngprovider = new OpenSSLRNGProvider();
}
@@ -346,10 +246,9 @@ class TwoFactorAuth
}
/**
* @return ITimeProvider
* @throws TwoFactorAuthException
*/
public function getTimeProvider()
public function getTimeProvider(): ITimeProvider
{
// Set default time provider if none was specified
if (null === $this->timeprovider) {
+7 -15
View File
@@ -6,29 +6,24 @@ use PHPUnit\Framework\TestCase;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use RobThree\Auth\Providers\Qr\HandlesDataUri;
use RobThree\Auth\Algorithm;
class IQRCodeProviderTest extends TestCase
{
use HandlesDataUri;
/**
* @return void
*/
public function testTotpUriIsCorrect()
public function testTotpUriIsCorrect(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, 'sha1', $qr);
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertEquals('test/test', $data['mimetype']);
$this->assertEquals('base64', $data['encoding']);
$this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
}
/**
* @return void
*/
public function testTotpUriIsCorrectNoIssuer()
public function testTotpUriIsCorrectNoIssuer(): void
{
$qr = new TestQrProvider();
@@ -37,21 +32,18 @@ class IQRCodeProviderTest extends TestCase
* there is a deprecation warning for passing null as a string argument to rawurlencode
*/
$tfa = new TwoFactorAuth(null, 6, 30, 'sha1', $qr);
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
$this->assertEquals('test/test', $data['mimetype']);
$this->assertEquals('base64', $data['encoding']);
$this->assertEquals('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
}
/**
* @return void
*/
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize()
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
{
$qr = new TestQrProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', $qr);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, $qr);
$this->expectException(TwoFactorAuthException::class);
+9 -20
View File
@@ -5,52 +5,41 @@ namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\TwoFactorAuthException;
use RobThree\Auth\Algorithm;
class IRNGProviderTest extends TestCase
{
/**
* @return void
*/
public function testCreateSecretThrowsOnInsecureRNGProvider()
public function testCreateSecretThrowsOnInsecureRNGProvider(): void
{
$rng = new TestRNGProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->expectException(TwoFactorAuthException::class);
$tfa->createSecret();
}
/**
* @return void
*/
public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG()
public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG(): void
{
$rng = new TestRNGProvider();
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
}
/**
* @return void
*/
public function testCreateSecretDoesNotThrowOnSecureRNGProvider()
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
{
$rng = new TestRNGProvider(true);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret());
}
/**
* @return void
*/
public function testCreateSecretGeneratesDesiredAmountOfEntropy()
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
{
$rng = new TestRNGProvider(true);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, $rng);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
$this->assertEquals('A', $tfa->createSecret(5));
$this->assertEquals('AB', $tfa->createSecret(6));
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
@@ -1,32 +0,0 @@
<?php
namespace Tests\Providers\Rng;
use PHPUnit\Framework\TestCase;
use Tests\MightNotMakeAssertions;
use RobThree\Auth\Providers\Rng\MCryptRNGProvider;
class MCryptRNGProviderTest extends TestCase
{
use NeedsRngLengths, MightNotMakeAssertions;
/**
* @requires function mcrypt_create_iv
*
* @return void
*/
public function testMCryptRNGProvidersReturnExpectedNumberOfBytes()
{
if (function_exists('mcrypt_create_iv')) {
$rng = new MCryptRNGProvider();
foreach ($this->rngTestLengths as $l) {
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
}
$this->assertTrue($rng->isCryptographicallySecure());
} else {
$this->noAssertionsMade();
}
}
}
+3 -10
View File
@@ -6,21 +6,14 @@ use RobThree\Auth\Providers\Rng\IRNGProvider;
class TestRNGProvider implements IRNGProvider
{
/** @var bool */
private $isSecure;
/**
* @param bool $isSecure whether this provider is cryptographically secure
*/
function __construct($isSecure = false)
function __construct(private bool $isSecure = false)
{
$this->isSecure = $isSecure;
}
/**
* {@inheritdoc}
*/
public function getRandomBytes($bytecount)
public function getRandomBytes(int $bytecount): string
{
$result = '';
@@ -34,7 +27,7 @@ class TestRNGProvider implements IRNGProvider
/**
* {@inheritdoc}
*/
public function isCryptographicallySecure()
public function isCryptographicallySecure(): bool
{
return $this->isSecure;
}
+7 -15
View File
@@ -6,46 +6,38 @@ use PHPUnit\Framework\TestCase;
use Tests\MightNotMakeAssertions;
use RobThree\Auth\TwoFactorAuthException;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Algorithm;
class ITimeProviderTest extends TestCase
{
use MightNotMakeAssertions;
/**
* @return void
*/
public function testEnsureCorrectTimeDoesNotThrowForCorrectTime()
public function testEnsureCorrectTimeDoesNotThrowForCorrectTime(): void
{
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(128);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
$this->noAssertionsMade();
}
/**
* @return void
*/
public function testEnsureCorrectTimeThrowsOnIncorrectTime()
public function testEnsureCorrectTimeThrowsOnIncorrectTime(): void
{
$tpr1 = new TestTimeProvider(123);
$tpr2 = new TestTimeProvider(124);
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1', null, null, $tpr1);
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
$this->expectException(TwoFactorAuthException::class);
$tfa->ensureCorrectTime(array($tpr2), 0); // We force a leniency of 0, 124-123 = 1 so this should throw
}
/**
* @return void
*/
public function testEnsureDefaultTimeProviderReturnsCorrectTime()
public function testEnsureDefaultTimeProviderReturnsCorrectTime(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(new TestTimeProvider(time())), 1); // Use a leniency of 1, should the time change between both time() calls
$this->noAssertionsMade();
+18 -66
View File
@@ -3,6 +3,7 @@
namespace Tests;
use PHPUnit\Framework\TestCase;
use RobThree\Auth\Algorithm;
use RobThree\Auth\TwoFactorAuthException;
use RobThree\Auth\TwoFactorAuth;
@@ -10,52 +11,30 @@ class TwoFactorAuthTest extends TestCase
{
use MightNotMakeAssertions;
/**
* @return void
*/
public function testConstructorThrowsOnInvalidDigits()
public function testConstructorThrowsOnInvalidDigits(): void
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 0);
}
/**
* @return void
*/
public function testConstructorThrowsOnInvalidPeriod()
public function testConstructorThrowsOnInvalidPeriod(): void
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 6, 0);
}
/**
* @return void
*/
public function testConstructorThrowsOnInvalidAlgorithm()
{
$this->expectException(TwoFactorAuthException::class);
new TwoFactorAuth('Test', 6, 30, 'xxx');
}
/**
* @return void
*/
public function testGetCodeReturnsCorrectResults()
public function testGetCodeReturnsCorrectResults(): void
{
$tfa = new TwoFactorAuth('Test');
$this->assertEquals('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
$this->assertEquals('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
}
/**
* @return void
*/
public function testEnsureAllTimeProvidersReturnCorrectTime()
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30, 'sha1');
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
$tfa->ensureCorrectTime(array(
new \RobThree\Auth\Providers\Time\NTPTimeProvider(), // Uses pool.ntp.org by default
//new \RobThree\Auth\Providers\Time\NTPTimeProvider('time.google.com'), // Somehow time.google.com and time.windows.com make travis timeout??
@@ -66,10 +45,7 @@ class TwoFactorAuthTest extends TestCase
$this->noAssertionsMade();
}
/**
* @return void
*/
public function testVerifyCodeWorksCorrectly()
public function testVerifyCodeWorksCorrectly(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
@@ -88,10 +64,7 @@ class TwoFactorAuthTest extends TestCase
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
}
/**
* @return void
*/
public function testVerifyCorrectTimeSliceIsReturned()
public function testVerifyCorrectTimeSliceIsReturned(): void
{
$tfa = new TwoFactorAuth('Test', 6, 30);
@@ -117,10 +90,7 @@ class TwoFactorAuthTest extends TestCase
$this->assertEquals(0, $timeslice8);
}
/**
* @return void
*/
public function testGetCodeThrowsOnInvalidBase32String1()
public function testGetCodeThrowsOnInvalidBase32String1(): void
{
$tfa = new TwoFactorAuth('Test');
@@ -129,10 +99,7 @@ class TwoFactorAuthTest extends TestCase
$tfa->getCode('FOO1BAR8BAZ9'); //1, 8 & 9 are invalid chars
}
/**
* @return void
*/
public function testGetCodeThrowsOnInvalidBase32String2()
public function testGetCodeThrowsOnInvalidBase32String2(): void
{
$tfa = new TwoFactorAuth('Test');
@@ -141,10 +108,7 @@ class TwoFactorAuthTest extends TestCase
$tfa->getCode('mzxw6==='); //Lowercase
}
/**
* @return void
*/
public function testKnownBase32DecodeTestVectors()
public function testKnownBase32DecodeTestVectors(): void
{
// We usually don't test internals (e.g. privates) but since we rely heavily on base32 decoding and don't want
// to expose this method nor do we want to give people the possibility of implementing / providing their own base32
@@ -172,10 +136,7 @@ class TwoFactorAuthTest extends TestCase
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
}
/**
* @return void
*/
public function testKnownBase32DecodeUnpaddedTestVectors()
public function testKnownBase32DecodeUnpaddedTestVectors(): void
{
// See testKnownBase32DecodeTestVectors() for the rationale behind testing the private base32Decode() method.
// This test ensures that strings without the padding-char ('=') are also decoded correctly.
@@ -196,14 +157,11 @@ class TwoFactorAuthTest extends TestCase
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
}
/**
* @return void
*/
public function testKnownTestVectors_sha1()
public function testKnownTestVectors_sha1(): void
{
//Known test vectors for SHA1: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ'; //== base32encode('12345678901234567890')
$tfa = new TwoFactorAuth('Test', 8, 30, 'sha1');
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha1);
$this->assertEquals('94287082', $tfa->getCode($secret, 59));
$this->assertEquals('07081804', $tfa->getCode($secret, 1111111109));
$this->assertEquals('14050471', $tfa->getCode($secret, 1111111111));
@@ -212,14 +170,11 @@ class TwoFactorAuthTest extends TestCase
$this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
}
/**
* @return void
*/
public function testKnownTestVectors_sha256()
public function testKnownTestVectors_sha256(): void
{
//Known test vectors for SHA256: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZA'; //== base32encode('12345678901234567890123456789012')
$tfa = new TwoFactorAuth('Test', 8, 30, 'sha256');
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha256);
$this->assertEquals('46119246', $tfa->getCode($secret, 59));
$this->assertEquals('68084774', $tfa->getCode($secret, 1111111109));
$this->assertEquals('67062674', $tfa->getCode($secret, 1111111111));
@@ -228,14 +183,11 @@ class TwoFactorAuthTest extends TestCase
$this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
}
/**
* @return void
*/
public function testKnownTestVectors_sha512()
public function testKnownTestVectors_sha512(): void
{
//Known test vectors for SHA512: https://tools.ietf.org/html/rfc6238#page-15
$secret = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQGEZDGNA'; //== base32encode('1234567890123456789012345678901234567890123456789012345678901234')
$tfa = new TwoFactorAuth('Test', 8, 30, 'sha512');
$tfa = new TwoFactorAuth('Test', 8, 30, Algorithm::Sha512);
$this->assertEquals('90693936', $tfa->getCode($secret, 59));
$this->assertEquals('25091201', $tfa->getCode($secret, 1111111109));
$this->assertEquals('99943326', $tfa->getCode($secret, 1111111111));