mirror of
https://github.com/RobThree/TwoFactorAuth.git
synced 2026-08-20 12:22:44 +00:00
Compare commits
95 Commits
v2.0.0-RC1
...
v3.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 92487acba4 | |||
| ab4c33007f | |||
| 0ab012bbaf | |||
| ec82d397ee | |||
| fc3adc7245 | |||
| 337e96b7af | |||
| 91c091c316 | |||
| 9e8b31ebbc | |||
| df43660714 | |||
| 6194bb08a7 | |||
| 6f78141196 | |||
| f5eb9a7051 | |||
| 5c97ce9be8 | |||
| ab677e43ee | |||
| eb02cb7095 | |||
| c15668dc6d | |||
| f16ea16e1b | |||
| f35f2ae51a | |||
| cd27effc02 | |||
| 2ef7b8e2c6 | |||
| b964b2abbf | |||
| 8007233058 | |||
| 22d45f2ab4 | |||
| 2080319f6f | |||
| cabcf5d493 | |||
| 8477141a36 | |||
| 028867dec2 | |||
| dfc1124f96 | |||
| d4a5026d86 | |||
| bb6802c093 | |||
| b909cb38f6 | |||
| e08b0a5445 | |||
| ecef270ba7 | |||
| 194ecc28eb | |||
| 323053bd52 | |||
| 83c7449270 | |||
| 086a3758ec | |||
| bba4c207c2 | |||
| 061a2a39b4 | |||
| 76f420e6b2 | |||
| 319a58a001 | |||
| d996779182 | |||
| ab93dd41ce | |||
| ec35073c06 | |||
| 13a56018f4 | |||
| 4b6b0601b7 | |||
| 126afe4c25 | |||
| 63f6259db2 | |||
| 137df4dd3c | |||
| 9476751d6b | |||
| 0565d63e6a | |||
| d5aa22a96f | |||
| a6f7735b9f | |||
| 6416e79c3e | |||
| 83611592db | |||
| dd0ecaeed1 | |||
| 241dfec585 | |||
| 63b49ce25c | |||
| e554a9b5e4 | |||
| 9bc454f425 | |||
| c2183e16b3 | |||
| cf84371dad | |||
| 4d91d6ae27 | |||
| d60113a77a | |||
| 3f3327e2a1 | |||
| 8bf962ecee | |||
| b80431341e | |||
| f542788673 | |||
| ab51d16de7 | |||
| d4f6aa1ee6 | |||
| 04ff3e7e3f | |||
| d94ee9c769 | |||
| 955ce522f0 | |||
| e584f0c56a | |||
| 5949a29b89 | |||
| 8bf8800523 | |||
| 2fd60fad5e | |||
| 26413e69f0 | |||
| 3adb3e54e5 | |||
| c45f7a23c9 | |||
| bdfa67d1f6 | |||
| 7675282f45 | |||
| 1ca305ec4a | |||
| aadc32e85e | |||
| 9a1aeb8c1f | |||
| 4c8a88224a | |||
| 9523b35d34 | |||
| cc9400d901 | |||
| dfb5c1a113 | |||
| 098dce6735 | |||
| ae1cc5e8af | |||
| 6e71f43bb1 | |||
| 5968be2cee | |||
| 04e26c9961 | |||
| 8391c5efb0 |
@@ -0,0 +1,12 @@
|
||||
/.github/ export-ignore
|
||||
/demo/ export-ignore
|
||||
/docs/ export-ignore
|
||||
/tests/ export-ignore
|
||||
/testsDependency/ export-ignore
|
||||
/.gitattributes export-ignore
|
||||
/.gitignore export-ignore
|
||||
/.php-cs-fixer.dist.php export-ignore
|
||||
/logo.png export-ignore
|
||||
/multifactorauthforeveryone.png export-ignore
|
||||
/phpstan.neon export-ignore
|
||||
/phpunit.xml export-ignore
|
||||
@@ -0,0 +1 @@
|
||||
Don't be a jerk!
|
||||
@@ -10,10 +10,11 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
php-version: ['8.2', '8.3']
|
||||
bacon-version: ['^2', '^3']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
@@ -22,9 +23,9 @@ jobs:
|
||||
coverage: xdebug
|
||||
ini-values: error_reporting=E_ALL
|
||||
|
||||
- uses: ramsey/composer-install@v2
|
||||
- uses: ramsey/composer-install@v3
|
||||
|
||||
- run: composer require bacon/bacon-qr-code
|
||||
- run: composer require bacon/bacon-qr-code:${{ matrix.bacon-version }}
|
||||
|
||||
- run: composer lint-ci
|
||||
- run: composer test testsDependency/BaconQRCodeTest.php
|
||||
|
||||
@@ -10,11 +10,11 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
endroid-version: ["^4"]
|
||||
php-version: ['8.2', '8.3']
|
||||
endroid-version: ["^3","^4","^5","^6"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
@@ -23,9 +23,9 @@ jobs:
|
||||
coverage: xdebug
|
||||
ini-values: error_reporting=E_ALL
|
||||
|
||||
- uses: ramsey/composer-install@v2
|
||||
- uses: ramsey/composer-install@v3
|
||||
|
||||
- run: composer require endroid/qrcode:${{ matrix.endroid-version }}
|
||||
- run: composer require endroid/qrcode:${{ matrix.endroid-version }} -W
|
||||
|
||||
- run: composer lint-ci
|
||||
- run: composer test testsDependency/EndroidQRCodeTest.php
|
||||
|
||||
@@ -10,10 +10,10 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['8.1', '8.2']
|
||||
php-version: ['8.2', '8.3']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
coverage: xdebug
|
||||
ini-values: error_reporting=E_ALL
|
||||
|
||||
- uses: ramsey/composer-install@v2
|
||||
- uses: ramsey/composer-install@v3
|
||||
|
||||
- run: composer lint-ci
|
||||
- run: composer phpstan
|
||||
|
||||
@@ -39,7 +39,6 @@ return $config->setRules(array(
|
||||
'semicolon_after_instruction' => true,
|
||||
'short_scalar_cast' => true,
|
||||
'simplified_null_return' => true,
|
||||
'single_blank_line_before_namespace' => true,
|
||||
'single_class_element_per_statement' => true,
|
||||
'single_line_comment_style' => true,
|
||||
'single_quote' => true,
|
||||
|
||||
@@ -1,5 +1,44 @@
|
||||
# RobThree\TwoFactorAuth changelog
|
||||
|
||||
# Version 3.x
|
||||
|
||||
## Breaking changes
|
||||
|
||||
### PHP Version
|
||||
|
||||
Version 3.x requires at least PHP 8.2.
|
||||
|
||||
### Constructor signature change
|
||||
|
||||
In order to ensure users of this library make a conscious choice of QR Code Provider, the QR Code Provider is now a mandatory argument, in first place.
|
||||
|
||||
If you didn't provide one explicitly before, you can get the old behavior with:
|
||||
|
||||
~~~php
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\Providers\Qr\QRServerProvider;
|
||||
$tfa = new TwoFactorAuth(new QRServerProvider());
|
||||
~~~
|
||||
|
||||
If you provided one before, the order of the parameters have been changed, so simply move the QRCodeProvider argument to the first place or use named arguments.
|
||||
|
||||
Documentation on selecting a QR Code Provider is available here: [QR Code Provider documentation](https://robthree.github.io/TwoFactorAuth/qr-codes.html).
|
||||
|
||||
### Default secret length
|
||||
|
||||
The default secret length has been increased from 80 bits to 160 bits (RFC4226) PR [#117](https://github.com/RobThree/TwoFactorAuth/pull/117). This might cause an issue in your application if you were previously storing secrets in a column with restricted size. This change doesn't impact existing secrets, only new ones will get longer.
|
||||
|
||||
Previously a secret was 16 characters, now it needs to be stored in a 32 characters width column.
|
||||
|
||||
You can keep the old behavior by setting `80` as argument to `createSecret()` (not recommended, see [#117](https://github.com/RobThree/TwoFactorAuth/pull/117) for further discussion).
|
||||
|
||||
## Other changes
|
||||
|
||||
* The new PHP attribute [SensitiveParameter](https://www.php.net/manual/en/class.sensitiveparameter.php) was added to the code, to prevent accidental leak of secrets in stack traces.
|
||||
* Likely not breaking anything, but now all external QR Code providers use HTTPS with a verified certificate. PR [#126](https://github.com/RobThree/TwoFactorAuth/pull/126).
|
||||
* The CSPRNG is now exclusively using `random_bytes()` PHP function. Previously a fallback to `openssl` or non cryptographically secure PRNG existed, they have been removed. PR [#122](https://github.com/RobThree/TwoFactorAuth/pull/122).
|
||||
* If an external QR code provider is used and the HTTP request results in an error, it will throw a `QRException`. Previously the error was ignored. PR [#130](https://github.com/RobThree/TwoFactorAuth/pull/130), fixes [#129](https://github.com/RobThree/TwoFactorAuth/issues/129).
|
||||
|
||||
# Version 2.x
|
||||
|
||||
## Breaking changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#  PHP library for Two Factor Authentication
|
||||
|
||||
[](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [](https://packagist.org/packages/robthree/twofactorauth) [](LICENSE) [](https://packagist.org/packages/robthree/twofactorauth) [](https://codeclimate.com/github/RobThree/TwoFactorAuth) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
|
||||
[](https://github.com/RobThree/TwoFactorAuth/actions?query=branch%3Amaster) [](https://packagist.org/packages/robthree/twofactorauth) [](LICENSE) [](https://packagist.org/packages/robthree/twofactorauth) [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=6MB5M2SQLP636 "Keep me off the streets")
|
||||
|
||||
PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) using [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) and [QR-codes](http://en.wikipedia.org/wiki/QR_code). Inspired by, based on but most importantly an *improvement* on '[PHPGangsta/GoogleAuthenticator](https://github.com/PHPGangsta/GoogleAuthenticator)'. There's a [.Net implementation](https://github.com/RobThree/TwoFactorAuth.Net) of this library as well.
|
||||
|
||||
@@ -10,15 +10,14 @@ PHP library for [two-factor (or multi-factor) authentication](http://en.wikipedi
|
||||
|
||||
## Requirements
|
||||
|
||||
* Requires PHP version >=8.1
|
||||
* [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), [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.
|
||||
* Requires PHP version >=8.2
|
||||
|
||||
Optionally, you may need:
|
||||
|
||||
* [sockets](https://www.php.net/manual/en/book.sockets.php) if you are using `NTPTimeProvider`
|
||||
* [endroid/qr-code](https://github.com/endroid/qr-code) if using `EndroidQrCodeProvider` or `EndroidQrCodeWithLogoProvider`.
|
||||
* [bacon/bacon-qr-code](https://github.com/Bacon/BaconQrCode) if using `BaconQrCodeProvider`.
|
||||
* [php-curl library](http://php.net/manual/en/book.curl.php) when using an external QR Code provider such as `QRServerProvider`, `ImageChartsQRCodeProvider`, `QRicketProvider` or any other custom provider connecting to an external service.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -35,6 +34,7 @@ If you need more in-depth information about the configuration available then you
|
||||
## Integrations
|
||||
|
||||
- [CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)
|
||||
- [CI4-Auth: a user, group, role and permission management library for Codeigniter 4](https://github.com/glewe/ci4-auth)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Name>TwoFactorAuth</Name>
|
||||
<ProjectGuid>{e569f53a-a604-4579-91ce-4e35b27da47b}</ProjectGuid>
|
||||
<RootNamespace>TwoFactorAuth</RootNamespace>
|
||||
<OutputType>Library</OutputType>
|
||||
<ProjectTypeGuids>{A0786B88-2ADB-4C21-ABE8-AA2D79766269}</ProjectTypeGuids>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
<Server>PHPDev</Server>
|
||||
<PublishEvent>None</PublishEvent>
|
||||
<PHPDevAutoPort>True</PHPDevAutoPort>
|
||||
<PHPDevPort>41315</PHPDevPort>
|
||||
<PHPDevHostName>localhost</PHPDevHostName>
|
||||
<IISProjectUrl>http://localhost:41315/</IISProjectUrl>
|
||||
<Runtime>PHP</Runtime>
|
||||
<RuntimeVersion>8.1</RuntimeVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<IncludeDebugInformation>true</IncludeDebugInformation>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<IncludeDebugInformation>false</IncludeDebugInformation>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="demo\demo.php" />
|
||||
<Compile Include="demo\loader.php" />
|
||||
<Compile Include="lib\Providers\Qr\BaseHTTPQRCodeProvider.php" />
|
||||
<Compile Include="lib\Providers\Qr\ImageChartsQRCodeProvider.php" />
|
||||
<Compile Include="lib\Providers\Qr\IQRCodeProvider.php" />
|
||||
<Compile Include="lib\Providers\Qr\QRException.php" />
|
||||
<Compile Include="lib\Providers\Qr\QRicketProvider.php" />
|
||||
<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\OpenSSLRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\HashRNGProvider.php" />
|
||||
<Compile Include="lib\Providers\Rng\RNGException.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" />
|
||||
<Compile Include="README.md" />
|
||||
<Compile Include="lib\TwoFactorAuthException.php" />
|
||||
<Compile Include="tests\TwoFactorAuthTest.php" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="lib\" />
|
||||
<Folder Include="lib\Providers\" />
|
||||
<Folder Include="lib\Providers\Time\" />
|
||||
<Folder Include="lib\Providers\Qr\" />
|
||||
<Folder Include="lib\Providers\Rng\" />
|
||||
<Folder Include="demo\" />
|
||||
<Folder Include="tests\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include=".travis.yml" />
|
||||
<Content Include="composer.json" />
|
||||
<Content Include="composer.lock" />
|
||||
<Content Include="logo.png" />
|
||||
<Content Include="multifactorauthforeveryone.png" />
|
||||
<Content Include="LICENSE" />
|
||||
<Content Include="phpunit.xml" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,22 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.30723.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{A0786B88-2ADB-4C21-ABE8-AA2D79766269}") = "TwoFactorAuth", "TwoFactorAuth.phpproj", "{E569F53A-A604-4579-91CE-4E35B27DA47B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E569F53A-A604-4579-91CE-4E35B27DA47B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
+1
-16
@@ -27,7 +27,7 @@
|
||||
"source": "https://github.com/RobThree/TwoFactorAuth"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1.0"
|
||||
"php": ">=8.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9",
|
||||
@@ -61,20 +61,5 @@
|
||||
"test": [
|
||||
"XDEBUG_MODE=coverage phpunit"
|
||||
]
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/.github/",
|
||||
"/demo/",
|
||||
"/docs/",
|
||||
"/tests/",
|
||||
"/testsDependency/",
|
||||
"/.gitignore",
|
||||
"/logo.png",
|
||||
"/multifactorauthforeveryone.png",
|
||||
"/phpunit.xml",
|
||||
"/TwoFactorAuth.phpproj",
|
||||
"/TwoFactorAuth.sln"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@
|
||||
});
|
||||
|
||||
// substitute your company or app name here
|
||||
$tfa = new RobThree\Auth\TwoFactorAuth('RobThree TwoFactorAuth');
|
||||
$tfa = new RobThree\Auth\TwoFactorAuth(new RobThree\Auth\Providers\Qr\QRServerProvider());
|
||||
?>
|
||||
<li>First create a secret and associate it with a user</li>
|
||||
<?php
|
||||
|
||||
+15
-10
@@ -7,28 +7,33 @@ title: Getting Started
|
||||
|
||||
The best way of making use of this project is by installing it with [composer](https://getcomposer.org/doc/01-basic-usage.md).
|
||||
|
||||
```
|
||||
php composer.phar require robthree/twofactorauth
|
||||
```
|
||||
|
||||
or if you have composer installed globally
|
||||
|
||||
```
|
||||
composer require robthree/twofactorauth
|
||||
```
|
||||
|
||||
## 2. Create an instance
|
||||
|
||||
Now you can create an instance for use with your code
|
||||
`TwoFactorAuth` constructor requires an object able to provide a QR Code image. It is the only mandatory argument. This lets you select your preferred QR Code generator/library.
|
||||
|
||||
See [QR code providers documentation](qr-codes.md) for more information about the different possibilites.
|
||||
|
||||
Example code:
|
||||
|
||||
```php
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider; // if using Bacon
|
||||
use RobThree\Auth\Providers\Qr\EndroidQrCodeProvider; // if using Endroid
|
||||
|
||||
$tfa = new TwoFactorAuth();
|
||||
// using Bacon
|
||||
$tfa = new TwoFactorAuth(new BaconQrCodeProvider());
|
||||
// using Endroid
|
||||
$tfa = new TwoFactorAuth(new EndroidQrCodeProvider());
|
||||
// using a custom object implementing IQRCodeProvider interface
|
||||
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
|
||||
// using named argument and a variable
|
||||
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
|
||||
```
|
||||
|
||||
**Note:** if you are not using a framework that uses composer, you should [include the composer loader yourself](https://getcomposer.org/doc/01-basic-usage.md#autoloading)
|
||||
|
||||
## 3. Shared secrets
|
||||
|
||||
When your user is setting up two-factor, or multi-factor, authentication in your project, you can create a secret from the instance.
|
||||
|
||||
@@ -7,29 +7,21 @@ title: Optional Configuration
|
||||
|
||||
The instance (`new TwoFactorAuth()`) can only be configured by the constructor with the following optional arguments
|
||||
|
||||
Argument | Default value | Use
|
||||
------------------|---------------|-----
|
||||
`$issuer` | `null` | Will be displayed in the users app as the default issuer name when using QR code to import the secret
|
||||
`$digits` | `6` | The number of digits the resulting codes will be
|
||||
`$period` | `30` | The number of seconds a code will be valid
|
||||
`$algorithm` | `'sha1'` | The algorithm used (one of `sha1`, `sha256`, `sha512`, `md5`)
|
||||
`$qrcodeprovider` | `null` | QR-code provider
|
||||
`$rngprovider` | `null` | Random Number Generator provider
|
||||
`$timeprovider` | `null` | Time provider
|
||||
Argument | Default value | Use
|
||||
------------------|-------------------|-----
|
||||
`$issuer` | `null` | Will be displayed in the users app as the default issuer name when using QR code to import the secret
|
||||
`$digits` | `6` | The number of digits the resulting codes will be
|
||||
`$period` | `30` | The number of seconds a code will be valid
|
||||
`$algorithm` | `Algorithm::Sha1` | The algorithm used (one of `Algorithm::Sha1`, `Algorithm::Sha256`, `Algorithm::Sha512`, `Algorithm::Md5`)
|
||||
`$qrcodeprovider` | `null` | QR-code provider
|
||||
`$rngprovider` | `null` | Random Number Generator provider
|
||||
`$timeprovider` | `null` | Time provider
|
||||
|
||||
**Note:** the default values for `$digits`, `$period`, and `$algorithm` provide the widest variety of support amongst common authenticator apps such as Google Authenticator. If you choose to use different values for these arguments you will likely have to instruct your users to use a specific app which supports your chosen configuration.
|
||||
|
||||
### RNG providers
|
||||
|
||||
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. [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.
|
||||
|
||||
You can also implement your own by implementing the [`IRNGProvider` interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Rng/IRNGProvider.php).
|
||||
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.
|
||||
|
||||
### Time providers
|
||||
|
||||
|
||||
+25
-28
@@ -5,7 +5,7 @@ title: QR Codes
|
||||
|
||||
An alternative way of communicating the secret to the user is through the use of [QR Codes](http://en.wikipedia.org/wiki/QR_code) which most if not all authenticator mobile apps can scan.
|
||||
|
||||
This can avoid accidental typing errors and also pre-set some text values within the users app.
|
||||
This can avoid accidental typing errors and also pre-set some text values within the two factor authentication mobile application.
|
||||
|
||||
You can display the QR Code as a base64 encoded image using the instance as follows, supplying the users name or other public identifier as the first argument
|
||||
|
||||
@@ -16,46 +16,43 @@ You can display the QR Code as a base64 encoded image using the instance as foll
|
||||
|
||||
You can also specify a size as a third argument which is 200 by default.
|
||||
|
||||
**Note:** by default, the QR code returned by the instance is generated from a third party across the internet. If the third party is encountering problems or is not available from where you have hosted your code, your user will likely experience a delay in seeing the QR code, if it even loads at all. This can be overcome with offline providers configured when you create the instance.
|
||||
|
||||
## Online Providers
|
||||
|
||||
[QRServerProvider](qr-codes/qr-server.html) (default)
|
||||
|
||||
[ImageChartsQRCodeProvider](qr-codes/image-charts.html)
|
||||
|
||||
[QRicketProvider](qr-codes/qrickit.html)
|
||||
|
||||
## Offline Providers
|
||||
|
||||
[EndroidQrCodeProvider](qr-codes/endroid.html) and EndroidQrCodeWithLogoProvider
|
||||
[EndroidQrCodeProvider](qr-codes/endroid.md) and EndroidQrCodeWithLogoProvider
|
||||
|
||||
[BaconQRCodeProvider](qr-codes/bacon.html)
|
||||
[BaconQRCodeProvider](qr-codes/bacon.md)
|
||||
|
||||
**Note:** offline providers may have additional PHP requirements in order to function, you should study what is required before trying to make use of them.
|
||||
|
||||
## Custom Provider
|
||||
|
||||
If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](https://github.com/RobThree/TwoFactorAuth/blob/master/lib/Providers/Qr/IQRCodeProvider.php).
|
||||
If you wish to make your own QR Code provider to reference another service or library, it must implement the [IQRCodeProvider interface](../lib/Providers/Qr/IQRCodeProvider.php).
|
||||
|
||||
It is recommended to use similar constructor arguments as the included providers to avoid big shifts when trying different providers.
|
||||
|
||||
## Using a specific provider
|
||||
|
||||
If you do not want to use the default QR code provider, you can specify the one you want to use when you create your instance.
|
||||
Example:
|
||||
|
||||
```php
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
|
||||
$qrCodeProvider = new YourChosenProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth(
|
||||
null,
|
||||
6,
|
||||
30,
|
||||
'sha1',
|
||||
$qrCodeProvider
|
||||
);
|
||||
// using a custom object implementing IQRCodeProvider
|
||||
$tfa = new TwoFactorAuth(new MyQrCodeProvider());
|
||||
// using named argument and a variable
|
||||
$tfa = new TwoFactorAuth(qrcodeprovider: $qrGenerator);
|
||||
```
|
||||
|
||||
As you create a new instance of your provider, you can supply any extra configuration there.
|
||||
## Online Providers
|
||||
|
||||
**Warning:** Using an external service for generating QR codes encoding authentication secrets is **not** recommended! You should instead make use of the included offline providers listed above.
|
||||
|
||||
* Gogr.me: [QRServerProvider](qr-codes/qr-server.md)
|
||||
* Image Charts: [ImageChartsQRCodeProvider](qr-codes/image-charts.md)
|
||||
* Qrickit: [QRicketProvider](qr-codes/qrickit.md)
|
||||
* Google Charts: [GoogleChartsQrCodeProvider](qr-codes/google-charts.md)
|
||||
|
||||
Example:
|
||||
|
||||
```php
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\Providers\Qr\GoogleChartsQrCodeProvider;
|
||||
$tfa = new TwoFactorAuth(new GoogleChartsQrCodeProvider());
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
layout: post
|
||||
title: QR GoogleCharts
|
||||
---
|
||||
|
||||
See: https://developers.google.com/chart/infographics/docs/qr_codes
|
||||
|
||||
## Optional Configuration
|
||||
|
||||
Argument | Default value
|
||||
------------------------|---------------
|
||||
`$verifyssl` | `false`
|
||||
`$errorcorrectionlevel` | `'L'`
|
||||
`$margin` | `4`
|
||||
`$encoding` | `'UTF-8'`
|
||||
@@ -7,7 +7,7 @@ title: Image-Charts
|
||||
|
||||
Argument | Default value
|
||||
------------------------|---------------
|
||||
`$verifyssl` | `false`
|
||||
`$verifyssl` | `true`
|
||||
`$errorcorrectionlevel` | `'L'`
|
||||
`$margin` | `4`
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ title: QR Server
|
||||
|
||||
Argument | Default value
|
||||
------------------------|---------------
|
||||
`$verifyssl` | `false`
|
||||
`$verifyssl` | `true`
|
||||
`$errorcorrectionlevel` | `'L'`
|
||||
`$margin` | `4`
|
||||
`$qzone` | `1`
|
||||
|
||||
@@ -10,7 +10,6 @@ use BaconQrCode\Renderer\Image\ImageBackEndInterface;
|
||||
use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
|
||||
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||
use BaconQrCode\Renderer\ImageRenderer;
|
||||
|
||||
use BaconQrCode\Renderer\RendererStyle\EyeFill;
|
||||
use BaconQrCode\Renderer\RendererStyle\Fill;
|
||||
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||
@@ -23,10 +22,10 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
* Ensure we using the latest Bacon QR Code and specify default options
|
||||
*/
|
||||
public function __construct(
|
||||
private int $borderWidth = 4,
|
||||
private readonly int $borderWidth = 4,
|
||||
private string|array $backgroundColour = '#ffffff',
|
||||
private string|array $foregroundColour = '#000000',
|
||||
private string $format = 'png',
|
||||
private string $format = 'png',
|
||||
) {
|
||||
$this->backgroundColour = $this->handleColour($this->backgroundColour);
|
||||
$this->foregroundColour = $this->handleColour($this->foregroundColour);
|
||||
@@ -54,20 +53,15 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
switch ($this->format) {
|
||||
case 'svg':
|
||||
$backend = new SvgImageBackEnd();
|
||||
break;
|
||||
case 'eps':
|
||||
$backend = new EpsImageBackEnd();
|
||||
break;
|
||||
default:
|
||||
$backend = new ImagickImageBackEnd($this->format);
|
||||
}
|
||||
$backend = match ($this->format) {
|
||||
'svg' => new SvgImageBackEnd(),
|
||||
'eps' => new EpsImageBackEnd(),
|
||||
default => new ImagickImageBackEnd($this->format),
|
||||
};
|
||||
|
||||
$output = $this->getQRCodeByBackend($qrText, $size, $backend);
|
||||
|
||||
if ($this->format == 'svg') {
|
||||
if ($this->format === 'svg') {
|
||||
$svg = explode("\n", $output);
|
||||
return $svg[1];
|
||||
}
|
||||
@@ -84,7 +78,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
$rendererStyleArgs = array($size, $this->borderWidth);
|
||||
|
||||
if (is_array($this->foregroundColour) && is_array($this->backgroundColour)) {
|
||||
$rendererStyleArgs = array_merge($rendererStyleArgs, array(
|
||||
$rendererStyleArgs = array(...$rendererStyleArgs, ...array(
|
||||
null,
|
||||
null,
|
||||
Fill::withForegroundColor(
|
||||
@@ -112,7 +106,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
private function handleColour(array|string $colour): array|string
|
||||
{
|
||||
if (is_string($colour) && $colour[0] == '#') {
|
||||
$hexToRGB = function ($input) {
|
||||
$hexToRGB = static function ($input) {
|
||||
// ensure input no longer has a # for more predictable division
|
||||
// PHP 8.1 does not like implicitly casting a float to an int
|
||||
$input = trim($input, '#');
|
||||
@@ -126,7 +120,7 @@ class BaconQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
// cope with three character hex reference
|
||||
if (strlen($input) == 3) {
|
||||
array_walk($split, function (&$character) {
|
||||
array_walk($split, static function (&$character) {
|
||||
$character = str_repeat($character, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
|
||||
{
|
||||
protected bool $verifyssl;
|
||||
protected bool $verifyssl = true;
|
||||
|
||||
protected function getContent(string $url): string|bool
|
||||
protected function getContent(string $url): string
|
||||
{
|
||||
$curlhandle = curl_init();
|
||||
|
||||
@@ -22,6 +22,9 @@ abstract class BaseHTTPQRCodeProvider implements IQRCodeProvider
|
||||
CURLOPT_USERAGENT => 'TwoFactorAuth',
|
||||
));
|
||||
$data = curl_exec($curlhandle);
|
||||
if ($data === false) {
|
||||
throw new QRException(curl_error($curlhandle));
|
||||
}
|
||||
|
||||
curl_close($curlhandle);
|
||||
return $data;
|
||||
|
||||
@@ -26,9 +26,15 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
|
||||
protected $endroid4 = false;
|
||||
|
||||
protected $endroid5 = false;
|
||||
|
||||
protected $endroid6 = false;
|
||||
|
||||
public function __construct($bgcolor = 'ffffff', $color = '000000', $margin = 0, $errorcorrectionlevel = 'H')
|
||||
{
|
||||
$this->endroid4 = method_exists(QrCode::class, 'create');
|
||||
$this->endroid5 = enum_exists(ErrorCorrectionLevel::class);
|
||||
$this->endroid6 = $this->endroid5 && !method_exists(QrCode::class, 'setSize');
|
||||
$this->endroid4 = $this->endroid6 || method_exists(QrCode::class, 'create');
|
||||
|
||||
$this->bgcolor = $this->handleColor($bgcolor);
|
||||
$this->color = $this->handleColor($color);
|
||||
@@ -41,30 +47,40 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$writer = new PngWriter();
|
||||
return $writer->write($this->qrCodeInstance($qrtext, $size))->getString();
|
||||
return $writer->write($this->qrCodeInstance($qrText, $size))->getString();
|
||||
}
|
||||
|
||||
protected function qrCodeInstance(string $qrtext, int $size): QrCode
|
||||
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||
{
|
||||
$qrCode = new QrCode($qrtext);
|
||||
if ($this->endroid6) {
|
||||
return new QrCode(
|
||||
data: $qrText,
|
||||
errorCorrectionLevel: $this->errorcorrectionlevel,
|
||||
size: $size,
|
||||
margin: $this->margin,
|
||||
foregroundColor: $this->color,
|
||||
backgroundColor: $this->bgcolor
|
||||
);
|
||||
}
|
||||
|
||||
$qrCode = new QrCode($qrText);
|
||||
$qrCode->setSize($size);
|
||||
|
||||
$qrCode->setErrorCorrectionLevel($this->errorcorrectionlevel);
|
||||
$qrCode->setMargin($this->margin);
|
||||
$qrCode->setBackgroundColor($this->bgcolor);
|
||||
$qrCode->setForegroundColor($this->color);
|
||||
|
||||
return $qrCode;
|
||||
}
|
||||
|
||||
private function handleColor(string $color): Color
|
||||
private function handleColor(string $color): Color|array
|
||||
{
|
||||
$split = str_split($color, 2);
|
||||
$r = hexdec($split[0]);
|
||||
@@ -74,18 +90,34 @@ class EndroidQrCodeProvider implements IQRCodeProvider
|
||||
return $this->endroid4 ? new Color($r, $g, $b, 0) : array('r' => $r, 'g' => $g, 'b' => $b, 'a' => 0);
|
||||
}
|
||||
|
||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface
|
||||
private function handleErrorCorrectionLevel(string $level): ErrorCorrectionLevelInterface|ErrorCorrectionLevel
|
||||
{
|
||||
switch ($level) {
|
||||
case 'L':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelLow() : ErrorCorrectionLevel::LOW();
|
||||
case 'M':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelMedium() : ErrorCorrectionLevel::MEDIUM();
|
||||
case 'Q':
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelQuartile() : ErrorCorrectionLevel::QUARTILE();
|
||||
case 'H':
|
||||
default:
|
||||
return $this->endroid4 ? new ErrorCorrectionLevelHigh() : ErrorCorrectionLevel::HIGH();
|
||||
// First check for version 5 (using enums)
|
||||
if ($this->endroid5) {
|
||||
return match ($level) {
|
||||
'L' => ErrorCorrectionLevel::Low,
|
||||
'M' => ErrorCorrectionLevel::Medium,
|
||||
'Q' => ErrorCorrectionLevel::Quartile,
|
||||
default => ErrorCorrectionLevel::High,
|
||||
};
|
||||
}
|
||||
|
||||
// If not check for version 4 (using classes)
|
||||
if ($this->endroid4) {
|
||||
return match ($level) {
|
||||
'L' => new ErrorCorrectionLevelLow(),
|
||||
'M' => new ErrorCorrectionLevelMedium(),
|
||||
'Q' => new ErrorCorrectionLevelQuartile(),
|
||||
default => new ErrorCorrectionLevelHigh(),
|
||||
};
|
||||
}
|
||||
|
||||
// Any other version will be using strings
|
||||
return match ($level) {
|
||||
'L' => ErrorCorrectionLevel::LOW(),
|
||||
'M' => ErrorCorrectionLevel::MEDIUM(),
|
||||
'Q' => ErrorCorrectionLevel::QUARTILE(),
|
||||
default => ErrorCorrectionLevel::HIGH(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
|
||||
$this->logoSize = (array)$size;
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
if (!$this->endroid4) {
|
||||
return $this->qrCodeInstance($qrtext, $size)->writeString();
|
||||
return $this->qrCodeInstance($qrText, $size)->writeString();
|
||||
}
|
||||
|
||||
$logo = null;
|
||||
@@ -42,12 +42,12 @@ class EndroidQrCodeWithLogoProvider extends EndroidQrCodeProvider
|
||||
}
|
||||
}
|
||||
$writer = new PngWriter();
|
||||
return $writer->write($this->qrCodeInstance($qrtext, $size), $logo)->getString();
|
||||
return $writer->write($this->qrCodeInstance($qrText, $size), $logo)->getString();
|
||||
}
|
||||
|
||||
protected function qrCodeInstance(string $qrtext, int $size): QrCode
|
||||
protected function qrCodeInstance(string $qrText, int $size): QrCode
|
||||
{
|
||||
$qrCode = parent::qrCodeInstance($qrtext, $size);
|
||||
$qrCode = parent::qrCodeInstance($qrText, $size);
|
||||
|
||||
if (!$this->endroid4 && $this->logoPath) {
|
||||
$qrCode->setLogoPath($this->logoPath);
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace RobThree\Auth\Providers\Qr;
|
||||
// https://developers.google.com/chart/infographics/docs/qr_codes
|
||||
class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 4, public string $encoding = 'UTF-8')
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public string $encoding = 'UTF-8')
|
||||
{
|
||||
}
|
||||
|
||||
@@ -16,18 +16,21 @@ class GoogleChartsQrCodeProvider extends BaseHTTPQRCodeProvider
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrtext, $size));
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrtext, int $size): string
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
return 'https://chart.googleapis.com/chart'
|
||||
. '?chs=' . $size . 'x' . $size
|
||||
. '&chld=' . urlencode(strtoupper($this->errorcorrectionlevel) . '|' . $this->margin)
|
||||
. '&cht=' . 'qr'
|
||||
. '&choe=' . $this->encoding
|
||||
. '&chl=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'chs' => $size . 'x' . $size,
|
||||
'chld' => strtoupper($this->errorcorrectionlevel) . '|' . $this->margin,
|
||||
'cht' => 'qr',
|
||||
'choe' => $this->encoding,
|
||||
'chl' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://chart.googleapis.com/chart?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use function preg_match;
|
||||
trait HandlesDataUri
|
||||
{
|
||||
/**
|
||||
* @return array<string, string>
|
||||
* @return array<string, string>|null
|
||||
*/
|
||||
private function DecodeDataUri(string $datauri): ?array
|
||||
{
|
||||
|
||||
@@ -9,12 +9,12 @@ interface IQRCodeProvider
|
||||
/**
|
||||
* Generate and return the QR code to embed in a web page
|
||||
*
|
||||
* @param string $qrtext the value to encode in the QR code
|
||||
* @param string $qrText the value to encode in the QR code
|
||||
* @param int $size the desired size of the QR code
|
||||
*
|
||||
* @return string file contents of the QR code
|
||||
*/
|
||||
public function getQRCodeImage(string $qrtext, int $size): string;
|
||||
public function getQRCodeImage(string $qrText, int $size): string;
|
||||
|
||||
/**
|
||||
* Returns the appropriate mime type for the QR code
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace RobThree\Auth\Providers\Qr;
|
||||
*/
|
||||
class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 1)
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 1)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -18,16 +18,20 @@ class ImageChartsQRCodeProvider extends BaseHTTPQRCodeProvider
|
||||
return 'image/png';
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrtext, $size));
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrtext, int $size): string
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
return 'https://image-charts.com/chart?cht=qr'
|
||||
. '&chs=' . ceil($size / 2) . 'x' . ceil($size / 2)
|
||||
. '&chld=' . $this->errorcorrectionlevel . '|' . $this->margin
|
||||
. '&chl=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'cht' => 'qr',
|
||||
'chs' => ceil($size / 2) . 'x' . ceil($size / 2),
|
||||
'chld' => $this->errorcorrectionlevel . '|' . $this->margin,
|
||||
'chl' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://image-charts.com/chart?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ declare(strict_types=1);
|
||||
namespace RobThree\Auth\Providers\Qr;
|
||||
|
||||
/**
|
||||
* Use http://goqr.me/api/doc/create-qr-code/ to get QR code
|
||||
* Use https://goqr.me/api/doc/create-qr-code/ to get QR code
|
||||
*/
|
||||
class QRServerProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(protected bool $verifyssl = false, public string $errorcorrectionlevel = 'L', public int $margin = 4, public int $qzone = 1, public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'png')
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public int $margin = 4, public int $qzone = 1, public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'png')
|
||||
{
|
||||
}
|
||||
|
||||
@@ -31,22 +31,25 @@ class QRServerProvider extends BaseHTTPQRCodeProvider
|
||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrtext, $size));
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrtext, int $size): string
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
return 'https://api.qrserver.com/v1/create-qr-code/'
|
||||
. '?size=' . $size . 'x' . $size
|
||||
. '&ecc=' . strtoupper($this->errorcorrectionlevel)
|
||||
. '&margin=' . $this->margin
|
||||
. '&qzone=' . $this->qzone
|
||||
. '&bgcolor=' . $this->decodeColor($this->bgcolor)
|
||||
. '&color=' . $this->decodeColor($this->color)
|
||||
. '&format=' . strtolower($this->format)
|
||||
. '&data=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'size' => $size . 'x' . $size,
|
||||
'ecc' => strtoupper($this->errorcorrectionlevel),
|
||||
'margin' => $this->margin,
|
||||
'qzone' => $this->qzone,
|
||||
'bgcolor' => $this->decodeColor($this->bgcolor),
|
||||
'color' => $this->decodeColor($this->color),
|
||||
'format' => strtolower($this->format),
|
||||
'data' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://api.qrserver.com/v1/create-qr-code/?' . http_build_query($queryParameters);
|
||||
}
|
||||
|
||||
private function decodeColor(string $value): string
|
||||
|
||||
@@ -9,9 +9,8 @@ namespace RobThree\Auth\Providers\Qr;
|
||||
*/
|
||||
class QRicketProvider extends BaseHTTPQRCodeProvider
|
||||
{
|
||||
public function __construct(public string $errorcorrectionlevel = 'L', public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'p')
|
||||
public function __construct(protected bool $verifyssl = true, public string $errorcorrectionlevel = 'L', public string $bgcolor = 'ffffff', public string $color = '000000', public string $format = 'p')
|
||||
{
|
||||
$this->verifyssl = false;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
@@ -27,19 +26,22 @@ class QRicketProvider extends BaseHTTPQRCodeProvider
|
||||
throw new QRException(sprintf('Unknown MIME-type: %s', $this->format));
|
||||
}
|
||||
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $this->getContent($this->getUrl($qrtext, $size));
|
||||
return $this->getContent($this->getUrl($qrText, $size));
|
||||
}
|
||||
|
||||
public function getUrl(string $qrtext, int $size): string
|
||||
public function getUrl(string $qrText, int $size): string
|
||||
{
|
||||
return 'http://qrickit.com/api/qr'
|
||||
. '?qrsize=' . (string) $size
|
||||
. '&e=' . strtolower($this->errorcorrectionlevel)
|
||||
. '&bgdcolor=' . $this->bgcolor
|
||||
. '&fgdcolor=' . $this->color
|
||||
. '&t=' . strtolower($this->format)
|
||||
. '&d=' . rawurlencode($qrtext);
|
||||
$queryParameters = array(
|
||||
'qrsize' => $size,
|
||||
'e' => strtolower($this->errorcorrectionlevel),
|
||||
'bgdcolor' => $this->bgcolor,
|
||||
'fgdcolor' => $this->color,
|
||||
't' => strtolower($this->format),
|
||||
'd' => $qrText,
|
||||
);
|
||||
|
||||
return 'https://qrickit.com/api/qr?' . http_build_query($queryParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,4 @@ class CSRNGProvider implements IRNGProvider
|
||||
{
|
||||
return random_bytes($bytecount); // PHP7+
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCryptographicallySecure(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
use function in_array;
|
||||
|
||||
class HashRNGProvider implements IRNGProvider
|
||||
{
|
||||
public function __construct(private string $algorithm = 'sha256')
|
||||
{
|
||||
$algos = array_values(hash_algos());
|
||||
if (!in_array($this->algorithm, $algos, true)) {
|
||||
throw new RNGException('Unsupported algorithm specified');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRandomBytes(int $bytecount): string
|
||||
{
|
||||
$result = '';
|
||||
$hash = mt_rand();
|
||||
for ($i = 0; $i < $bytecount; $i++) {
|
||||
$hash = hash($this->algorithm, $hash . mt_rand(), true);
|
||||
$result .= $hash[mt_rand(0, strlen($hash) - 1)];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCryptographicallySecure(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,4 @@ namespace RobThree\Auth\Providers\Rng;
|
||||
interface IRNGProvider
|
||||
{
|
||||
public function getRandomBytes(int $bytecount): string;
|
||||
|
||||
public function isCryptographicallySecure(): bool;
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth\Providers\Rng;
|
||||
|
||||
class OpenSSLRNGProvider implements IRNGProvider
|
||||
{
|
||||
public function __construct(private bool $requirestrong = true)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRandomBytes(int $bytecount): string
|
||||
{
|
||||
// will throw an Exception on failure
|
||||
return openssl_random_pseudo_bytes($bytecount, $crypto_strong);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCryptographicallySecure(): bool
|
||||
{
|
||||
return $this->requirestrong;
|
||||
}
|
||||
}
|
||||
@@ -12,21 +12,16 @@ use Exception;
|
||||
*/
|
||||
class HttpTimeProvider implements ITimeProvider
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
public array $options;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $url = 'https://google.com',
|
||||
public string $expectedtimeformat = 'D, d M Y H:i:s O+',
|
||||
array $options = null,
|
||||
public ?array $options = null,
|
||||
) {
|
||||
$this->url = $url;
|
||||
$this->expectedtimeformat = $expectedtimeformat;
|
||||
if ($options === null) {
|
||||
$options = array(
|
||||
if ($this->options === null) {
|
||||
$this->options = array(
|
||||
'http' => array(
|
||||
'method' => 'HEAD',
|
||||
'follow_location' => false,
|
||||
@@ -41,7 +36,6 @@ class HttpTimeProvider implements ITimeProvider
|
||||
),
|
||||
);
|
||||
}
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +44,7 @@ class HttpTimeProvider implements ITimeProvider
|
||||
public function getTime()
|
||||
{
|
||||
try {
|
||||
$context = stream_context_create($this->options);
|
||||
$context = stream_context_create($this->options);
|
||||
$fd = fopen($this->url, 'rb', false, $context);
|
||||
$headers = stream_get_meta_data($fd);
|
||||
fclose($fd);
|
||||
|
||||
@@ -47,7 +47,7 @@ class NTPTimeProvider implements ITimeProvider
|
||||
|
||||
// Interpret response
|
||||
$data = unpack('N12', $recv);
|
||||
$timestamp = (int) sprintf('%u', $data[9]);
|
||||
$timestamp = (int)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;
|
||||
|
||||
+28
-81
@@ -4,16 +4,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace RobThree\Auth;
|
||||
|
||||
use function hash_equals;
|
||||
|
||||
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
||||
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\OpenSSLRNGProvider;
|
||||
use RobThree\Auth\Providers\Time\HttpTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\ITimeProvider;
|
||||
use RobThree\Auth\Providers\Time\LocalMachineTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\NTPTimeProvider;
|
||||
use SensitiveParameter;
|
||||
|
||||
// Based on / inspired by: https://github.com/PHPGangsta/GoogleAuthenticator
|
||||
// Algorithms, digits, period etc. explained: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
||||
@@ -28,13 +28,13 @@ class TwoFactorAuth
|
||||
private static array $_base32lookup = array();
|
||||
|
||||
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
|
||||
private IQRCodeProvider $qrcodeprovider,
|
||||
private readonly ?string $issuer = null,
|
||||
private readonly int $digits = 6,
|
||||
private readonly int $period = 30,
|
||||
private readonly Algorithm $algorithm = Algorithm::Sha1,
|
||||
private ?IRNGProvider $rngprovider = null,
|
||||
private ?ITimeProvider $timeprovider = null
|
||||
) {
|
||||
if ($this->digits <= 0) {
|
||||
throw new TwoFactorAuthException('Digits must be > 0');
|
||||
@@ -51,14 +51,11 @@ class TwoFactorAuth
|
||||
/**
|
||||
* Create a new secret
|
||||
*/
|
||||
public function createSecret(int $bits = 80, bool $requirecryptosecure = true): string
|
||||
public function createSecret(int $bits = 160): 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');
|
||||
}
|
||||
$rnd = $rngprovider->getRandomBytes($bytes);
|
||||
for ($i = 0; $i < $bytes; $i++) {
|
||||
$secret .= self::$_base32[ord($rnd[$i]) & 31]; //Mask out left 3 bits for 0-31 values
|
||||
@@ -69,7 +66,7 @@ class TwoFactorAuth
|
||||
/**
|
||||
* Calculate the code with given secret and point in time
|
||||
*/
|
||||
public function getCode(string $secret, ?int $time = null): string
|
||||
public function getCode(#[SensitiveParameter] string $secret, ?int $time = null): string
|
||||
{
|
||||
$secretkey = $this->base32Decode($secret);
|
||||
|
||||
@@ -79,7 +76,7 @@ class TwoFactorAuth
|
||||
$value = unpack('N', $hashpart); // Unpack binary value
|
||||
$value = $value[1] & 0x7FFFFFFF; // Drop MSB, keep only 31 bits
|
||||
|
||||
return str_pad((string) ($value % 10** $this->digits), $this->digits, '0', STR_PAD_LEFT);
|
||||
return str_pad((string)($value % 10 ** $this->digits), $this->digits, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +95,7 @@ class TwoFactorAuth
|
||||
for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
|
||||
$ts = $timestamp + ($i * $this->period);
|
||||
$slice = $this->getTimeSlice($ts);
|
||||
$timeslice = $this->codeEquals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
|
||||
$timeslice = hash_equals($this->getCode($secret, $ts), $code) ? $slice : $timeslice;
|
||||
}
|
||||
|
||||
return $timeslice > 0;
|
||||
@@ -107,22 +104,22 @@ class TwoFactorAuth
|
||||
/**
|
||||
* Get data-uri of QRCode
|
||||
*/
|
||||
public function getQRCodeImageAsDataUri(string $label, string $secret, int $size = 200): string
|
||||
public function getQRCodeImageAsDataUri(string $label, #[SensitiveParameter] string $secret, int $size = 200): string
|
||||
{
|
||||
if ($size <= 0) {
|
||||
throw new TwoFactorAuthException('Size must be > 0');
|
||||
}
|
||||
|
||||
$qrcodeprovider = $this->getQrCodeProvider();
|
||||
return 'data:'
|
||||
. $qrcodeprovider->getMimeType()
|
||||
. $this->qrcodeprovider->getMimeType()
|
||||
. ';base64,'
|
||||
. base64_encode($qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
|
||||
. base64_encode($this->qrcodeprovider->getQRCodeImage($this->getQRText($label, $secret), $size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare default timeprovider with specified timeproviders and ensure the time is within the specified number of seconds (leniency)
|
||||
* @param array<ITimeProvider> $timeproviders
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function ensureCorrectTime(?array $timeproviders = null, int $leniency = 5): void
|
||||
{
|
||||
@@ -152,26 +149,14 @@ class TwoFactorAuth
|
||||
/**
|
||||
* Builds a string to be encoded in a QR code
|
||||
*/
|
||||
public function getQRText(string $label, string $secret): string
|
||||
public function getQRText(string $label, #[SensitiveParameter] string $secret): string
|
||||
{
|
||||
return 'otpauth://totp/' . rawurlencode($label)
|
||||
. '?secret=' . rawurlencode($secret)
|
||||
. '&issuer=' . rawurlencode((string)$this->issuer)
|
||||
. '&period=' . intval($this->period)
|
||||
. '&period=' . $this->period
|
||||
. '&algorithm=' . rawurlencode(strtoupper($this->algorithm->value))
|
||||
. '&digits=' . intval($this->digits);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function getQrCodeProvider(): IQRCodeProvider
|
||||
{
|
||||
// Set default QR Code provider if none was specified
|
||||
if (null === $this->qrcodeprovider) {
|
||||
return $this->qrcodeprovider = new QRServerProvider();
|
||||
}
|
||||
return $this->qrcodeprovider;
|
||||
. '&digits=' . $this->digits;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,70 +164,32 @@ class TwoFactorAuth
|
||||
*/
|
||||
public function getRngProvider(): IRNGProvider
|
||||
{
|
||||
if (null !== $this->rngprovider) {
|
||||
return $this->rngprovider;
|
||||
}
|
||||
if (function_exists('random_bytes')) {
|
||||
return $this->rngprovider = new CSRNGProvider();
|
||||
}
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return $this->rngprovider = new OpenSSLRNGProvider();
|
||||
}
|
||||
if (function_exists('hash')) {
|
||||
return $this->rngprovider = new HashRNGProvider();
|
||||
}
|
||||
throw new TwoFactorAuthException('Unable to find a suited RNGProvider');
|
||||
return $this->rngprovider ??= new CSRNGProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws TwoFactorAuthException
|
||||
*/
|
||||
public function getTimeProvider(): ITimeProvider
|
||||
{
|
||||
// Set default time provider if none was specified
|
||||
if (null === $this->timeprovider) {
|
||||
return $this->timeprovider = new LocalMachineTimeProvider();
|
||||
}
|
||||
return $this->timeprovider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Timing-attack safe comparison of 2 codes (see http://blog.ircmaxell.com/2014/11/its-all-about-time.html)
|
||||
*/
|
||||
private function codeEquals(string $safe, string $user): bool
|
||||
{
|
||||
if (function_exists('hash_equals')) {
|
||||
return hash_equals($safe, $user);
|
||||
}
|
||||
// In general, it's not possible to prevent length leaks. So it's OK to leak the length. The important part is that
|
||||
// we don't leak information about the difference of the two strings.
|
||||
if (strlen($safe) === strlen($user)) {
|
||||
$result = 0;
|
||||
for ($i = 0; $i < strlen($safe); $i++) {
|
||||
$result |= (ord($safe[$i]) ^ ord($user[$i]));
|
||||
}
|
||||
return $result === 0;
|
||||
}
|
||||
return false;
|
||||
return $this->timeprovider ??= new LocalMachineTimeProvider();
|
||||
}
|
||||
|
||||
private function getTime(?int $time = null): int
|
||||
{
|
||||
return ($time === null) ? $this->getTimeProvider()->getTime() : $time;
|
||||
return $time ?? $this->getTimeProvider()->getTime();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private function base32Decode(string $value): string
|
||||
{
|
||||
if (strlen($value) == 0) {
|
||||
if ($value === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (preg_match('/[^' . preg_quote(self::$_base32dict) . ']/', $value) !== 0) {
|
||||
if (preg_match('/[^' . preg_quote(self::$_base32dict, '/') . ']/', $value) !== 0) {
|
||||
throw new TwoFactorAuthException('Invalid base32 string');
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Tests\Providers\Qr;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\Providers\Qr\HandlesDataUri;
|
||||
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
|
||||
@@ -14,38 +15,39 @@ class IQRCodeProviderTest extends TestCase
|
||||
{
|
||||
use HandlesDataUri;
|
||||
|
||||
protected IQRCodeProvider $qr;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->qr = new TestQrProvider();
|
||||
}
|
||||
|
||||
public function testTotpUriIsCorrect(): void
|
||||
{
|
||||
$qr = new TestQrProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
|
||||
$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']);
|
||||
$this->assertSame('test/test', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=Test%26Issuer&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||
}
|
||||
|
||||
public function testTotpUriIsCorrectNoIssuer(): void
|
||||
{
|
||||
$qr = new TestQrProvider();
|
||||
|
||||
/**
|
||||
* The library specifies the issuer is null by default however in PHP 8.1
|
||||
* there is a deprecation warning for passing null as a string argument to rawurlencode
|
||||
*/
|
||||
|
||||
$tfa = new TwoFactorAuth(null, 6, 30, Algorithm::Sha1, $qr);
|
||||
$tfa = new TwoFactorAuth($this->qr, null, 6, 30, Algorithm::Sha1);
|
||||
$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']);
|
||||
$this->assertSame('test/test', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertSame('otpauth://totp/Test%26Label?secret=VMR466AB62ZBOKHE&issuer=&period=30&algorithm=SHA1&digits=6@200', $data['data']);
|
||||
}
|
||||
|
||||
public function testGetQRCodeImageAsDataUriThrowsOnInvalidSize(): void
|
||||
{
|
||||
$qr = new TestQrProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, $qr);
|
||||
$tfa = new TwoFactorAuth($this->qr, 'Test', 6, 30, Algorithm::Sha1);
|
||||
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
||||
|
||||
class TestQrProvider implements IQRCodeProvider
|
||||
{
|
||||
public function getQRCodeImage(string $qrtext, int $size): string
|
||||
public function getQRCodeImage(string $qrText, int $size): string
|
||||
{
|
||||
return $qrtext . '@' . $size;
|
||||
return $qrText . '@' . $size;
|
||||
}
|
||||
|
||||
public function getMimeType(): string
|
||||
|
||||
@@ -11,19 +11,11 @@ class CSRNGProviderTest extends TestCase
|
||||
{
|
||||
use NeedsRngLengths;
|
||||
|
||||
/**
|
||||
* @requires function random_bytes
|
||||
*/
|
||||
public function testCSRNGProvidersReturnExpectedNumberOfBytes(): void
|
||||
{
|
||||
if (function_exists('random_bytes')) {
|
||||
$rng = new CSRNGProvider();
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
$this->assertTrue($rng->isCryptographicallySecure());
|
||||
} else {
|
||||
$this->expectNotToPerformAssertions();
|
||||
$rng = new CSRNGProvider();
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertSame($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Providers\Rng;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Providers\Rng\HashRNGProvider;
|
||||
|
||||
class HashRNGProviderTest extends TestCase
|
||||
{
|
||||
use NeedsRngLengths;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testHashRNGProvidersReturnExpectedNumberOfBytes()
|
||||
{
|
||||
$rng = new HashRNGProvider();
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertFalse($rng->isCryptographicallySecure());
|
||||
}
|
||||
}
|
||||
@@ -7,46 +7,13 @@ namespace Tests\Providers\Rng;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
use Tests\Providers\Qr\TestQrProvider;
|
||||
|
||||
class IRNGProviderTest extends TestCase
|
||||
{
|
||||
public function testCreateSecretThrowsOnInsecureRNGProvider(): void
|
||||
public function testCreateSecret(): void
|
||||
{
|
||||
$rng = new TestRNGProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
$tfa->createSecret();
|
||||
}
|
||||
|
||||
public function testCreateSecretOverrideSecureDoesNotThrowOnInsecureRNG(): void
|
||||
{
|
||||
$rng = new TestRNGProvider();
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret(80, false));
|
||||
}
|
||||
|
||||
public function testCreateSecretDoesNotThrowOnSecureRNGProvider(): void
|
||||
{
|
||||
$rng = new TestRNGProvider(true);
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, $rng);
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOP', $tfa->createSecret());
|
||||
}
|
||||
|
||||
public function testCreateSecretGeneratesDesiredAmountOfEntropy(): void
|
||||
{
|
||||
$rng = new TestRNGProvider(true);
|
||||
|
||||
$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));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(160));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $tfa->createSecret(320));
|
||||
$this->assertEquals('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567ABCDEFGHIJKLMNOPQRSTUVWXYZ234567A', $tfa->createSecret(321));
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, null);
|
||||
$this->assertIsString($tfa->createSecret());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Providers\Rng;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Providers\Rng\OpenSSLRNGProvider;
|
||||
|
||||
class OpenSSLRNGProviderTest extends TestCase
|
||||
{
|
||||
use NeedsRngLengths;
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes()
|
||||
{
|
||||
$rng = new OpenSSLRNGProvider(true);
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertTrue($rng->isCryptographicallySecure());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function testNonStrongOpenSSLRNGProvidersReturnExpectedNumberOfBytes()
|
||||
{
|
||||
$rng = new OpenSSLRNGProvider(false);
|
||||
foreach ($this->rngTestLengths as $l) {
|
||||
$this->assertEquals($l, strlen($rng->getRandomBytes($l)));
|
||||
}
|
||||
|
||||
$this->assertFalse($rng->isCryptographicallySecure());
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Providers\Rng;
|
||||
|
||||
use RobThree\Auth\Providers\Rng\IRNGProvider;
|
||||
|
||||
class TestRNGProvider implements IRNGProvider
|
||||
{
|
||||
public function __construct(private bool $isSecure = false)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRandomBytes(int $bytecount): string
|
||||
{
|
||||
$result = '';
|
||||
|
||||
for ($i = 0; $i < $bytecount; $i++) {
|
||||
$result .= chr($i);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCryptographicallySecure(): bool
|
||||
{
|
||||
return $this->isSecure;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
use Tests\Providers\Qr\TestQrProvider;
|
||||
|
||||
class ITimeProviderTest extends TestCase
|
||||
{
|
||||
@@ -17,7 +18,7 @@ class ITimeProviderTest extends TestCase
|
||||
$tpr1 = new TestTimeProvider(123);
|
||||
$tpr2 = new TestTimeProvider(128);
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
|
||||
$tfa->ensureCorrectTime(array($tpr2)); // 128 - 123 = 5 => within default leniency
|
||||
}
|
||||
|
||||
@@ -26,7 +27,7 @@ class ITimeProviderTest extends TestCase
|
||||
$tpr1 = new TestTimeProvider(123);
|
||||
$tpr2 = new TestTimeProvider(124);
|
||||
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1, null, null, $tpr1);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1, null, $tpr1);
|
||||
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
@@ -36,7 +37,7 @@ class ITimeProviderTest extends TestCase
|
||||
public function testEnsureDefaultTimeProviderReturnsCorrectTime(): void
|
||||
{
|
||||
$this->expectNotToPerformAssertions();
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), '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
|
||||
}
|
||||
}
|
||||
|
||||
+71
-70
@@ -7,8 +7,11 @@ namespace Tests;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionMethod;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\Providers\Time\HttpTimeProvider;
|
||||
use RobThree\Auth\Providers\Time\NTPTimeProvider;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RobThree\Auth\TwoFactorAuthException;
|
||||
use Tests\Providers\Qr\TestQrProvider;
|
||||
|
||||
class TwoFactorAuthTest extends TestCase
|
||||
{
|
||||
@@ -16,84 +19,84 @@ class TwoFactorAuthTest extends TestCase
|
||||
{
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
new TwoFactorAuth('Test', 0);
|
||||
new TwoFactorAuth(new TestQrProvider(), 'Test', 0);
|
||||
}
|
||||
|
||||
public function testConstructorThrowsOnInvalidPeriod(): void
|
||||
{
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
new TwoFactorAuth('Test', 6, 0);
|
||||
new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 0);
|
||||
}
|
||||
|
||||
public function testGetCodeReturnsCorrectResults(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$this->assertEquals('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
||||
$this->assertEquals('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
|
||||
$this->assertSame('543160', $tfa->getCode('VMR466AB62ZBOKHE', 1426847216));
|
||||
$this->assertSame('538532', $tfa->getCode('VMR466AB62ZBOKHE', 0));
|
||||
}
|
||||
|
||||
public function testEnsureAllTimeProvidersReturnCorrectTime(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30, Algorithm::Sha1);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30, Algorithm::Sha1);
|
||||
$tfa->ensureCorrectTime(array(
|
||||
new \RobThree\Auth\Providers\Time\NTPTimeProvider(), // Uses pool.ntp.org by default
|
||||
new 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??
|
||||
new \RobThree\Auth\Providers\Time\HttpTimeProvider(), // Uses google.com by default
|
||||
new HttpTimeProvider(), // Uses google.com by default
|
||||
//new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://github.com'), // github.com will periodically report times that are off by more than 5 sec
|
||||
new \RobThree\Auth\Providers\Time\HttpTimeProvider('https://yahoo.com'),
|
||||
new HttpTimeProvider('https://yahoo.com'),
|
||||
));
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
|
||||
public function testVerifyCodeWorksCorrectly(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 6, 30);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847190));
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 29)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 + 30)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 0, 1426847190 - 1)); //Test discrepancy
|
||||
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 0)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 35)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 35)); //Test discrepancy
|
||||
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 65)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 65)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 + 65)); //Test discrepancy
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 1, 1426847205 - 65)); //Test discrepancy
|
||||
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 + 65)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 + 65)); //Test discrepancy
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 2, 1426847205 - 65)); //Test discrepancy
|
||||
}
|
||||
|
||||
public function testVerifyCorrectTimeSliceIsReturned(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test', 6, 30);
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), '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->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '534113', 3, 1426847190, $timeslice1));
|
||||
$this->assertEquals(47561570, $timeslice1);
|
||||
$this->assertSame(47561570, $timeslice1);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '819652', 3, 1426847190, $timeslice2));
|
||||
$this->assertEquals(47561571, $timeslice2);
|
||||
$this->assertSame(47561571, $timeslice2);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '915954', 3, 1426847190, $timeslice3));
|
||||
$this->assertEquals(47561572, $timeslice3);
|
||||
$this->assertSame(47561572, $timeslice3);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '543160', 3, 1426847190, $timeslice4));
|
||||
$this->assertEquals(47561573, $timeslice4);
|
||||
$this->assertSame(47561573, $timeslice4);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '348401', 3, 1426847190, $timeslice5));
|
||||
$this->assertEquals(47561574, $timeslice5);
|
||||
$this->assertSame(47561574, $timeslice5);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '648525', 3, 1426847190, $timeslice6));
|
||||
$this->assertEquals(47561575, $timeslice6);
|
||||
$this->assertSame(47561575, $timeslice6);
|
||||
$this->assertTrue($tfa->verifyCode('VMR466AB62ZBOKHE', '170645', 3, 1426847190, $timeslice7));
|
||||
$this->assertEquals(47561576, $timeslice7);
|
||||
$this->assertSame(47561576, $timeslice7);
|
||||
|
||||
// Incorrect code should return false and a 0 timeslice
|
||||
$this->assertFalse($tfa->verifyCode('VMR466AB62ZBOKHE', '111111', 3, 1426847190, $timeslice8));
|
||||
$this->assertEquals(0, $timeslice8);
|
||||
$this->assertSame(0, $timeslice8);
|
||||
}
|
||||
|
||||
public function testGetCodeThrowsOnInvalidBase32String1(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
|
||||
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
@@ -102,7 +105,7 @@ class TwoFactorAuthTest extends TestCase
|
||||
|
||||
public function testGetCodeThrowsOnInvalidBase32String2(): void
|
||||
{
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
|
||||
|
||||
$this->expectException(TwoFactorAuthException::class);
|
||||
|
||||
@@ -122,19 +125,18 @@ class TwoFactorAuthTest extends TestCase
|
||||
// "In general, you don't want to break any encapsulation for the sake of testing (or as Mom used to say, "don't
|
||||
// expose your privates!"). Most of the time, you should be able to test a class by exercising its public methods."
|
||||
// Dave Thomas and Andy Hunt -- "Pragmatic Unit Testing
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
|
||||
|
||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
||||
$this->assertEquals('f', $method->invoke($tfa, 'MY======'));
|
||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ===='));
|
||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6==='));
|
||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
||||
$this->assertSame('', $method->invoke($tfa, ''));
|
||||
$this->assertSame('f', $method->invoke($tfa, 'MY======'));
|
||||
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ===='));
|
||||
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6==='));
|
||||
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ='));
|
||||
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI======'));
|
||||
}
|
||||
|
||||
public function testKnownBase32DecodeUnpaddedTestVectors(): void
|
||||
@@ -143,57 +145,56 @@ class TwoFactorAuthTest extends TestCase
|
||||
// This test ensures that strings without the padding-char ('=') are also decoded correctly.
|
||||
// https://tools.ietf.org/html/rfc4648#page-4:
|
||||
// "In some circumstances, the use of padding ("=") in base-encoded data is not required or used."
|
||||
$tfa = new TwoFactorAuth('Test');
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test');
|
||||
|
||||
$method = new ReflectionMethod(TwoFactorAuth::class, 'base32Decode');
|
||||
$method->setAccessible(true);
|
||||
|
||||
// Test vectors from: https://tools.ietf.org/html/rfc4648#page-12
|
||||
$this->assertEquals('', $method->invoke($tfa, ''));
|
||||
$this->assertEquals('f', $method->invoke($tfa, 'MY'));
|
||||
$this->assertEquals('fo', $method->invoke($tfa, 'MZXQ'));
|
||||
$this->assertEquals('foo', $method->invoke($tfa, 'MZXW6'));
|
||||
$this->assertEquals('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
||||
$this->assertEquals('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertEquals('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
||||
$this->assertSame('', $method->invoke($tfa, ''));
|
||||
$this->assertSame('f', $method->invoke($tfa, 'MY'));
|
||||
$this->assertSame('fo', $method->invoke($tfa, 'MZXQ'));
|
||||
$this->assertSame('foo', $method->invoke($tfa, 'MZXW6'));
|
||||
$this->assertSame('foob', $method->invoke($tfa, 'MZXW6YQ'));
|
||||
$this->assertSame('fooba', $method->invoke($tfa, 'MZXW6YTB'));
|
||||
$this->assertSame('foobar', $method->invoke($tfa, 'MZXW6YTBOI'));
|
||||
}
|
||||
|
||||
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, Algorithm::Sha1);
|
||||
$this->assertEquals('94287082', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('07081804', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('14050471', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('89005924', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('69279037', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('65353130', $tfa->getCode($secret, 20000000000));
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha1);
|
||||
$this->assertSame('94287082', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('07081804', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('14050471', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('89005924', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('69279037', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertSame('65353130', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
|
||||
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, Algorithm::Sha256);
|
||||
$this->assertEquals('46119246', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('68084774', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('67062674', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('91819424', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('90698825', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('77737706', $tfa->getCode($secret, 20000000000));
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha256);
|
||||
$this->assertSame('46119246', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('68084774', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('67062674', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('91819424', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('90698825', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertSame('77737706', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
|
||||
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, Algorithm::Sha512);
|
||||
$this->assertEquals('90693936', $tfa->getCode($secret, 59));
|
||||
$this->assertEquals('25091201', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertEquals('99943326', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertEquals('93441116', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertEquals('38618901', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertEquals('47863826', $tfa->getCode($secret, 20000000000));
|
||||
$tfa = new TwoFactorAuth(new TestQrProvider(), 'Test', 8, 30, Algorithm::Sha512);
|
||||
$this->assertSame('90693936', $tfa->getCode($secret, 59));
|
||||
$this->assertSame('25091201', $tfa->getCode($secret, 1111111109));
|
||||
$this->assertSame('99943326', $tfa->getCode($secret, 1111111111));
|
||||
$this->assertSame('93441116', $tfa->getCode($secret, 1234567890));
|
||||
$this->assertSame('38618901', $tfa->getCode($secret, 2000000000));
|
||||
$this->assertSame('47863826', $tfa->getCode($secret, 20000000000));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use RobThree\Auth\Algorithm;
|
||||
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider;
|
||||
use RobThree\Auth\Providers\Qr\HandlesDataUri;
|
||||
use RobThree\Auth\Providers\Qr\IQRCodeProvider;
|
||||
use RobThree\Auth\TwoFactorAuth;
|
||||
use RuntimeException;
|
||||
|
||||
@@ -15,14 +16,20 @@ class BaconQRCodeTest extends TestCase
|
||||
{
|
||||
use HandlesDataUri;
|
||||
|
||||
protected IQRCodeProvider $qr;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg');
|
||||
;
|
||||
}
|
||||
|
||||
public function testDependency(): void
|
||||
{
|
||||
$qr = new BaconQrCodeProvider(1, '#000', '#FFF', 'svg');
|
||||
|
||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||
$tfa = new TwoFactorAuth($this->qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
|
||||
|
||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||
$this->assertEquals('image/svg+xml', $data['mimetype']);
|
||||
$this->assertSame('image/svg+xml', $data['mimetype']);
|
||||
}
|
||||
|
||||
public function testBadTextColour(): void
|
||||
|
||||
@@ -17,10 +17,10 @@ class EndroidQRCodeTest extends TestCase
|
||||
public function testDependency(): void
|
||||
{
|
||||
$qr = new EndroidQrCodeProvider();
|
||||
$tfa = new TwoFactorAuth('Test&Issuer', 6, 30, Algorithm::Sha1, $qr);
|
||||
$tfa = new TwoFactorAuth($qr, 'Test&Issuer', 6, 30, Algorithm::Sha1);
|
||||
$data = $this->DecodeDataUri($tfa->getQRCodeImageAsDataUri('Test&Label', 'VMR466AB62ZBOKHE'));
|
||||
$this->assertEquals('image/png', $data['mimetype']);
|
||||
$this->assertEquals('base64', $data['encoding']);
|
||||
$this->assertSame('image/png', $data['mimetype']);
|
||||
$this->assertSame('base64', $data['encoding']);
|
||||
$this->assertNotEmpty($data['data']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user