Skip to content

Commit 194ecc2

Browse files
authored
remove insecure rng providers and remove polyfill for hash_equals (#122)
* remove insecure rng providers and remove the openssl provider. We now rely exclusively on random_bytes(), as there are no reasons not to. Fix #121 * remove the isSecure property of the test rng class * remove pointless test rng class we were testing a test class, which didn't make a lot of sense. * Revert "remove pointless test rng class" This reverts commit f6da6be. * Reapply "remove pointless test rng class" This reverts commit 06220d4. * assing rng provider to class attribute this also aligns with other providers * remove polyfill for hash_equals
1 parent 323053b commit 194ecc2

12 files changed

+12
-279
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ You can make use of the included [Endroid](https://robthree.github.io/TwoFactorA
1919

2020
* Requires PHP version >=8.1
2121
* [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.
22-
* [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.
2322

2423
Optionally, you may need:
2524

docs/optional-configuration.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,7 @@ Argument | Default value | Use
2121

2222
### RNG providers
2323

24-
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.
25-
26-
1. [CSRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/CSRNGProvider.php) for PHP7+
27-
2. [OpenSSLRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/OpenSSLRNGProvider.php) where openssl is available
28-
3. [HashRNGProvider](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/HashRNGProvider.php) **non-cryptographically secure** fallback
29-
30-
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.
31-
32-
You can also implement your own by implementing the [`IRNGProvider` interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/IRNGProvider.php).
24+
Should you feel the need to use a CSPRNG different than `random_bytes()`, you can use the `rngprovider` argument of the constructor to provide an object implementing the [`IRNGProvider`](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/IRNGProvider.php) interface.
3325

3426
### Time providers
3527

lib/Providers/Rng/CSRNGProvider.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,4 @@ public function getRandomBytes(int $bytecount): string
1313
{
1414
return random_bytes($bytecount); // PHP7+
1515
}
16-
17-
/**
18-
* {@inheritdoc}
19-
*/
20-
public function isCryptographicallySecure(): bool
21-
{
22-
return true;
23-
}
2416
}

lib/Providers/Rng/HashRNGProvider.php

Lines changed: 0 additions & 40 deletions
This file was deleted.

lib/Providers/Rng/IRNGProvider.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,4 @@
77
interface IRNGProvider
88
{
99
public function getRandomBytes(int $bytecount): string;
10-
11-
public function isCryptographicallySecure(): bool;
1210
}

lib/Providers/Rng/OpenSSLRNGProvider.php

Lines changed: 0 additions & 29 deletions
This file was deleted.

lib/TwoFactorAuth.php

Lines changed: 5 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44

55
namespace RobThree\Auth;
66

7+
use function hash_equals;
8+
79
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
810
use RobThree\Auth\Providers\Qr\QRServerProvider;
911
use RobThree\Auth\Providers\Rng\CSRNGProvider;
10-
use RobThree\Auth\Providers\Rng\HashRNGProvider;
1112
use RobThree\Auth\Providers\Rng\IRNGProvider;
12-
use RobThree\Auth\Providers\Rng\OpenSSLRNGProvider;
1313
use RobThree\Auth\Providers\Time\HttpTimeProvider;
1414
use RobThree\Auth\Providers\Time\ITimeProvider;
1515
use RobThree\Auth\Providers\Time\LocalMachineTimeProvider;
@@ -51,14 +51,11 @@ public function __construct(
5151
/**
5252
* Create a new secret
5353
*/
54-
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
54+
public function createSecret(int $bits = 80): string
5555
{
5656
$secret = '';
5757
$bytes = (int)ceil($bits / 5); // We use 5 bits of each byte (since we have a 32-character 'alphabet' / BASE32)
5858
$rngprovider = $this->getRngProvider();
59-
if ($requirecryptosecure && !$rngprovider->isCryptographicallySecure()) {
60-
throw new TwoFactorAuthException('RNG provider is not cryptographically secure');
61-
}
6259
$rnd = $rngprovider->getRandomBytes($bytes);
6360
for ($i = 0; $i < $bytes; $i++) {
6461
$secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
@@ -98,7 +95,7 @@ public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?
9895
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
9996
$ts = $timestamp + ($i * $this->period);
10097
$slice = $this->getTimeSlice($ts);
101-
$timeslice = $this->codeEquals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
98+
$timeslice = hash_equals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
10299
}
103100

104101
return $timeslice > 0;
@@ -174,19 +171,7 @@ public function getQrCodeProvider(): IQRCodeProvider
174171
*/
175172
public function getRngProvider(): IRNGProvider
176173
{
177-
if ($this->rngprovider !== null) {
178-
return $this->rngprovider;
179-
}
180-
if (function_exists('random_bytes')) {
181-
return $this->rngprovider = new CSRNGProvider();
182-
}
183-
if (function_exists('openssl_random_pseudo_bytes')) {
184-
return $this->rngprovider = new OpenSSLRNGProvider();
185-
}
186-
if (function_exists('hash')) {
187-
return $this->rngprovider = new HashRNGProvider();
188-
}
189-
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
174+
return $this->rngprovider ??= new CSRNGProvider();
190175
}
191176

192177
public function getTimeProvider(): ITimeProvider
@@ -195,27 +180,6 @@ public function getTimeProvider(): ITimeProvider
195180
return $this->timeprovider ??= new LocalMachineTimeProvider();
196181
}
197182

198-
/**
199-
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
200-
*/
201-
private function codeEquals(string $safe, string $user): bool
202-
{
203-
if (function_exists('hash_equals')) {
204-
return hash_equals($safe, $user);
205-
}
206-
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
207-
// we don't leak information about the difference of the two strings.
208-
if (strlen($safe) === strlen($user)) {
209-
$result = 0;
210-
$strlen = strlen($safe);
211-
for ($i = 0; $i < $strlen; $i++) {
212-
$result |= (ord($safe[$i]) ^ ord($user[$i]));
213-
}
214-
return $result === 0;
215-
}
216-
return false;
217-
}
218-
219183
private function getTime(?int $time = null): int
220184
{
221185
return $time ?? $this->getTimeProvider()->getTime();

tests/Providers/Rng/CSRNGProviderTest.php

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,11 @@ class CSRNGProviderTest extends TestCase
1111
{
1212
use NeedsRngLengths;
1313

14-
/**
15-
* @requires function random_bytes
16-
*/
1714
public function testCSRNGProvidersReturnExpectedNumberOfBytes(): void
1815
{
19-
if (function_exists('random_bytes')) {
20-
$rng = new CSRNGProvider();
21-
foreach ($this->rngTestLengths as $l) {
22-
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
23-
}
24-
$this->assertTrue($rng->isCryptographicallySecure());
25-
} else {
26-
$this->expectNotToPerformAssertions();
16+
$rng = new CSRNGProvider();
17+
foreach ($this->rngTestLengths as $l) {
18+
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
2719
}
2820
}
2921
}

tests/Providers/Rng/HashRNGProviderTest.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

tests/Providers/Rng/IRNGProviderTest.php

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,12 @@
77
use PHPUnit\Framework\TestCase;
88
use RobThree\Auth\Algorithm;
99
use RobThree\Auth\TwoFactorAuth;
10-
use RobThree\Auth\TwoFactorAuthException;
1110

1211
class IRNGProviderTest extends TestCase
1312
{
14-
public function testCreateSecretThrowsOnInsecureRNGProvider(): void
13+
public function testCreateSecret(): void
1514
{
16-
$rng = new TestRNGProvider();
17-
18-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
19-
20-
$this->expectException(TwoFactorAuthException::class);
21-
$tfa->createSecret();
22-
}
23-
24-
public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG(): void
25-
{
26-
$rng = new TestRNGProvider();
27-
28-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
29-
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
30-
}
31-
32-
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
33-
{
34-
$rng = new TestRNGProvider(true);
35-
36-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
37-
$this->assertSame('ABCDEFGHIJKLMNOP', $tfa->createSecret());
38-
}
39-
40-
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
41-
{
42-
$rng = new TestRNGProvider(true);
43-
44-
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
45-
$this->assertSame('A', $tfa->createSecret(5));
46-
$this->assertSame('AB', $tfa->createSecret(6));
47-
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ', $tfa->createSecret(128));
48-
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
49-
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
50-
$this->assertSame('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
15+
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null);
16+
$this->assertIsString($tfa->createSecret());
5117
}
5218
}

0 commit comments

Comments
 (0)